[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.

ingest generator

+132
+2
services/ingester/src/db/connection.ts
··· 10 10 repostSchema, 11 11 musicSchema, 12 12 lookSchema, 13 + generatorSchema, 13 14 } from './models.js' 14 15 import { env } from '../utils/env.js' 15 16 import { pino } from 'pino' ··· 31 32 Repost: this.connection.model('Repost', repostSchema), 32 33 Music: this.connection.model('Music', musicSchema), 33 34 Look: this.connection.model('Look', lookSchema), 35 + Generator: this.connection.model('Generator', generatorSchema), 34 36 } 35 37 } 36 38
+38
services/ingester/src/db/models.ts
··· 305 305 musicSchema.index({ authorDid: 1, createdAt: -1 }) 306 306 musicSchema.index({ tags: 1, createdAt: -1 }) 307 307 308 + export interface GeneratorDocument extends Document { 309 + uri: string 310 + did: string 311 + displayName: string 312 + description?: string 313 + descriptionFacets?: Array<any> 314 + avatar?: string 315 + acceptsInteractions?: boolean 316 + labels?: any 317 + contentMode?: string 318 + authorDid: string 319 + authorHandle: string 320 + createdAt: string 321 + indexedAt: string 322 + cid: string 323 + } 324 + 325 + export const generatorSchema = new Schema<GeneratorDocument>({ 326 + uri: { type: String, required: true, unique: true, index: true }, 327 + did: { type: String, required: true, index: true }, 328 + displayName: { type: String, required: true }, 329 + description: { type: String, required: false }, 330 + descriptionFacets: { type: [Object], required: false }, 331 + avatar: { type: String, required: false }, 332 + acceptsInteractions: { type: Boolean, required: false }, 333 + labels: { type: Object, required: false }, 334 + contentMode: { type: String, required: false }, 335 + authorDid: { type: String, required: true, index: true }, 336 + authorHandle: { type: String, required: true }, 337 + createdAt: { type: String, required: true }, 338 + indexedAt: { type: String, required: true }, 339 + cid: { type: String, required: true }, 340 + }) 341 + 342 + generatorSchema.index({ authorDid: 1, createdAt: -1 }) 343 + generatorSchema.index({ did: 1, createdAt: -1 }) 344 + 308 345 export interface DatabaseModels { 309 346 Like: Model<LikeDocument> 310 347 Post: Model<PostDocument> ··· 315 352 Repost: Model<RepostDocument> 316 353 Music: Model<MusicDocument> 317 354 Look: Model<LookDocument> 355 + Generator: Model<GeneratorDocument> 318 356 }
+86
services/ingester/src/handlers/generator-handler.ts
··· 1 + import { pino } from 'pino' 2 + import { Database } from '../db/connection.js' 3 + import type { NormalizedEvent } from '../types/events.js' 4 + 5 + const logger = pino({ name: 'generator-handler' }) 6 + 7 + export async function handleGeneratorEvent(evt: NormalizedEvent, db: Database): Promise<void> { 8 + // Skip if not a generator event 9 + if (evt.collection !== 'so.sprk.feed.generator') { 10 + return 11 + } 12 + 13 + if (evt.event === 'create' || evt.event === 'update') { 14 + await handleCreateOrUpdate(evt, db) 15 + } else if (evt.event === 'delete') { 16 + await handleDelete(evt, db) 17 + } 18 + } 19 + 20 + async function handleCreateOrUpdate(evt: NormalizedEvent, db: Database): Promise<void> { 21 + const now = new Date() 22 + const record = evt.record 23 + 24 + if (!record) { 25 + logger.warn({ uri: evt.uri }, 'Generator event missing record data') 26 + return 27 + } 28 + 29 + logger.info({ 30 + did: evt.did, 31 + handle: evt.handle, 32 + collection: evt.collection, 33 + uri: evt.uri, 34 + }, 'Processing generator event') 35 + 36 + try { 37 + // Extract generator data from record 38 + const generatorData = { 39 + uri: evt.uri, 40 + did: record.did, 41 + displayName: record.displayName, 42 + description: record.description || null, 43 + descriptionFacets: record.descriptionFacets || [], 44 + avatar: record.avatar || null, 45 + acceptsInteractions: record.acceptsInteractions || false, 46 + labels: record.labels || null, 47 + contentMode: record.contentMode || null, 48 + authorDid: evt.did, 49 + authorHandle: evt.handle || 'unknown', 50 + createdAt: record.createdAt, 51 + indexedAt: now.toISOString(), 52 + cid: evt.commit.cid 53 + } 54 + 55 + // Create or update the generator record 56 + await db.models.Generator.findOneAndUpdate( 57 + { uri: evt.uri }, 58 + generatorData, 59 + { upsert: true, new: true } 60 + ) 61 + 62 + logger.info( 63 + { uri: evt.uri }, 64 + 'Successfully saved generator to database' 65 + ) 66 + } catch (error) { 67 + logger.error( 68 + { error, uri: evt.uri }, 69 + 'Failed to save generator to database' 70 + ) 71 + } 72 + } 73 + 74 + async function handleDelete(evt: NormalizedEvent, db: Database): Promise<void> { 75 + try { 76 + const result = await db.models.Generator.deleteOne({ uri: evt.uri }) 77 + 78 + if (result.deletedCount > 0) { 79 + logger.info({ uri: evt.uri }, 'Successfully removed generator from database') 80 + } else { 81 + logger.warn({ uri: evt.uri }, 'Generator not found in database for deletion') 82 + } 83 + } catch (error) { 84 + logger.error({ error, uri: evt.uri }, 'Failed to delete generator from database') 85 + } 86 + }
+6
services/ingester/src/handlers/index.ts
··· 10 10 import { handleRepostEvent } from './repost-handler.js' 11 11 import { handleMusicEvent } from './music-handler.js' 12 12 import { handleLookEvent } from './look-handler.js' 13 + import { handleGeneratorEvent } from './generator-handler.js' 13 14 14 15 const logger = pino({ name: 'event-handler' }) 15 16 ··· 58 59 59 60 if (evt.collection === 'so.sprk.feed.look') { 60 61 await handleLookEvent(evt, db) 62 + return 63 + } 64 + 65 + if (evt.collection === 'so.sprk.feed.generator') { 66 + await handleGeneratorEvent(evt, db) 61 67 return 62 68 } 63 69