1import type http from "node:http";
2import path from "node:path";
3
4import express from "express";
5import { OAuthClient } from "@atproto/oauth-client-node";
6import { pino } from "pino";
7import { Firehose } from "@atproto/sync";
8import { createRouter } from "#/routes";
9import {
10 createIdResolver,
11 createBidirectionalResolver,
12 BidirectionalResolver,
13} from "#/id-resolver";
14import { createIngester } from "#/ingester";
15import { createClient } from "#/auth/client";
16import { Database, createDb, migrateToLatest } from "#/db";
17import { env } from "#/lib";
18
19export type Ctx = {
20 ingester: Firehose;
21 logger: pino.Logger;
22 oauthClient: OAuthClient;
23 resolver: BidirectionalResolver;
24 db: Database;
25};
26
27export class Server {
28 constructor(
29 public app: express.Application,
30 public server: http.Server,
31 public ctx: Ctx,
32 ) {}
33
34 static async create() {
35 const db: Database = createDb(env.PLONK_DB_PATH);
36 await migrateToLatest(db);
37 const idResolver = createIdResolver();
38 const ctx: Ctx = {
39 ingester: createIngester(db, idResolver),
40 logger: pino({ name: "server start" }),
41 oauthClient: await createClient(db),
42 resolver: createBidirectionalResolver(idResolver),
43 db: db,
44 };
45
46 const app = express();
47 app.set("trust proxy", true);
48 app.use(express.static(path.join(__dirname, "public")));
49 app.set("views", path.join(__dirname, "views"));
50 app.set("view engine", "pug");
51
52 const router = createRouter(ctx);
53 app.use(express.json());
54 app.use(express.urlencoded({ extended: true }));
55 app.use(router);
56 app.use((_req, res) => res.sendStatus(404));
57
58 const server = app.listen(env.PLONK_PORT);
59
60 return new Server(app, server, ctx);
61 }
62
63 async close() {
64 this.ctx.logger.info("shutting down");
65 await this.ctx.ingester.destroy();
66 return new Promise<void>((resolve) => {
67 this.server.close(() => {
68 this.ctx.logger.info("server closed");
69 resolve();
70 });
71 });
72 }
73}
74
75const run = async () => {
76 const server = await Server.create();
77
78 const onCloseSignal = async () => {
79 setTimeout(() => process.exit(1), 10000).unref(); // Force shutdown after 10s
80 await server.close();
81 process.exit();
82 };
83
84 process.on("SIGINT", onCloseSignal);
85 process.on("SIGTERM", onCloseSignal);
86};
87
88run();