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:
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user