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