Scratch space for learning atproto app development
1import events from 'node:events'
2import type http from 'node:http'
3import express, { type Express } from 'express'
4import { pino } from 'pino'
5
6import { createDb, migrateToLatest } from '#/db'
7import { env } from '#/env'
8import { Ingester } from '#/firehose/ingester'
9import { createRouter } from '#/routes'
10import { createClient } from '#/auth/client'
11import { createResolver } from '#/ident/resolver'
12import type { AppContext } from '#/config'
13
14export class Server {
15 constructor(
16 public app: express.Application,
17 public server: http.Server,
18 public ctx: AppContext
19 ) {}
20
21 static async create() {
22 const { NODE_ENV, HOST, PORT, DB_PATH } = env
23
24 const logger = pino({ name: 'server start' })
25 const db = createDb(DB_PATH)
26 await migrateToLatest(db)
27 const ingester = new Ingester(db)
28 const oauthClient = await createClient(db)
29 const resolver = createResolver()
30 ingester.start()
31 const ctx = {
32 db,
33 ingester,
34 logger,
35 oauthClient,
36 resolver,
37 }
38
39 const app: Express = express()
40
41 // Set the application to trust the reverse proxy
42 app.set('trust proxy', true)
43
44 // Middlewares
45 app.use(express.json())
46 app.use(express.urlencoded({ extended: true }))
47
48 // Routes
49 const router = createRouter(ctx)
50 app.use(router)
51
52 // Error handlers
53 app.use((_req, res) => res.sendStatus(404))
54
55 const server = app.listen(env.PORT)
56 await events.once(server, 'listening')
57 logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`)
58
59 return new Server(app, server, ctx)
60 }
61
62 async close() {
63 this.ctx.logger.info('sigint received, shutting down')
64 this.ctx.ingester.destroy()
65 return new Promise<void>((resolve) => {
66 this.server.close(() => {
67 this.ctx.logger.info('server closed')
68 resolve()
69 })
70 })
71 }
72}