diff --git a/apps/web/app/[locale]/page.tsx b/apps/web/app/[locale]/page.tsx index 83ca8ea..0c12519 100644 --- a/apps/web/app/[locale]/page.tsx +++ b/apps/web/app/[locale]/page.tsx @@ -26,11 +26,10 @@ async function getStats() { .where(sql`${skills.sourceFormat} = 'skill.md' AND ${skills.isBlocked} = false AND ${browseReady}`); const totalSkills = skillsResult[0]?.count ?? 0; - // Get total downloads (browse-ready only) + // Get total downloads (ALL skills — downloads are real user actions) const downloadsResult = await db .select({ sum: sql`coalesce(sum(${skills.downloadCount}), 0)::int` }) - .from(skills) - .where(browseReady); + .from(skills); const totalDownloads = downloadsResult[0]?.sum ?? 0; // Get total categories diff --git a/apps/web/app/api/stats/route.test.ts b/apps/web/app/api/stats/route.test.ts index 15e7020..62d4163 100644 --- a/apps/web/app/api/stats/route.test.ts +++ b/apps/web/app/api/stats/route.test.ts @@ -18,7 +18,8 @@ vi.mock('@/lib/cache', () => ({ // Mock the db module - must be before imports that use it vi.mock('@skillhub/db', () => { - const mockStatsRow = [{ totalSkills: 100, totalDownloads: 5000, totalContributors: 50 }]; + const mockStatsRow = [{ totalSkills: 100, totalContributors: 50 }]; + const mockDownloadsRow = [{ totalDownloads: 5000 }]; const mockCategoryRow = [{ count: 8 }]; return { createDb: vi.fn(() => ({ @@ -28,9 +29,11 @@ vi.mock('@skillhub/db', () => { if (table === 'categories-table') { return Promise.resolve(mockCategoryRow); } - // skills table query has .where() chained + // skills table: thenable for downloads (no .where()) + .where() for filtered stats return { where: vi.fn().mockResolvedValue(mockStatsRow), + then: (resolve: (v: unknown) => void, reject: (e: unknown) => void) => + Promise.resolve(mockDownloadsRow).then(resolve, reject), }; }), }), diff --git a/apps/web/app/api/stats/route.ts b/apps/web/app/api/stats/route.ts index cf8f599..0b3c279 100644 --- a/apps/web/app/api/stats/route.ts +++ b/apps/web/app/api/stats/route.ts @@ -37,16 +37,22 @@ export async function GET(request: NextRequest) { // Browse-ready filter: exclude duplicates and aggregators const browseReady = sql`${skills.isDuplicate} = false AND (${skills.skillType} IS NULL OR ${skills.skillType} != 'aggregator')`; - // Consolidate all skill stats into a single query (browse-ready only) + // Browse-ready skill stats (skills count + contributors) const statsResult = await db .select({ totalSkills: sql`count(*)::int`, - totalDownloads: sql`coalesce(sum(${skills.downloadCount}), 0)::int`, totalContributors: sql`count(distinct ${skills.githubOwner})::int`, }) .from(skills) .where(browseReady); + // Total downloads: count ALL downloads (real user actions, not filtered) + const downloadsResult = await db + .select({ + totalDownloads: sql`coalesce(sum(${skills.downloadCount}), 0)::int`, + }) + .from(skills); + // Get category count in separate query (different table) const categoryResult = await db .select({ count: sql`count(*)::int` }) @@ -57,7 +63,7 @@ export async function GET(request: NextRequest) { const data: StatsData = { totalSkills: stats?.totalSkills ?? 0, - totalDownloads: stats?.totalDownloads ?? 0, + totalDownloads: downloadsResult[0]?.totalDownloads ?? 0, totalCategories, totalContributors: stats?.totalContributors ?? 0, platforms: 5, // Claude, Codex, Copilot, Cursor, Windsurf