[READ ONLY MIRROR] Spark Social AppView Server
github.com/sprksocial/server
atproto
deno
hono
lexicon
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";
18
19await configureLogger();
20
21// Create app without starting services
22export function createApp(ctx: AppContext): Hono<AppEnv> {
23 const app = new Hono<AppEnv>();
24
25 app.use("*", cors());
26 app.use("*", logger());
27 app.use("*", async (c, next) => {
28 c.env = ctx;
29 await next();
30 });
31
32 // Lexicon/XRPC server and routers
33 const lexServer = createServer();
34 API(lexServer, ctx);
35 app.route("/", lexServer.xrpc.app);
36
37 app.route("/.well-known", wellKnown);
38 app.route("/", health);
39 return app;
40}
41
42// Setup function to create context and app
43export function setupApp(): { app: Hono<AppEnv>; ctx: AppContext } {
44 // Setup logger and database
45 const appLogger = getLogger(["appview"]);
46 const cfg = ServerConfig.readEnv();
47 const db = new Database(cfg);
48 db.connect();
49
50 // DID and resolver setup
51 const idResolver = new IdResolver({ plcUrl: cfg.plcUrl });
52
53 const dataplane = new DataPlane(db, idResolver);
54 const hydrator = new Hydrator(dataplane);
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 ctx = {
70 db,
71 dataplane,
72 hydrator,
73 views,
74 logger: appLogger,
75 idResolver,
76 cfg,
77 authVerifier,
78 };
79
80 const app = createApp(ctx);
81 return { app, ctx };
82}
83
84// Start server function
85export function startServer() {
86 const { app, ctx } = setupApp();
87
88 // Start HTTP server immediately
89 const { port } = ctx.cfg;
90 Deno.serve({
91 port,
92 onListen: (info) => {
93 ctx.logger.info(`Server listening on ${info.hostname}:${info.port}`);
94 },
95 }, app.fetch);
96
97 // Handle shutdown
98 const shutdown = async (signal: string) => {
99 ctx.logger.info(`Received ${signal}; shutting down...`);
100 try {
101 ctx.logger.info("Disconnecting database...");
102 await ctx.db.disconnect();
103 } catch (err) {
104 ctx.logger.error("Error disconnecting database during shutdown", { err });
105 }
106 ctx.logger.info("Shutdown complete");
107 Deno.exit(0);
108 };
109
110 Deno.addSignalListener("SIGINT", () => shutdown("SIGINT"));
111 Deno.addSignalListener("SIGTERM", () => shutdown("SIGTERM"));
112}
113
114// Start the server if this file is run directly
115if (import.meta.main) {
116 startServer();
117}