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

remove old feed route (#11)

authored by

Davi Rodrigues and committed by
GitHub
c1ab2580 04fc4d4a

-132
-128
services/appview/src/feed/feed.ts
··· 1 - import { Hono } from 'hono' 2 - import { AppContext } from '../index.js' 3 - import { Agent } from '@atproto/api' 4 - import { HTTPException } from 'hono/http-exception' 5 - import { CID } from 'multiformats/cid' 6 - 7 - export const createFeedRouter = (ctx: AppContext) => { 8 - const router = new Hono() 9 - 10 - router.get('/actorFeed/:actorDid', async (c) => { 11 - const { actorDid } = c.req.param() 12 - 13 - 14 - const didDoc = await ctx.resolver.resolveDidToDidDoc(actorDid) 15 - const pdsUrl = didDoc.pds 16 - 17 - if (!pdsUrl) { 18 - throw new HTTPException(400, { 19 - message: 'Missing PDS URL in access token', 20 - }) 21 - } 22 - 23 - const agent = new Agent(new URL(pdsUrl)) 24 - 25 - const listRes = await agent.com.atproto.repo.listRecords( 26 - { 27 - collection: 'so.sprk.feed.post', 28 - repo: actorDid, 29 - limit: 30, 30 - }, 31 - ) 32 - 33 - if (!listRes.success) { 34 - throw new HTTPException(400, { 35 - message: 'Failed to list records from PDS', 36 - }) 37 - } 38 - const actorDidDoc = await ctx.resolver.resolveDidToDidDoc(actorDid) 39 - 40 - const actor = await agent.com.atproto.repo.getRecord({ 41 - repo: actorDid, 42 - collection: 'app.bsky.actor.profile', 43 - rkey: 'self', 44 - }) 45 - 46 - if (!actor.success) { 47 - throw new HTTPException(400, { 48 - message: 'Failed to get profile', 49 - }) 50 - } 51 - 52 - // Prepare to collect all post URIs to fetch like counts in a single query 53 - const postUris = listRes.data.records.map((record) => record.uri) 54 - 55 - // Create a map to store like counts for each post 56 - const likeCounts = new Map() 57 - 58 - // Get like counts for all posts in a single query for efficiency 59 - if (postUris.length > 0) { 60 - try { 61 - // Aggregate counts for each subject (post URI) 62 - const likeAggregation = await ctx.db.models.Like.aggregate([ 63 - { $match: { subject: { $in: postUris } } }, 64 - { $group: { _id: '$subject', count: { $sum: 1 } } }, 65 - ]) 66 - 67 - // Populate the map with the results 68 - likeAggregation.forEach((result) => { 69 - likeCounts.set(result._id, result.count) 70 - }) 71 - } catch (error) { 72 - console.error('Error fetching like counts:', error) 73 - // Continue with zero counts if there's an error 74 - } 75 - } 76 - 77 - const feed = { 78 - feed: listRes.data.records.map((record) => { 79 - // Get the like count for this post, defaulting to 0 if not found 80 - if ((record.value as any).embed?.$type !== 'so.sprk.embed.video') { 81 - return undefined 82 - } 83 - const likeCount = likeCounts.get(record.uri) || 0 84 - const blobCid: CID = (record.value as any).embed?.video?.ref 85 - 86 - return { 87 - post: { 88 - uri: record.uri, 89 - cid: record.cid, 90 - author: { 91 - did: actorDid, 92 - handle: actorDidDoc.handle, 93 - displayName: '', 94 - avatar: 'placeholder-avatar-url', 95 - viewer: { 96 - muted: false, 97 - blockedBy: false, 98 - }, 99 - labels: [], 100 - createdAt: new Date().toISOString(), 101 - }, 102 - record: record.value, 103 - embed: { 104 - $type: 'so.sprk.embed.video#view', 105 - cid: record.cid, 106 - playlist: `https://media.sprk.so/video/${actorDid}/${blobCid.toString()}`, 107 - thumbnail: `https://thumb.sprk.so/${actorDid}/${blobCid.toString()}/thumbnail`, 108 - }, 109 - replyCount: 0, 110 - repostCount: 0, 111 - likeCount, 112 - quoteCount: 0, 113 - indexedAt: new Date().toISOString(), 114 - viewer: { 115 - threadMuted: false, 116 - embeddingDisabled: false, 117 - }, 118 - labels: [], 119 - }, 120 - } 121 - }), 122 - } 123 - 124 - return c.json(feed) 125 - }) 126 - 127 - return router 128 - }
-4
services/appview/src/index.ts
··· 7 7 import { pino } from 'pino' 8 8 import { Database } from './data-plane/server/index.js' 9 9 import { env } from './env.js' 10 - import { createFeedRouter } from './feed/feed.js' 11 10 import { AuthVerifier, createAuthVerifier } from './auth/auth-verifier.js' 12 11 import API from './api/index.js' 13 12 import { createServer } from './lexicon/index.js' ··· 108 107 109 108 app.use('*', takedownFilterMiddleware) 110 109 111 - // TODO: Remove this after getAuthorFeedRouter is properly implemented on frontend 112 - const feedRouter = createFeedRouter(ctx) 113 110 const lexServer = createServer() 114 111 const server = API(lexServer, ctx) 115 112 ··· 124 121 const getRecordRouter = createGetRecordRouter(ctx) 125 122 const resolveHandleRouter = createResolveHandleRouter(ctx) 126 123 127 - app.route('/', feedRouter) 128 124 app.route('/', getPostsRouter) 129 125 app.route('/', getPostThreadRouter) 130 126 app.route('/', getProfileRouter)