Spark feed generator template
at main 131 lines 3.2 kB view raw
1import { Hono } from "hono"; 2import { configure, getConsoleSink, getLogger, Logger } from "@logtape/logtape"; 3import { getPrettyFormatter } from "@logtape/pretty"; 4import { Database } from "./db/connection.ts"; 5import { DidResolver } from "@atp/identity"; 6import { AuthVerifier } from "./utils/auth.ts"; 7import { createServer } from "./lex/index.ts"; 8import { env } from "./utils/env.ts"; 9 10import describeFeedGenerator from "./api/describeFeedGenerator.ts"; 11import getFeedSkeleton from "./api/getFeedSkeleton.ts"; 12import wellKnown from "./api/well-known.ts"; 13import health from "./api/health.ts"; 14import { Ingester } from "./ingester/index.ts"; 15 16await configure({ 17 sinks: { 18 console: getConsoleSink({ 19 formatter: getPrettyFormatter({ 20 properties: true, 21 categoryStyle: "underline", 22 messageColor: "rgb(255, 255, 255)", 23 categoryColor: "rgb(255, 255, 255)", 24 messageStyle: "reset", 25 }), 26 }), 27 }, 28 loggers: [ 29 { category: "feedgen", lowestLevel: "info", sinks: ["console"] }, 30 { category: ["logtape", "meta"], lowestLevel: "error", sinks: ["console"] }, 31 ], 32}); 33 34const logger = getLogger("feedgen"); 35 36const ownDid = `did:web:${env.SPRK_FEEDGEN_DOMAIN}`; 37const didResolver = new DidResolver({}); 38const authVerifier = new AuthVerifier(ownDid, didResolver); 39const db = new Database(); 40 41const ctx = { 42 db, 43 logger, 44 ownDid, 45 didResolver, 46 authVerifier, 47}; 48 49export type AppContext = { 50 db: Database; 51 logger: Logger; 52 ownDid: string; 53 didResolver: DidResolver; 54 authVerifier: AuthVerifier; 55}; 56 57export type AppEnv = { 58 Bindings: AppContext; 59}; 60 61const app = new Hono<AppEnv>(); 62const server = createServer(); 63describeFeedGenerator(server, ctx); 64getFeedSkeleton(server, ctx); 65 66app.route("/", server.xrpc.app); 67 68app.use("*", async (c, next) => { 69 // Initialize c.env if it doesn't exist (for testing compatibility) 70 if (!c.env) { 71 c.env = {} as AppContext; 72 } 73 c.env.ownDid = ownDid; 74 c.env.didResolver = didResolver; 75 c.env.authVerifier = authVerifier; 76 c.env.logger = logger; 77 await next(); 78}); 79 80app.get("/", (c) => { 81 return c.text( 82 "This is a Feed Generation service! Most endpoints are under /xrpc", 83 ); 84}); 85 86// Initialize database connection when server starts 87 88export async function startServer() { 89 logger.info("Starting Feed Generator service"); 90 91 try { 92 await db.connect(); 93 } catch (err) { 94 logger.error("Failed to connect to database", { err }); 95 Deno.exit(1); 96 } 97 98 const ingester = new Ingester(db); 99 ingester.firehose.start(); 100 101 const { SPRK_HOST, SPRK_PORT } = env; 102 Deno.serve({ 103 hostname: SPRK_HOST, 104 port: SPRK_PORT, 105 onListen: (info) => { 106 logger.info(`Server listening on http://${info.hostname}:${info.port}`); 107 }, 108 }, app.fetch); 109 110 // Handle shutdown gracefully 111 const shutdown = async () => { 112 logger.info("Shutting down..."); 113 await db.disconnect(); 114 Deno.exit(0); 115 }; 116 117 Deno.addSignalListener("SIGINT", shutdown); 118 Deno.addSignalListener("SIGTERM", shutdown); 119 120 logger.info("Feed Generator service is running"); 121 122 app.route("/.well-known", wellKnown); 123 app.route("/", health); 124 125 return app; 126} 127 128// For development and testing 129if (import.meta.main) { 130 startServer(); 131}