Barazo AppView backend
barazo.forum
1import { and, eq, inArray } from 'drizzle-orm'
2import type { Database } from '../db/index.js'
3import { users } from '../db/schema/users.js'
4import { communityProfiles } from '../db/schema/community-profiles.js'
5import { resolveProfile, type SourceProfile, type CommunityOverride } from './resolve-profile.js'
6
7/**
8 * Compact author profile for embedding in topic/reply responses.
9 * Intentionally excludes bannerUrl and bio to keep payloads small.
10 */
11export interface AuthorProfile {
12 did: string
13 handle: string
14 displayName: string | null
15 avatarUrl: string | null
16}
17
18/**
19 * Batch-resolve author profiles for a list of DIDs.
20 *
21 * When `communityDid` is provided, per-community profile overrides are
22 * applied (display name, avatar) via `resolveProfile()`.
23 *
24 * Returns a Map keyed by DID for O(1) lookup during serialization.
25 */
26export async function resolveAuthors(
27 dids: string[],
28 communityDid: string | null,
29 db: Database
30): Promise<Map<string, AuthorProfile>> {
31 const uniqueDids = [...new Set(dids)]
32 if (uniqueDids.length === 0) {
33 return new Map()
34 }
35
36 // Batch query 1: source profiles from users table
37 const userRows: SourceProfile[] = await db
38 .select({
39 did: users.did,
40 handle: users.handle,
41 displayName: users.displayName,
42 avatarUrl: users.avatarUrl,
43 bannerUrl: users.bannerUrl,
44 bio: users.bio,
45 })
46 .from(users)
47 .where(inArray(users.did, uniqueDids))
48
49 const sourceMap = new Map<string, SourceProfile>()
50 for (const row of userRows) {
51 sourceMap.set(row.did, row)
52 }
53
54 // Batch query 2: community profile overrides (only when community context exists)
55 const overrideMap = new Map<string, CommunityOverride>()
56 if (communityDid) {
57 const overrideRows = await db
58 .select({
59 did: communityProfiles.did,
60 displayName: communityProfiles.displayName,
61 avatarUrl: communityProfiles.avatarUrl,
62 bannerUrl: communityProfiles.bannerUrl,
63 bio: communityProfiles.bio,
64 })
65 .from(communityProfiles)
66 .where(
67 and(
68 inArray(communityProfiles.did, uniqueDids),
69 eq(communityProfiles.communityDid, communityDid)
70 )
71 )
72
73 for (const row of overrideRows) {
74 overrideMap.set(row.did, {
75 displayName: row.displayName,
76 avatarUrl: row.avatarUrl,
77 bannerUrl: row.bannerUrl,
78 bio: row.bio,
79 })
80 }
81 }
82
83 // Merge: resolve each DID using resolveProfile, then project to AuthorProfile
84 const result = new Map<string, AuthorProfile>()
85 for (const did of uniqueDids) {
86 const source = sourceMap.get(did) ?? {
87 did,
88 handle: did,
89 displayName: null,
90 avatarUrl: null,
91 bannerUrl: null,
92 bio: null,
93 }
94
95 const resolved = resolveProfile(source, overrideMap.get(did) ?? null)
96
97 result.set(did, {
98 did: resolved.did,
99 handle: resolved.handle,
100 displayName: resolved.displayName,
101 avatarUrl: resolved.avatarUrl,
102 })
103 }
104
105 return result
106}