diff --git a/convex/schema.ts b/convex/schema.ts index 96d85d4..a59584b 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -109,6 +109,11 @@ const skills = defineTable({ .index('by_stats_installs_all_time', ['statsInstallsAllTime', 'updatedAt']) .index('by_batch', ['batch']) .index('by_active_updated', ['softDeletedAt', 'updatedAt']) + .index('by_active_created', ['softDeletedAt', 'createdAt']) + .index('by_active_name', ['softDeletedAt', 'displayName']) + .index('by_active_stats_downloads', ['softDeletedAt', 'statsDownloads', 'updatedAt']) + .index('by_active_stats_stars', ['softDeletedAt', 'statsStars', 'updatedAt']) + .index('by_active_stats_installs_all_time', ['softDeletedAt', 'statsInstallsAllTime', 'updatedAt']) .index('by_canonical', ['canonicalSkillId']) .index('by_fork_of', ['forkOf.skillId']) diff --git a/convex/skills.ts b/convex/skills.ts index 44be105..1c2ed02 100644 --- a/convex/skills.ts +++ b/convex/skills.ts @@ -37,6 +37,15 @@ const MAX_ACTIVE_REPORTS_PER_USER = 20 const AUTO_HIDE_REPORT_THRESHOLD = 3 const MAX_REPORT_REASON_SAMPLE = 5 +const SORT_INDEXES = { + newest: 'by_active_created', + updated: 'by_active_updated', + name: 'by_active_name', + downloads: 'by_active_stats_downloads', + stars: 'by_active_stats_stars', + installs: 'by_active_stats_installs_all_time', +} as const + function isSkillVersionId( value: Id<'skillVersions'> | null | undefined, ): value is Id<'skillVersions'> { @@ -1033,14 +1042,28 @@ export const listPublicPage = query({ export const listPublicPageV2 = query({ args: { paginationOpts: paginationOptsValidator, + sort: v.optional( + v.union( + v.literal('newest'), + v.literal('updated'), + v.literal('downloads'), + v.literal('installs'), + v.literal('stars'), + v.literal('name'), + ), + ), + dir: v.optional(v.union(v.literal('asc'), v.literal('desc'))), }, handler: async (ctx, args) => { - // Use the new index to filter out soft-deleted skills at query time. + const sort = args.sort ?? 'newest' + const dir = args.dir ?? 'desc' + + // Use the index to filter out soft-deleted skills at query time. // softDeletedAt === undefined means active (non-deleted) skills only. const result = await paginator(ctx.db, schema) .query('skills') - .withIndex('by_active_updated', (q) => q.eq('softDeletedAt', undefined)) - .order('desc') + .withIndex(SORT_INDEXES[sort], (q) => q.eq('softDeletedAt', undefined)) + .order(dir) .paginate(args.paginationOpts) // Build the public skill entries (fetch latestVersion + ownerHandle) diff --git a/src/__tests__/skills-index.test.tsx b/src/__tests__/skills-index.test.tsx index 769bacc..5efd7e1 100644 --- a/src/__tests__/skills-index.test.tsx +++ b/src/__tests__/skills-index.test.tsx @@ -48,10 +48,10 @@ describe('SkillsIndex', () => { it('requests the first skills page', () => { render() - // usePaginatedQuery should be called with the API endpoint and empty args + // usePaginatedQuery should be called with the API endpoint and sort/dir args expect(usePaginatedQueryMock).toHaveBeenCalledWith( expect.anything(), - {}, + { sort: 'newest', dir: 'desc' }, { initialNumItems: 25 }, ) }) diff --git a/src/routes/skills/index.tsx b/src/routes/skills/index.tsx index bf0a2a6..6e7049c 100644 --- a/src/routes/skills/index.tsx +++ b/src/routes/skills/index.tsx @@ -84,7 +84,7 @@ export function SkillsIndex() { results: paginatedResults, status: paginationStatus, loadMore: loadMorePaginated, - } = usePaginatedQuery(api.skills.listPublicPageV2, hasQuery ? 'skip' : {}, { + } = usePaginatedQuery(api.skills.listPublicPageV2, hasQuery ? 'skip' : { sort, dir }, { initialNumItems: pageSize, })