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

reorganization (#3)

authored by

Davi Rodrigues and committed by
GitHub
59ed58bd f84a0e9d

+193 -142
+16 -4
services/appview/src/db.ts
··· 437 437 Repost: this.connection.model<RepostDocument>('Repost', repostSchema), 438 438 Music: this.connection.model<MusicDocument>('Music', musicSchema), 439 439 Look: this.connection.model<LookDocument>('Look', lookSchema), 440 - Generator: this.connection.model<GeneratorDocument>('Generator', generatorSchema), 441 - Takedown: this.connection.model<TakedownDocument>('Takedown', takedownSchema), 442 - RepoTakedown: this.connection.model<RepoTakedownDocument>('RepoTakedown', repoTakedownSchema), 443 - BlobTakedown: this.connection.model<BlobTakedownDocument>('BlobTakedown', blobTakedownSchema), 440 + Generator: this.connection.model<GeneratorDocument>( 441 + 'Generator', 442 + generatorSchema, 443 + ), 444 + Takedown: this.connection.model<TakedownDocument>( 445 + 'Takedown', 446 + takedownSchema, 447 + ), 448 + RepoTakedown: this.connection.model<RepoTakedownDocument>( 449 + 'RepoTakedown', 450 + repoTakedownSchema, 451 + ), 452 + BlobTakedown: this.connection.model<BlobTakedownDocument>( 453 + 'BlobTakedown', 454 + blobTakedownSchema, 455 + ), 444 456 } 445 457 } 446 458
+12 -15
services/appview/src/index.ts
··· 13 13 createIdResolver, 14 14 } from './id-resolver.js' 15 15 import { takedownFilterMiddleware } from './middleware/takedown-filter.js' 16 - import { createGetProfileRouter } from './routes/actor/getProfile.js' 17 - import { createSearchActorRouter } from './routes/actor/searchActor.js' 16 + import { createGetProfileRouter } from './routes/so/sprk/actor/getProfile.js' 17 + import { createSearchActorRouter } from './routes/so/sprk/actor/searchActor.js' 18 + import { createGetAuthorFeedRouter } from './routes/so/sprk/feed/getAuthorFeed.js' 19 + import { createGetPostsRouter } from './routes/so/sprk/feed/getPosts.js' 20 + import { createGetPostThreadRouter } from './routes/so/sprk/feed/getPostThread.js' 21 + import { createGetFollowersRouter } from './routes/so/sprk/graph/getFollowers.js' 22 + import { createGetFollowsRouter } from './routes/so/sprk/graph/getFollows.js' 18 23 import { createTakedownRouter } from './routes/admin/takedowns.js' 19 - import { createGetAuthorFeedRouter } from './routes/feed/getAuthorFeed.js' 20 - import { createGetPostsRouter } from './routes/feed/getPosts.js' 21 - import { createGetPostThreadRouter } from './routes/feed/getPostThread.js' 22 - import { createGetFollowersRouter } from './routes/graph/getFollowers.js' 23 - import { createGetFollowsRouter } from './routes/graph/getFollows.js' 24 + import { createUpdateSubjectStatusRouter } from './routes/com/atproto/admin/updateSubjectStatus.js' 25 + import wellKnownRouter from './well-known.js' 24 26 import { TakedownService } from './services/takedown.js' 25 - import wellKnownRouter from './well-known.js' 26 27 27 28 export type AppContext = { 28 29 db: Database ··· 65 66 66 67 const app = new Hono() 67 68 68 - // Middleware 69 69 app.use('*', logger()) 70 70 71 - // Set context variables for auth middleware 72 71 app.use('*', async (c, next) => { 73 - // Type-safe way to set context variables 74 72 c.set('serviceDid', serviceDid) 75 73 c.set('didResolver', baseIdResolver.did) 76 74 c.set('takedownService', takedownService) 77 75 await next() 78 76 }) 79 77 80 - // Apply takedown filter middleware to all routes 81 78 app.use('*', takedownFilterMiddleware) 82 79 83 80 // TODO: Remove this after getAuthorFeedRouter is properly implemented on frontend ··· 91 88 const getFollowsRouter = createGetFollowsRouter(ctx) 92 89 const getAuthorFeedRouter = createGetAuthorFeedRouter(ctx) 93 90 const searchActorRouter = createSearchActorRouter(ctx) 91 + const updateSubjectStatusRouter = createUpdateSubjectStatusRouter(ctx) 92 + const takedownRouter = createTakedownRouter(ctx) 94 93 95 94 app.route('/', getPostsRouter) 96 95 app.route('/', getPostThreadRouter) ··· 99 98 app.route('/', getFollowsRouter) 100 99 app.route('/', getAuthorFeedRouter) 101 100 app.route('/', searchActorRouter) 102 - 103 - // Create and configure the takedown router 104 - const takedownRouter = createTakedownRouter({ takedownService }) 101 + app.route('/', updateSubjectStatusRouter) 105 102 app.route('/', takedownRouter) 106 103 107 104 app.route('/', wellKnownRouter())
+6 -6
services/appview/src/routes/actor/getProfile.ts services/appview/src/routes/so/sprk/actor/getProfile.ts
··· 1 1 import { ensureValidDid, isValidHandle } from '@atproto/syntax' 2 2 import { Hono } from 'hono' 3 3 4 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 5 - import { AppContext } from '../../index.js' 6 - import type { Label } from '../../lexicon/types/com/atproto/label/defs.js' 7 - import type * as ComAtprotoRepoStrongRef from '../../lexicon/types/com/atproto/repo/strongRef.js' 8 - import type * as SoSprkActorDefs from '../../lexicon/types/so/sprk/actor/defs.js' 9 - import type * as SoSprkGraphDefs from '../../lexicon/types/so/sprk/graph/defs.js' 4 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 5 + import { AppContext } from '../../../../index.js' 6 + import type { Label } from '../../../../lexicon/types/com/atproto/label/defs.js' 7 + import type * as ComAtprotoRepoStrongRef from '../../../../lexicon/types/com/atproto/repo/strongRef.js' 8 + import type * as SoSprkActorDefs from '../../../../lexicon/types/so/sprk/actor/defs.js' 9 + import type * as SoSprkGraphDefs from '../../../../lexicon/types/so/sprk/graph/defs.js' 10 10 11 11 export const createGetProfileRouter = (ctx: AppContext) => { 12 12 const router = new Hono()
+4 -4
services/appview/src/routes/actor/searchActor.ts services/appview/src/routes/so/sprk/actor/searchActor.ts
··· 1 1 import { Hono } from 'hono' 2 - import { AppContext } from '../../index.js' 3 - import type { Label } from '../../lexicon/types/com/atproto/label/defs.js' 4 - import type * as SoSprkActorDefs from '../../lexicon/types/so/sprk/actor/defs.js' 5 - import type * as SoSprkActorSearch from '../../lexicon/types/so/sprk/actor/searchActors.js' 2 + import { AppContext } from '../../../../index.js' 3 + import type { Label } from '../../../../lexicon/types/com/atproto/label/defs.js' 4 + import type * as SoSprkActorDefs from '../../../../lexicon/types/so/sprk/actor/defs.js' 5 + import type * as SoSprkActorSearch from '../../../../lexicon/types/so/sprk/actor/searchActors.js' 6 6 7 7 // Helper to escape user input for safe RegExp usage 8 8 function escapeRegExp(str: string): string {
-93
services/appview/src/routes/admin/takedowns.ts
··· 223 223 } 224 224 }) 225 225 226 - // XRPC endpoint for Ozone integration: com.atproto.admin.updateSubjectStatus 227 - takedownRoutes.post('/xrpc/com.atproto.admin.updateSubjectStatus', adminAuthMiddleware, zValidator('json', z.object({ 228 - subject: z.object({ 229 - $type: z.string(), 230 - did: z.string().optional(), 231 - uri: z.string().optional(), 232 - cid: z.string().optional(), 233 - }), 234 - takedown: z.object({ 235 - applied: z.boolean(), 236 - ref: z.string().optional(), 237 - }), 238 - })), async (c) => { 239 - const { subject, takedown } = c.req.valid('json') 240 - const adminDid = c.get('did') 241 - 242 - try { 243 - // Handle different subject types 244 - if (subject.$type === 'com.atproto.admin.defs#repoRef') { 245 - // Repository (user account) takedown 246 - if (!subject.did) { 247 - throw new HTTPException(400, { message: 'DID is required for repo takedowns' }) 248 - } 249 - 250 - if (takedown.applied) { 251 - // Apply takedown 252 - await takedownService.takedownRepo({ 253 - did: subject.did, 254 - reason: 'Moderation via Ozone', 255 - adminDid, 256 - ref: takedown.ref, 257 - }) 258 - } else { 259 - // Remove takedown 260 - await takedownService.removeRepoTakedown(subject.did) 261 - } 262 - } else if (subject.$type === 'com.atproto.repo.strongRef') { 263 - // Record (post) takedown 264 - if (!subject.uri || !subject.cid) { 265 - throw new HTTPException(400, { message: 'URI and CID are required for record takedowns' }) 266 - } 267 - 268 - if (takedown.applied) { 269 - // Apply takedown 270 - await takedownService.takedownContent({ 271 - targetUri: subject.uri, 272 - targetCid: subject.cid, 273 - reason: 'Moderation via Ozone', 274 - adminDid, 275 - }) 276 - } else { 277 - // Remove takedown 278 - await takedownService.removeTakedown(subject.uri) 279 - } 280 - } else if (subject.$type === 'com.atproto.admin.defs#repoBlobRef') { 281 - // Blob (image/attachment) takedown 282 - if (!subject.did || !subject.cid) { 283 - throw new HTTPException(400, { message: 'DID and CID are required for blob takedowns' }) 284 - } 285 - 286 - if (takedown.applied) { 287 - // Apply takedown 288 - await takedownService.takedownBlob({ 289 - did: subject.did, 290 - cid: subject.cid, 291 - reason: 'Moderation via Ozone', 292 - adminDid, 293 - ref: takedown.ref, 294 - }) 295 - } else { 296 - // Remove takedown 297 - await takedownService.removeBlobTakedown(subject.did, subject.cid) 298 - } 299 - } else { 300 - throw new HTTPException(400, { message: `Unsupported subject type: ${subject.$type}` }) 301 - } 302 - 303 - // Return the response format expected by Ozone 304 - return c.json({ 305 - subject, 306 - takedown: takedown.applied ? { 307 - applied: takedown.applied, 308 - ref: takedown.ref 309 - } : undefined 310 - }) 311 - } catch (error) { 312 - if (error instanceof HTTPException) { 313 - throw error 314 - } 315 - throw new HTTPException(500, { message: 'Failed to update subject status' }) 316 - } 317 - }) 318 - 319 226 return takedownRoutes 320 227 }
+135
services/appview/src/routes/com/atproto/admin/updateSubjectStatus.ts
··· 1 + import { Hono } from 'hono' 2 + import { zValidator } from '@hono/zod-validator' 3 + import { z } from 'zod' 4 + import { HTTPException } from 'hono/http-exception' 5 + import { TakedownService } from '../../../../services/takedown.js' 6 + import { adminAuthMiddleware } from '../../../../auth/middleware.js' 7 + import type * as ComAtprotoAdminUpdateSubjectStatus from '../../../../lexicon/types/com/atproto/admin/updateSubjectStatus.js' 8 + import type * as ComAtprotoAdminDefs from '../../../../lexicon/types/com/atproto/admin/defs.js' 9 + import type * as ComAtprotoRepoStrongRef from '../../../../lexicon/types/com/atproto/repo/strongRef.js' 10 + 11 + type UpdateSubjectStatusContext = { 12 + takedownService: TakedownService 13 + } 14 + 15 + export const createUpdateSubjectStatusRouter = ( 16 + ctx: UpdateSubjectStatusContext, 17 + ) => { 18 + const router = new Hono() 19 + const takedownService = ctx.takedownService 20 + 21 + // XRPC endpoint for Ozone integration: com.atproto.admin.updateSubjectStatus 22 + router.post( 23 + '/xrpc/com.atproto.admin.updateSubjectStatus', 24 + adminAuthMiddleware, 25 + zValidator( 26 + 'json', 27 + z.object({ 28 + subject: z.object({ 29 + $type: z.string(), 30 + did: z.string().optional(), 31 + uri: z.string().optional(), 32 + cid: z.string().optional(), 33 + }), 34 + takedown: z.object({ 35 + applied: z.boolean(), 36 + ref: z.string().optional(), 37 + }), 38 + }), 39 + ), 40 + async (c) => { 41 + const { subject, takedown } = c.req.valid('json') 42 + const adminDid = c.get('did') 43 + 44 + try { 45 + // Handle different subject types 46 + if (subject.$type === 'com.atproto.admin.defs#repoRef') { 47 + // Repository (user account) takedown 48 + if (!subject.did) { 49 + throw new HTTPException(400, { 50 + message: 'DID is required for repo takedowns', 51 + }) 52 + } 53 + 54 + if (takedown.applied) { 55 + // Apply takedown 56 + await takedownService.takedownRepo({ 57 + did: subject.did, 58 + reason: 'Moderation via Ozone', 59 + adminDid, 60 + ref: takedown.ref, 61 + }) 62 + } else { 63 + // Remove takedown 64 + await takedownService.removeRepoTakedown(subject.did) 65 + } 66 + } else if (subject.$type === 'com.atproto.repo.strongRef') { 67 + // Record (post) takedown 68 + if (!subject.uri || !subject.cid) { 69 + throw new HTTPException(400, { 70 + message: 'URI and CID are required for record takedowns', 71 + }) 72 + } 73 + 74 + if (takedown.applied) { 75 + // Apply takedown 76 + await takedownService.takedownContent({ 77 + targetUri: subject.uri, 78 + targetCid: subject.cid, 79 + reason: 'Moderation via Ozone', 80 + adminDid, 81 + }) 82 + } else { 83 + // Remove takedown 84 + await takedownService.removeTakedown(subject.uri) 85 + } 86 + } else if (subject.$type === 'com.atproto.admin.defs#repoBlobRef') { 87 + // Blob (image/attachment) takedown 88 + if (!subject.did || !subject.cid) { 89 + throw new HTTPException(400, { 90 + message: 'DID and CID are required for blob takedowns', 91 + }) 92 + } 93 + 94 + if (takedown.applied) { 95 + // Apply takedown 96 + await takedownService.takedownBlob({ 97 + did: subject.did, 98 + cid: subject.cid, 99 + reason: 'Moderation via Ozone', 100 + adminDid, 101 + ref: takedown.ref, 102 + }) 103 + } else { 104 + // Remove takedown 105 + await takedownService.removeBlobTakedown(subject.did, subject.cid) 106 + } 107 + } else { 108 + throw new HTTPException(400, { 109 + message: `Unsupported subject type: ${subject.$type}`, 110 + }) 111 + } 112 + 113 + // Return the response format expected by Ozone 114 + return c.json({ 115 + subject, 116 + takedown: takedown.applied 117 + ? { 118 + applied: takedown.applied, 119 + ref: takedown.ref, 120 + } 121 + : undefined, 122 + }) 123 + } catch (error) { 124 + if (error instanceof HTTPException) { 125 + throw error 126 + } 127 + throw new HTTPException(500, { 128 + message: 'Failed to update subject status', 129 + }) 130 + } 131 + }, 132 + ) 133 + 134 + return router 135 + }
+3 -3
services/appview/src/routes/feed/getAuthorFeed.ts services/appview/src/routes/so/sprk/feed/getAuthorFeed.ts
··· 1 1 import { Hono } from 'hono' 2 2 import { HTTPException } from 'hono/http-exception' 3 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 4 - import { AppContext } from '../../index.js' 5 - import { transformPostToPostView } from '../../utils/post-transformer.js' 3 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 4 + import { AppContext } from '../../../../index.js' 5 + import { transformPostToPostView } from '../../../../utils/post-transformer.js' 6 6 7 7 export const createGetAuthorFeedRouter = (ctx: AppContext) => { 8 8 const router = new Hono()
+5 -5
services/appview/src/routes/feed/getPostThread.ts services/appview/src/routes/so/sprk/feed/getPostThread.ts
··· 1 1 import { Hono } from 'hono' 2 - import { OutputSchema as GetPostThreadView } from '../../lexicon/types/so/sprk/feed/getPostThread.js' 3 - import type * as SoSprkFeedDefs from '../../lexicon/types/so/sprk/feed/defs.js' 4 - import { AppContext } from '../../index.js' 5 - import { transformPostToPostView } from '../../utils/post-transformer.js' 6 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 2 + import { OutputSchema as GetPostThreadView } from '../../../../lexicon/types/so/sprk/feed/getPostThread.js' 3 + import type * as SoSprkFeedDefs from '../../../../lexicon/types/so/sprk/feed/defs.js' 4 + import { AppContext } from '../../../../index.js' 5 + import { transformPostToPostView } from '../../../../utils/post-transformer.js' 6 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 7 7 8 8 export const createGetPostThreadRouter = (ctx: AppContext) => { 9 9 const router = new Hono()
+6 -6
services/appview/src/routes/feed/getPosts.ts services/appview/src/routes/so/sprk/feed/getPosts.ts
··· 1 1 import { Hono } from 'hono' 2 2 3 - import { OutputSchema as GetPostsView } from '../../lexicon/types/so/sprk/feed/getPosts.js' 4 - import { AppContext } from '../../index.js' 5 - import { transformPostToPostView } from '../../utils/post-transformer.js' 6 - import { Database } from '../../db.js' 7 - import type * as SoSprkFeedDefs from '../../lexicon/types/so/sprk/feed/defs.js' 8 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 3 + import { OutputSchema as GetPostsView } from '../../../../lexicon/types/so/sprk/feed/getPosts.js' 4 + import { AppContext } from '../../../../index.js' 5 + import { transformPostToPostView } from '../../../../utils/post-transformer.js' 6 + import { Database } from '../../../../db.js' 7 + import type * as SoSprkFeedDefs from '../../../../lexicon/types/so/sprk/feed/defs.js' 8 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 9 9 10 10 // Function to fetch posts by URIs 11 11 async function getPosts(
+3 -3
services/appview/src/routes/graph/getFollowers.ts services/appview/src/routes/so/sprk/graph/getFollowers.ts
··· 1 1 import { Hono } from 'hono' 2 2 3 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 4 - import { AppContext } from '../../index.js' 5 - import type * as SoSprkActorDefs from '../../lexicon/types/so/sprk/actor/defs.js' 3 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 4 + import { AppContext } from '../../../../index.js' 5 + import type * as SoSprkActorDefs from '../../../../lexicon/types/so/sprk/actor/defs.js' 6 6 7 7 export const createGetFollowersRouter = (ctx: AppContext) => { 8 8 const router = new Hono()
+3 -3
services/appview/src/routes/graph/getFollows.ts services/appview/src/routes/so/sprk/graph/getFollows.ts
··· 1 1 import { Hono } from 'hono' 2 2 3 - import { optionalAuthMiddleware } from '../../auth/middleware.js' 4 - import { AppContext } from '../../index.js' 5 - import type * as SoSprkActorDefs from '../../lexicon/types/so/sprk/actor/defs.js' 3 + import { optionalAuthMiddleware } from '../../../../auth/middleware.js' 4 + import { AppContext } from '../../../../index.js' 5 + import type * as SoSprkActorDefs from '../../../../lexicon/types/so/sprk/actor/defs.js' 6 6 7 7 export const createGetFollowsRouter = (ctx: AppContext) => { 8 8 const router = new Hono()