From 52001373e03a5959e2dff2910d69996222f3410b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 17 Sep 2024 11:04:27 -0300 Subject: [PATCH] feat: show users you follow first in search getPubkeysBySearch() function refactored to accept a followList argument --- src/controllers/api/accounts.ts | 7 ++++++- src/controllers/api/search.ts | 17 ++++++++++++----- src/utils/search.ts | 34 ++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index f14b8b5d..ae3c7e10 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -117,6 +117,7 @@ const accountSearchController: AppController = async (c) => { const { signal } = c.req.raw; const { limit } = c.get('pagination'); const kysely = await Storages.kysely(); + const viewerPubkey = await c.get('signer')?.getPublicKey(); const result = accountSearchQuerySchema.safeParse(c.req.query()); @@ -135,7 +136,11 @@ const accountSearchController: AppController = async (c) => { return c.json(pubkey ? [await accountFromPubkey(pubkey)] : []); } - const pubkeys = await getPubkeysBySearch(kysely, { q: query, limit }); + const followList: string[] = []; + if (viewerPubkey) { + followList.push(...await getFollowedPubkeys(viewerPubkey)); + } + const pubkeys = (await getPubkeysBySearch(kysely, { q: query, limit, followList })).slice(0, limit); 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 05d42044..ebcbc4ca 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -10,6 +10,7 @@ import { extractIdentifier, lookupPubkey } from '@/utils/lookup.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; +import { getFollowedPubkeys } from '@/queries.ts'; import { getPubkeysBySearch } from '@/utils/search.ts'; const searchQuerySchema = z.object({ @@ -26,6 +27,7 @@ type SearchQuery = z.infer; const searchController: AppController = async (c) => { const result = searchQuerySchema.safeParse(c.req.query()); const { signal } = c.req.raw; + const viewerPubkey = await c.get('signer')?.getPublicKey(); if (!result.success) { return c.json({ error: 'Bad request', schema: result.error }, 422); @@ -49,9 +51,7 @@ const searchController: AppController = async (c) => { if (event) { events = [event]; } - events.push(...(await searchEvents(result.data, signal))); - - const viewerPubkey = await c.get('signer')?.getPublicKey(); + events.push(...(await searchEvents({ ...result.data, viewerPubkey }, signal))); const [accounts, statuses] = await Promise.all([ Promise.all( @@ -76,7 +76,10 @@ const searchController: AppController = async (c) => { }; /** Get events for the search params. */ -async function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: AbortSignal): Promise { +async function searchEvents( + { q, type, limit, account_id, viewerPubkey }: SearchQuery & { viewerPubkey?: string }, + signal: AbortSignal, +): Promise { if (type === 'hashtags') return Promise.resolve([]); const filter: NostrFilter = { @@ -93,7 +96,11 @@ async function searchEvents({ q, type, limit, account_id }: SearchQuery, signal: if (type === 'accounts') { const kysely = await Storages.kysely(); - pubkeys.push(...(await getPubkeysBySearch(kysely, { q, limit }))); + const followList: string[] = []; + if (viewerPubkey) { + followList.push(...await getFollowedPubkeys(viewerPubkey)); + } + pubkeys.push(...(await getPubkeysBySearch(kysely, { q, limit, followList }))); if (!filter?.authors) { filter.authors = pubkeys; diff --git a/src/utils/search.ts b/src/utils/search.ts index 460c2525..81b9240a 100644 --- a/src/utils/search.ts +++ b/src/utils/search.ts @@ -3,14 +3,30 @@ import { Kysely, sql } from 'kysely'; 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 }) { - const { q, limit } = opts; - const pubkeys = (await sql<{ pubkey: string }>` - SELECT *, word_similarity(${q}, search) AS sml - FROM author_search - WHERE ${q} % search - ORDER BY sml DESC, search LIMIT ${limit} - `.execute(kysely)).rows.map(({ pubkey }) => pubkey); +export async function getPubkeysBySearch( + kysely: Kysely, + opts: { q: string; limit: number; followList: string[] }, +) { + const { q, limit, followList } = opts; - return pubkeys; + let query = kysely + .selectFrom('author_search') + .select((eb) => [ + 'pubkey', + 'search', + eb.fn('word_similarity', [sql`${q}`, 'search']).as('sml'), + ]) + .where(() => sql`${q} % search`) + .orderBy(['sml desc', 'search']) + .limit(limit); + + const pubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); + + if (followList.length > 0) { + query = query.where('pubkey', 'in', followList); + } + + const followingPubkeys = new Set((await query.execute()).map(({ pubkey }) => pubkey)); + + return Array.from(followingPubkeys.union(pubkeys)); }