Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/

fix(search): deduplicate profiles when multiple current positions exist (#109)

The LEFT JOIN on positions produced duplicate rows when a profile had
multiple current positions. Add DID-based dedup filter after mapping
DB results, keeping only the highest-ranked occurrence of each profile.

authored by

Guido X Jansen and committed by
GitHub
b1a25f1f 0bd01f8c

+52 -15
+23 -15
src/routes/search.ts
··· 34 34 LIMIT ${limitNum} OFFSET ${offsetNum} 35 35 `); 36 36 37 - const dbProfiles = results.rows.map((row) => { 38 - const r = row as Record<string, unknown>; 39 - const profile: Record<string, unknown> = { 40 - did: r.did, 41 - handle: r.handle, 42 - headline: r.headline, 43 - about: r.about, 44 - claimed: true, 45 - }; 46 - if (r.displayName != null) profile.displayName = r.displayName; 47 - if (r.avatarUrl != null) profile.avatar = r.avatarUrl; 48 - if (r.currentRole != null) profile.currentRole = r.currentRole; 49 - if (r.currentCompany != null) profile.currentCompany = r.currentCompany; 50 - return profile; 51 - }); 37 + const seen = new Set<string>(); 38 + const dbProfiles = results.rows 39 + .map((row) => { 40 + const r = row as Record<string, unknown>; 41 + const profile: Record<string, unknown> = { 42 + did: r.did, 43 + handle: r.handle, 44 + headline: r.headline, 45 + about: r.about, 46 + claimed: true, 47 + }; 48 + if (r.displayName != null) profile.displayName = r.displayName; 49 + if (r.avatarUrl != null) profile.avatar = r.avatarUrl; 50 + if (r.currentRole != null) profile.currentRole = r.currentRole; 51 + if (r.currentCompany != null) profile.currentCompany = r.currentCompany; 52 + return profile; 53 + }) 54 + .filter((p) => { 55 + const did = p.did as string; 56 + if (seen.has(did)) return false; 57 + seen.add(did); 58 + return true; 59 + }); 52 60 53 61 // Try AT Protocol handle resolution as fallback 54 62 try {
+29
tests/routes/search.test.ts
··· 169 169 expect(erlend.avatar).toBeUndefined(); 170 170 }); 171 171 172 + it('deduplicates profiles when multiple current positions exist', async () => { 173 + // Insert a second current position for the same profile 174 + await db 175 + .insert(positions) 176 + .values({ 177 + did: 'did:plc:search-with-role', 178 + rkey: '3searchpos2', 179 + companyName: 'Beta Inc', 180 + title: 'Advisor', 181 + startDate: '2024-01', 182 + current: true, 183 + createdAt: new Date(), 184 + }) 185 + .onConflictDoNothing(); 186 + 187 + const res = await app.inject({ method: 'GET', url: '/api/search/profiles?q=TypeScript' }); 188 + expect(res.statusCode).toBe(200); 189 + const body = res.json(); 190 + const matches = body.profiles.filter( 191 + (p: { did: string }) => p.did === 'did:plc:search-with-role', 192 + ); 193 + expect(matches).toHaveLength(1); 194 + 195 + // Cleanup 196 + await db.execute( 197 + sql`DELETE FROM positions WHERE did = 'did:plc:search-with-role' AND rkey = '3searchpos2'`, 198 + ); 199 + }); 200 + 172 201 it('does not leak rank field in response', async () => { 173 202 const res = await app.inject({ method: 'GET', url: '/api/search/profiles?q=TypeScript' }); 174 203 const body = res.json();