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

at 3b73895e29748ca524bbe040b656ddb4e167104b 131 lines 3.7 kB view raw
1import { Hono } from "hono"; 2import { cors } from "hono/cors"; 3import { logger } from "hono/logger"; 4import { Database } from "./data-plane/db/index.ts"; 5import { createAuthVerifier } from "./auth-verifier.ts"; 6import API from "./api/index.ts"; 7import { createServer } from "./lex/index.ts"; 8import wellKnown from "./api/well-known.ts"; 9import health from "./api/health.ts"; 10import { IdResolver, MemoryCache } from "@atp/identity"; 11import { DataPlane } from "./data-plane/index.ts"; 12import { Hydrator } from "./hydration/index.ts"; 13import { Views } from "./views/index.ts"; 14import { AppContext, AppEnv } from "./context.ts"; 15import { ServerConfig } from "./config.ts"; 16import { defaultLabelerHeader, parseLabelerHeader } from "./util.ts"; 17import { PushService } from "./utils/push.ts"; 18 19// Create app without starting services 20export function createApp(ctx: AppContext): Hono<AppEnv> { 21 const app = new Hono<AppEnv>(); 22 23 app.use("*", cors()); 24 app.use("*", logger()); 25 app.use("*", async (c, next) => { 26 c.env = ctx; 27 await next(); 28 }); 29 30 // Lexicon/XRPC server and routers 31 const lexServer = createServer(); 32 API(lexServer, ctx); 33 34 app.route("/.well-known", wellKnown); 35 app.route("/", health); 36 app.route("/", lexServer.xrpc.app); 37 38 return app; 39} 40 41// Setup function to create context and app 42export function setupApp(): { app: Hono<AppEnv>; ctx: AppContext } { 43 const cfg = ServerConfig.readEnv(); 44 const db = new Database(cfg); 45 db.connect(); 46 47 // DID and resolver setup with caching 48 const idResolver = new IdResolver({ 49 plcUrl: cfg.plcUrl, 50 didCache: new MemoryCache(), 51 }); 52 53 const dataplane = new DataPlane(db, idResolver); 54 const hydrator = new Hydrator(dataplane, cfg.labelsFromIssuerDids); 55 const views = new Views({ 56 indexedAtEpoch: cfg.indexedAtEpoch, 57 videoCdn: cfg.videoCdn, 58 mediaCdn: cfg.mediaCdn, 59 thumbCdn: cfg.thumbCdn, 60 }); 61 62 const authVerifier = createAuthVerifier(dataplane, { 63 ownDid: cfg.serverDid, 64 alternateAudienceDids: [], 65 modServiceDid: cfg.modServiceDid, 66 adminPasses: cfg.adminPasswords, 67 }); 68 69 const reqLabelers = (req: Request) => { 70 const val = req.headers.get("atproto-accept-labelers") ?? undefined; 71 const parsed = parseLabelerHeader(val); 72 if (!parsed) return defaultLabelerHeader(cfg.labelsFromIssuerDids); 73 return parsed; 74 }; 75 76 // Create push service for badge management 77 const pushService = new PushService(dataplane.pushTokens, db, { 78 enabled: cfg.pushEnabled, 79 fcmServiceAccount: cfg.fcmServiceAccount, 80 }); 81 82 const ctx = { 83 db, 84 dataplane, 85 hydrator, 86 views, 87 idResolver, 88 cfg, 89 authVerifier, 90 reqLabelers, 91 pushService, 92 }; 93 94 const app = createApp(ctx); 95 return { app, ctx }; 96} 97 98// Start server function 99export function startServer() { 100 const { app, ctx } = setupApp(); 101 102 // Start HTTP server immediately 103 const { port } = ctx.cfg; 104 Deno.serve({ 105 port, 106 onListen: (info) => { 107 console.info(`Server listening on ${info.hostname}:${info.port}`); 108 }, 109 }, app.fetch); 110 111 // Handle shutdown 112 const shutdown = async (signal: string) => { 113 console.info(`Received ${signal}; shutting down...`); 114 try { 115 console.info("Disconnecting database..."); 116 await ctx.db.disconnect(); 117 } catch (err) { 118 console.error("Error disconnecting database during shutdown", { err }); 119 } 120 console.info("Shutdown complete"); 121 Deno.exit(0); 122 }; 123 124 Deno.addSignalListener("SIGINT", () => shutdown("SIGINT")); 125 Deno.addSignalListener("SIGTERM", () => shutdown("SIGTERM")); 126} 127 128// Start the server if this file is run directly 129if (import.meta.main) { 130 startServer(); 131}