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 helmet from 'helmet'
5import { pino } from 'pino'
6
7import { createDb, migrateToLatest } from '#/db'
8import { env } from '#/env'
9import { Ingester } from '#/firehose/ingester'
10import errorHandler from '#/middleware/errorHandler'
11import requestLogger from '#/middleware/requestLogger'
12import { createRouter } from '#/routes'
13import { createClient } from '#/auth/client'
14import { createResolver } from '#/ident/resolver'
15import type { AppContext } from '#/config'
16
17export class Server {
18 constructor(
19 public app: express.Application,
20 public server: http.Server,
21 public ctx: AppContext
22 ) {}
23
24 static async create() {
25 const { NODE_ENV, HOST, PORT, DB_PATH } = env
26
27 const logger = pino({ name: 'server start' })
28 const db = createDb(DB_PATH)
29 await migrateToLatest(db)
30 const ingester = new Ingester(db)
31 const oauthClient = await createClient(db)
32 const resolver = createResolver()
33 ingester.start()
34 const ctx = {
35 db,
36 ingester,
37 logger,
38 oauthClient,
39 resolver,
40 }
41
42 const app: Express = express()
43
44 // Set the application to trust the reverse proxy
45 app.set('trust proxy', true)
46
47 // TODO: middleware for sqlite server
48 // TODO: middleware for OAuth
49
50 // Middlewares
51 app.use(express.json())
52 app.use(express.urlencoded({ extended: true }))
53 app.use(
54 helmet({
55 contentSecurityPolicy: {
56 directives: {
57 // allow oauth redirect when submitting login form
58 formAction: null,
59 },
60 },
61 })
62 )
63
64 // Request logging
65 app.use(requestLogger)
66
67 // Routes
68 const router = createRouter(ctx)
69 app.use(router)
70
71 // Error handlers
72 app.use(errorHandler())
73
74 const server = app.listen(env.PORT)
75 await events.once(server, 'listening')
76 logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`)
77
78 return new Server(app, server, ctx)
79 }
80
81 async close() {
82 this.ctx.logger.info('sigint received, shutting down')
83 this.ctx.ingester.destroy()
84 return new Promise<void>((resolve) => {
85 this.server.close(() => {
86 this.ctx.logger.info('server closed')
87 resolve()
88 })
89 })
90 }
91}