[READ ONLY MIRROR] Spark Social AppView Server github.com/sprksocial/server
atproto deno hono lexicon
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

small tweaks to actors

+51 -195
+3 -33
services/appview/src/db.ts
··· 93 93 avatar?: Record<string, any> 94 94 banner?: Record<string, any> 95 95 labels?: Record<string, any> 96 - joinedViaStarterPack?: Record<string, any> 97 96 pinnedPost?: Record<string, any> 98 97 authorDid: string 99 98 authorHandle: string ··· 109 108 avatar: { type: Object, required: false }, 110 109 banner: { type: Object, required: false }, 111 110 labels: { type: Object, required: false }, 112 - joinedViaStarterPack: { type: Object, required: false }, 113 111 pinnedPost: { type: Object, required: false }, 114 112 authorDid: { type: String, required: true, index: true }, 115 113 authorHandle: { type: String, required: true }, ··· 405 403 blobTakedownSchema.index({ did: 1, cid: 1 }, { unique: true }) 406 404 407 405 export interface ActorDocument extends Document { 408 - uri: string 409 406 did: string 410 - handle?: string 411 - profile?: ProfileDocument 412 - profileCid?: string 413 - profileTakedownRef?: string 414 - followersCount: number 415 - followingCount: number 416 - postsCount: number 417 - sortedAt?: Date 407 + handle: string | null 418 408 indexedAt: string 419 - takedownRef?: string 420 - isLabeler: boolean 421 - allowIncomingChatsFrom?: string 422 - upstreamStatus?: string 423 - createdAt?: Date 424 - priorityNotifications: boolean 425 - trustedVerifier?: boolean 426 - labelsDeclaration?: Record<string, any> 409 + takedownRef: string | null 410 + upstreamStatus: string | null 427 411 } 428 412 429 413 export const actorSchema = new Schema<ActorDocument>({ 430 - uri: { type: String, required: true, unique: true, index: true }, 431 414 did: { type: String, required: true, index: true }, 432 415 handle: { type: String, required: false, index: true }, 433 - profile: { type: Schema.Types.ObjectId, ref: 'Profile', required: false }, 434 - profileCid: { type: String, required: false }, 435 - profileTakedownRef: { type: String, required: false }, 436 - followersCount: { type: Number, required: true, default: 0 }, 437 - followingCount: { type: Number, required: true, default: 0 }, 438 - postsCount: { type: Number, required: true, default: 0 }, 439 - sortedAt: { type: Date, required: false }, 440 416 indexedAt: { type: String, required: true }, 441 417 takedownRef: { type: String, required: false }, 442 - isLabeler: { type: Boolean, required: true, default: false }, 443 - allowIncomingChatsFrom: { type: String, required: false }, 444 418 upstreamStatus: { type: String, required: false }, 445 - createdAt: { type: Date, required: false }, 446 - priorityNotifications: { type: Boolean, required: true, default: false }, 447 - trustedVerifier: { type: Boolean, required: false }, 448 - labelsDeclaration: { type: Object, required: false }, 449 419 }) 450 420 451 421 // Add compound indexes for Actor
+18 -85
services/appview/src/routes/so/sprk/actor/getProfile.ts
··· 44 44 let actorDoc = await ctx.db.models.Actor.findOne({ 45 45 did: actorDid, 46 46 }) 47 - .populate('profile') 48 - .lean() 47 + 48 + let profile = await ctx.db.models.Profile.findOne({ 49 + authorDid: actorDid, 50 + }) 49 51 50 - // If no actor or no profile, try indexing 51 - if (!actorDoc || !actorDoc.profile) { 52 + if (!actorDoc) { 52 53 try { 53 54 ctx.logger.info({ did: actorDid }, 'No profile found, attempting to index') 54 55 await ctx.indexingService.indexHandle(actorDid, now, true) ··· 57 58 actorDoc = await ctx.db.models.Actor.findOne({ 58 59 did: actorDid, 59 60 }) 60 - .populate('profile') 61 - .lean() 62 - 63 - ctx.logger.info({ 64 - did: actorDid, 65 - hasActor: !!actorDoc, 66 - hasProfile: !!(actorDoc?.profile) 67 - }, 'State after indexing') 68 61 } catch (error) { 69 62 ctx.logger.error({ error, did: actorDid }, 'Failed to index handle') 70 63 } ··· 74 67 return c.json({ error: 'Actor not found' }, 404) 75 68 } 76 69 77 - if (!actorDoc.profile) { 70 + if (!profile) { 78 71 return c.json({ error: 'Profile not found' }, 404) 79 72 } 80 73 81 - const profile = actorDoc.profile 82 - 83 - // Get follower count 84 - const followersCount = actorDoc.followersCount || 0 85 - 86 - // Get follows count 87 - const followsCount = actorDoc.followingCount || 0 88 - 89 - // Get posts count 90 - const postsCount = actorDoc.postsCount || 0 91 - 92 74 // Use actor's handle if available, otherwise resolve from DID 93 - const profileHandle = actorDoc.handle || await ctx.resolver.resolveDidToHandle(actorDid) 75 + const handle = actorDoc.handle || await ctx.resolver.resolveDidToHandle(actorDid) 94 76 95 77 // Build viewer state if a user is authenticated 96 78 const viewer: SoSprkActorDefs.ViewerState = {} ··· 131 113 if (blockedBy) { 132 114 viewer.blockedBy = true 133 115 } 134 - 135 - // Get known followers (followers of the profile that the viewer also follows) 136 - if (followersCount > 0) { 137 - // Get the followers of this profile 138 - const followers = await ctx.db.models.Follow.find({ 139 - subject: actorDid, 140 - }).lean() 141 - 142 - const followerDids = followers.map((f) => f.authorDid) 143 - 144 - // Check which of these followers the viewer follows 145 - const knownFollowsQuery = await ctx.db.models.Follow.find({ 146 - subject: { $in: followerDids }, 147 - authorDid: viewerDid, 148 - }).lean() 149 - 150 - if (knownFollowsQuery.length > 0) { 151 - const knownFollowerDids = knownFollowsQuery.map((f) => f.subject) 152 - 153 - // Get profiles for known followers 154 - const knownFollowerProfiles = await ctx.db.models.Profile.find({ 155 - authorDid: { $in: knownFollowerDids }, 156 - }) 157 - .limit(3) 158 - .lean() 159 - 160 - const knownFollowersBasic = await Promise.all( 161 - knownFollowerProfiles.map(async (p) => { 162 - const handle = await ctx.resolver.resolveDidToHandle( 163 - p.authorDid, 164 - ) 165 - return { 166 - did: p.authorDid, 167 - handle, 168 - displayName: p.displayName, 169 - avatar: p.avatar 170 - ? `https://media.sprk.so/avatar/tiny/${p.authorDid}/${p.avatar.ref.$link}/webp` 171 - : undefined, 172 - } as SoSprkActorDefs.ProfileViewBasic 173 - }), 174 - ) 175 - 176 - viewer.knownFollowers = { 177 - count: knownFollowsQuery.length, 178 - followers: knownFollowersBasic, 179 - } 180 - } 181 - } 182 116 } 183 117 184 118 // Check for associated services ··· 208 142 ? `https://media.sprk.so/img/tiny/${actorDid}/${profile.banner.ref.$link}/webp` 209 143 : undefined 210 144 211 - // Convert joinedViaStarterPack to the correct type if it exists 212 - let joinedViaStarterPack: 213 - | SoSprkGraphDefs.StarterPackViewBasic 214 - | undefined = undefined 215 - if (profile.joinedViaStarterPack) { 216 - // Type assertion assuming the structure fits the requirements 217 - joinedViaStarterPack = 218 - profile.joinedViaStarterPack as unknown as SoSprkGraphDefs.StarterPackViewBasic 219 - } 220 - 221 145 // Convert labels to the correct type if it exists 222 146 let labels: Label[] | undefined = undefined 223 147 if (profile.labels) { ··· 233 157 profile.pinnedPost as unknown as ComAtprotoRepoStrongRef.Main 234 158 } 235 159 160 + const followersCount = await ctx.db.models.Follow.countDocuments({ 161 + subject: actorDid, 162 + }) 163 + const followsCount = await ctx.db.models.Follow.countDocuments({ 164 + authorDid: actorDid, 165 + }) 166 + const postsCount = await ctx.db.models.Post.countDocuments({ 167 + authorDid: actorDid, 168 + }) 169 + 236 170 // Build the ProfileViewDetailed response 237 171 const profileView: SoSprkActorDefs.ProfileViewDetailed = { 238 172 did: actorDid, 239 - handle: profileHandle, 173 + handle: handle, 240 174 displayName: profile.displayName, 241 175 description: profile.description, 242 176 avatar, ··· 245 179 followsCount, 246 180 postsCount, 247 181 associated: Object.keys(associated).length > 0 ? associated : undefined, 248 - joinedViaStarterPack, 249 182 indexedAt: profile.indexedAt, 250 183 createdAt: profile.createdAt, 251 184 viewer: Object.keys(viewer).length > 0 ? viewer : undefined,
+23 -5
services/appview/src/routes/so/sprk/actor/searchActor.ts
··· 52 52 .limit(limit) 53 53 .lean() 54 54 55 - const actors: SoSprkActorDefs.ProfileView[] = await Promise.all( 56 - profiles.map(async (p) => { 55 + // For handle search, exclude DIDs already found 56 + const foundDids = profiles.map(p => p.authorDid) 57 + const handleFilter = { ...filter, did: { $nin: foundDids } } 58 + const handles = await ctx.db.models.Actor.find(handleFilter) 59 + .sort(sort) 60 + .skip(skip) 61 + .limit(limit) 62 + .lean() 63 + 64 + const handleProfiles = await ctx.db.models.Profile.find({ authorDid: { $in: handles.map(h => h.did) } }).lean() 65 + 66 + const fullProfiles = [...profiles, ...handleProfiles] 67 + 68 + const actors: SoSprkActorDefs.ProfileView[] = (await Promise.all( 69 + fullProfiles.map(async (p) => { 57 70 const avatar = p.avatar 58 71 ? `https://media.sprk.so/avatar/tiny/${p.authorDid}/${(p.avatar as any).ref.$link}/webp` 59 72 : undefined 60 73 const labels = Array.isArray(p.labels) 61 74 ? (p.labels as Label[]) 62 75 : undefined 63 - const handle = await ctx.resolver.resolveDidToHandle(p.authorDid) 76 + const now = new Date().toISOString() 77 + await ctx.indexingService.indexHandle(p.authorDid, now) 78 + const actor = await ctx.db.models.Actor.findOne({ did: p.authorDid }) 79 + if (!actor || !actor.handle) { 80 + return null 81 + } 64 82 return { 65 83 $type: 'so.sprk.actor.defs#profileView', 66 84 did: p.authorDid, 67 - handle: handle, 85 + handle: actor.handle, 68 86 displayName: p.displayName, 69 87 description: p.description, 70 88 avatar, ··· 73 91 labels, 74 92 } satisfies SoSprkActorDefs.ProfileView 75 93 }), 76 - ) 94 + )).filter((actor): actor is { $type: "so.sprk.actor.defs#profileView"; did: string; handle: string; displayName: string | undefined; description: string | undefined; avatar: string | undefined; indexedAt: string; createdAt: string; labels: Label[] | undefined; } => actor !== null) 77 95 78 96 const nextCursor = 79 97 profiles.length === limit ? String(skip + limit) : undefined
+1 -22
services/appview/src/services/indexing.ts
··· 148 148 } 149 149 } 150 150 151 - const existingProfile = await this.db.models.Profile.findOne({ authorDid: did }) 152 - if (existingProfile) { 153 - console.log('existingProfile: ', existingProfile) 154 - } 155 - 156 - // Update or create actor 157 151 await this.db.models.Actor.updateOne( 158 152 { did }, 159 153 { 160 154 $set: { 161 155 handle, 162 156 indexedAt: timestamp, 163 - ...(existingProfile && existingProfile._id ? { 164 - profile: existingProfile._id, 165 - profileCid: existingProfile.cid 166 - } : {}) 167 - }, 168 - $setOnInsert: { 169 - uri: `at://${did}/so.sprk.actor.profile`, 170 - followersCount: 0, 171 - followingCount: 0, 172 - postsCount: 0, 173 - isLabeler: false, 174 - priorityNotifications: false, 175 157 } 176 158 }, 177 159 { upsert: true } 178 160 ) 179 - 180 - if (existingProfile) { 181 - this.logger.info({ did, profileId: existingProfile._id }, 'Linked existing profile to actor during indexing') 182 - } 161 + 183 162 } catch (error) { 184 163 this.logger.error({ error, did }, 'Error indexing handle') 185 164 }
+4 -31
services/ingester/src/db/models.ts
··· 342 342 generatorSchema.index({ authorDid: 1, createdAt: -1 }) 343 343 generatorSchema.index({ did: 1, createdAt: -1 }) 344 344 345 + 345 346 export interface ActorDocument extends Document { 346 - uri: string 347 347 did: string 348 - handle?: string 349 - profile?: ProfileDocument 350 - profileCid?: string 351 - profileTakedownRef?: string 352 - followersCount: number 353 - followingCount: number 354 - postsCount: number 355 - sortedAt?: Date 348 + handle: string | null 356 349 indexedAt: string 357 - takedownRef?: string 358 - isLabeler: boolean 359 - allowIncomingChatsFrom?: string 360 - upstreamStatus?: string 361 - createdAt?: Date 362 - priorityNotifications: boolean 363 - trustedVerifier?: boolean 364 - labelsDeclaration?: Record<string, any> 350 + takedownRef: string | null 351 + upstreamStatus: string | null 365 352 } 366 353 367 354 export const actorSchema = new Schema<ActorDocument>({ 368 - uri: { type: String, required: true, unique: true, index: true }, 369 355 did: { type: String, required: true, index: true }, 370 356 handle: { type: String, required: false, index: true }, 371 - profile: { type: Schema.Types.ObjectId, ref: 'Profile', required: false }, 372 - profileCid: { type: String, required: false }, 373 - profileTakedownRef: { type: String, required: false }, 374 - followersCount: { type: Number, required: true, default: 0 }, 375 - followingCount: { type: Number, required: true, default: 0 }, 376 - postsCount: { type: Number, required: true, default: 0 }, 377 - sortedAt: { type: Date, required: false }, 378 357 indexedAt: { type: String, required: true }, 379 358 takedownRef: { type: String, required: false }, 380 - isLabeler: { type: Boolean, required: true, default: false }, 381 - allowIncomingChatsFrom: { type: String, required: false }, 382 359 upstreamStatus: { type: String, required: false }, 383 - createdAt: { type: Date, required: false }, 384 - priorityNotifications: { type: Boolean, required: true, default: false }, 385 - trustedVerifier: { type: Boolean, required: false }, 386 - labelsDeclaration: { type: Object, required: false }, 387 360 }) 388 361 389 362 // Add compound indexes for Actor
+2 -19
services/ingester/src/services/indexing.ts
··· 22 22 * @param timestamp The timestamp of the operation 23 23 * @param force Force reindexing even if recently indexed 24 24 */ 25 + 25 26 async indexHandle(did: string, timestamp: string, force = false): Promise<void> { 26 27 try { 27 28 // Find existing actor ··· 54 55 } 55 56 } 56 57 57 - const existingProfile = await this.db.models.Profile.findOne({ authorDid: did }) 58 - 59 58 // Update or create actor 60 59 await this.db.models.Actor.updateOne( 61 60 { did }, 62 61 { 63 62 $set: { 64 63 handle, 65 - indexedAt: timestamp, 66 - ...(existingProfile && existingProfile._id ? { 67 - profile: existingProfile._id, 68 - profileCid: existingProfile.cid 69 - } : {}) 64 + indexedAt: timestamp 70 65 }, 71 - $setOnInsert: { 72 - uri: `at://${did}/so.sprk.actor.profile`, 73 - followersCount: 0, 74 - followingCount: 0, 75 - postsCount: 0, 76 - isLabeler: false, 77 - priorityNotifications: false, 78 - } 79 66 }, 80 67 { upsert: true } 81 68 ) 82 - 83 - if (existingProfile) { 84 - this.logger.info({ did, profileId: existingProfile._id }, 'Linked existing profile to actor during indexing') 85 - } 86 69 } catch (error) { 87 70 this.logger.error({ error, did }, 'Error indexing handle') 88 71 }