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