From a432f04de15f94e38e209d3086918799a57bb5f4 Mon Sep 17 00:00:00 2001 From: Roscoe Rubin-Rottenberg Date: Sat, 4 Apr 2026 14:13:43 -0400 Subject: [PATCH] feat: searchActors personalized sort --- api/so/sprk/actor/searchActorsTypeahead.ts | 6 +++- data-plane/routes/search.ts | 34 +++++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/api/so/sprk/actor/searchActorsTypeahead.ts b/api/so/sprk/actor/searchActorsTypeahead.ts index d11bc18..2d29f0b 100644 --- a/api/so/sprk/actor/searchActorsTypeahead.ts +++ b/api/so/sprk/actor/searchActorsTypeahead.ts @@ -66,7 +66,11 @@ const skeleton = async (inputs: SkeletonFnInput) => { }; } - const res = await ctx.dataplane.search.actorsTypeahead(term, params.limit); + const res = await ctx.dataplane.search.actorsTypeahead( + term, + params.limit, + params.hydrateCtx.viewer, + ); return { dids: res.dids, }; diff --git a/data-plane/routes/search.ts b/data-plane/routes/search.ts index e0ccec5..69e4464 100644 --- a/data-plane/routes/search.ts +++ b/data-plane/routes/search.ts @@ -93,7 +93,7 @@ export class Search { }; } - async actorsTypeahead(term: string, limit = 10) { + async actorsTypeahead(term: string, limit = 10, viewerDid?: string | null) { const cleanedTerm = cleanQuery(term); if (!cleanedTerm) { return { @@ -127,10 +127,14 @@ export class Search { } : { $text: { $search: cleanedTerm } }; const matchingProfiles = await this.db.models.Profile.find(profileQuery) - .select("authorDid -_id") + .select("authorDid followersCount -_id") .limit(candidateLimit * 2) .lean(); + const followerCountMap = new Map( + matchingProfiles.map((p) => [p.authorDid, p.followersCount ?? 0]), + ); + const handleDidSet = new Set(handleDids); const handleProfileDidSet = new Set( matchingProfiles.map((p) => p.authorDid), @@ -143,10 +147,32 @@ export class Search { .map((profile) => profile.authorDid) .filter((did) => !includedDids.has(did) && !handleDidSet.has(did)); - const dids = [...handleProfileDids, ...textProfileDids].slice(0, safeLimit); + // Sort each group by follower count descending + const byFollowers = (a: string, b: string) => + (followerCountMap.get(b) ?? 0) - (followerCountMap.get(a) ?? 0); + handleProfileDids.sort(byFollowers); + textProfileDids.sort(byFollowers); + + let candidates = [...handleProfileDids, ...textProfileDids].slice( + 0, + safeLimit * 2, + ); + + // Boost accounts the viewer already follows to the front + if (viewerDid && candidates.length > 0) { + const viewerFollows = await this.db.models.Follow.find({ + authorDid: viewerDid, + subject: { $in: candidates }, + }).select("subject -_id").lean(); + const followedSet = new Set(viewerFollows.map((f) => f.subject)); + candidates = [ + ...candidates.filter((did) => followedSet.has(did)), + ...candidates.filter((did) => !followedSet.has(did)), + ]; + } return { - dids, + dids: candidates.slice(0, safeLimit), }; } -- 2.51.2