fix(stats): show all downloads without browse-ready filter

Downloads represent real user actions and should not be filtered by
browse-ready criteria. Skills count and contributors remain filtered.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-19 16:48:27 +03:30
parent c531db31db
commit 9b2a741179
3 changed files with 16 additions and 8 deletions

View File

@@ -26,11 +26,10 @@ async function getStats() {
.where(sql`${skills.sourceFormat} = 'skill.md' AND ${skills.isBlocked} = false AND ${browseReady}`); .where(sql`${skills.sourceFormat} = 'skill.md' AND ${skills.isBlocked} = false AND ${browseReady}`);
const totalSkills = skillsResult[0]?.count ?? 0; 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 const downloadsResult = await db
.select({ sum: sql<number>`coalesce(sum(${skills.downloadCount}), 0)::int` }) .select({ sum: sql<number>`coalesce(sum(${skills.downloadCount}), 0)::int` })
.from(skills) .from(skills);
.where(browseReady);
const totalDownloads = downloadsResult[0]?.sum ?? 0; const totalDownloads = downloadsResult[0]?.sum ?? 0;
// Get total categories // Get total categories

View File

@@ -18,7 +18,8 @@ vi.mock('@/lib/cache', () => ({
// Mock the db module - must be before imports that use it // Mock the db module - must be before imports that use it
vi.mock('@skillhub/db', () => { 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 }]; const mockCategoryRow = [{ count: 8 }];
return { return {
createDb: vi.fn(() => ({ createDb: vi.fn(() => ({
@@ -28,9 +29,11 @@ vi.mock('@skillhub/db', () => {
if (table === 'categories-table') { if (table === 'categories-table') {
return Promise.resolve(mockCategoryRow); return Promise.resolve(mockCategoryRow);
} }
// skills table query has .where() chained // skills table: thenable for downloads (no .where()) + .where() for filtered stats
return { return {
where: vi.fn().mockResolvedValue(mockStatsRow), where: vi.fn().mockResolvedValue(mockStatsRow),
then: (resolve: (v: unknown) => void, reject: (e: unknown) => void) =>
Promise.resolve(mockDownloadsRow).then(resolve, reject),
}; };
}), }),
}), }),

View File

@@ -37,16 +37,22 @@ export async function GET(request: NextRequest) {
// Browse-ready filter: exclude duplicates and aggregators // Browse-ready filter: exclude duplicates and aggregators
const browseReady = sql`${skills.isDuplicate} = false AND (${skills.skillType} IS NULL OR ${skills.skillType} != 'aggregator')`; 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 const statsResult = await db
.select({ .select({
totalSkills: sql<number>`count(*)::int`, totalSkills: sql<number>`count(*)::int`,
totalDownloads: sql<number>`coalesce(sum(${skills.downloadCount}), 0)::int`,
totalContributors: sql<number>`count(distinct ${skills.githubOwner})::int`, totalContributors: sql<number>`count(distinct ${skills.githubOwner})::int`,
}) })
.from(skills) .from(skills)
.where(browseReady); .where(browseReady);
// Total downloads: count ALL downloads (real user actions, not filtered)
const downloadsResult = await db
.select({
totalDownloads: sql<number>`coalesce(sum(${skills.downloadCount}), 0)::int`,
})
.from(skills);
// Get category count in separate query (different table) // Get category count in separate query (different table)
const categoryResult = await db const categoryResult = await db
.select({ count: sql<number>`count(*)::int` }) .select({ count: sql<number>`count(*)::int` })
@@ -57,7 +63,7 @@ export async function GET(request: NextRequest) {
const data: StatsData = { const data: StatsData = {
totalSkills: stats?.totalSkills ?? 0, totalSkills: stats?.totalSkills ?? 0,
totalDownloads: stats?.totalDownloads ?? 0, totalDownloads: downloadsResult[0]?.totalDownloads ?? 0,
totalCategories, totalCategories,
totalContributors: stats?.totalContributors ?? 0, totalContributors: stats?.totalContributors ?? 0,
platforms: 5, // Claude, Codex, Copilot, Cursor, Windsurf platforms: 5, // Claude, Codex, Copilot, Cursor, Windsurf