diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index b3c75ae5..4f5b55ff 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -136,7 +136,7 @@ const accountSearchController: AppController = async (c) => { } const followedPubkeys: Set = viewerPubkey ? await getFollowedPubkeys(viewerPubkey) : new Set(); - const pubkeys = Array.from(await getPubkeysBySearch(kysely, { q: query, limit, followedPubkeys })); + const pubkeys = Array.from(await getPubkeysBySearch(kysely, { q: query, limit, offset: 0, followedPubkeys })); let events = event ? [event] : await store.query([{ kinds: [0], authors: pubkeys, limit }], { signal, diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index ce7ca9f3..e8ec6057 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -20,6 +20,7 @@ const searchQuerySchema = z.object({ following: z.boolean().default(false), account_id: n.id().optional(), limit: z.coerce.number().catch(20).transform((value) => Math.min(Math.max(value, 0), 40)), + offset: z.coerce.number().nonnegative().catch(0), }); type SearchQuery = z.infer; @@ -77,7 +78,7 @@ const searchController: AppController = async (c) => { /** Get events for the search params. */ async function searchEvents( - { q, type, limit, account_id, viewerPubkey }: SearchQuery & { viewerPubkey?: string }, + { q, type, limit, offset, account_id, viewerPubkey }: SearchQuery & { viewerPubkey?: string }, signal: AbortSignal, ): Promise { // Hashtag search is not supported. @@ -98,7 +99,7 @@ async function searchEvents( const kysely = await Storages.kysely(); const followedPubkeys = viewerPubkey ? await getFollowedPubkeys(viewerPubkey) : new Set(); - const searchPubkeys = await getPubkeysBySearch(kysely, { q, limit, followedPubkeys }); + const searchPubkeys = await getPubkeysBySearch(kysely, { q, limit, offset, followedPubkeys }); filter.authors = [...searchPubkeys]; filter.search = undefined; diff --git a/src/utils/search.test.ts b/src/utils/search.test.ts index 1acd2f60..056c2927 100644 --- a/src/utils/search.test.ts +++ b/src/utils/search.test.ts @@ -14,17 +14,37 @@ Deno.test('fuzzy search works', async () => { following_count: 0, }).execute(); - assertEquals(await getPubkeysBySearch(db.kysely, { q: 'pat rick', limit: 1, followedPubkeys: new Set() }), new Set()); assertEquals( - await getPubkeysBySearch(db.kysely, { q: 'patrick dosreis', limit: 1, followedPubkeys: new Set() }), + await getPubkeysBySearch(db.kysely, { q: 'pat rick', limit: 1, offset: 0, followedPubkeys: new Set() }), + new Set(), + ); + assertEquals( + await getPubkeysBySearch(db.kysely, { q: 'patrick dosreis', limit: 1, offset: 0, followedPubkeys: new Set() }), new Set([ '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4', ]), ); assertEquals( - await getPubkeysBySearch(db.kysely, { q: 'dosreis.com', limit: 1, followedPubkeys: new Set() }), + await getPubkeysBySearch(db.kysely, { q: 'dosreis.com', limit: 1, offset: 0, followedPubkeys: new Set() }), new Set([ '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4', ]), ); }); + +Deno.test('fuzzy search works with offset', async () => { + await using db = await createTestDB(); + + await db.kysely.insertInto('author_stats').values({ + pubkey: '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4', + search: 'abdcef patrickReiis patrickdosreis.com', + notes_count: 0, + followers_count: 0, + following_count: 0, + }).execute(); + + assertEquals( + await getPubkeysBySearch(db.kysely, { q: 'dosreis.com', limit: 1, offset: 1, followedPubkeys: new Set() }), + new Set(), + ); +}); diff --git a/src/utils/search.ts b/src/utils/search.ts index e17be135..29ecefd9 100644 --- a/src/utils/search.ts +++ b/src/utils/search.ts @@ -5,9 +5,9 @@ import { DittoTables } from '@/db/DittoTables.ts'; /** Get pubkeys whose name and NIP-05 is similar to 'q' */ export async function getPubkeysBySearch( kysely: Kysely, - opts: { q: string; limit: number; followedPubkeys: Set }, + opts: { q: string; limit: number; offset: number; followedPubkeys: Set }, ): Promise> { - const { q, limit, followedPubkeys } = opts; + const { q, limit, followedPubkeys, offset } = opts; let query = kysely .selectFrom('author_stats') @@ -19,7 +19,8 @@ export async function getPubkeysBySearch( .where(() => sql`${q} <% search`) .orderBy(['followers_count desc']) .orderBy(['sml desc', 'search']) - .limit(limit); + .limit(limit) + .offset(offset); const pubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); @@ -29,5 +30,5 @@ export async function getPubkeysBySearch( const followingPubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); - return new Set(Array.from(followingPubkeys.union(pubkeys)).slice(0, limit)); + return new Set(Array.from(followingPubkeys.union(pubkeys))); }