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 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 } = env
26
27 const logger = pino({ name: 'server start' })
28 const db = createDb(':memory:')
29 await migrateToLatest(db)
30 const ingester = new Ingester(db)
31 const oauthClient = await createClient(db)
32 ingester.start()
33 const ctx = {
34 db,
35 ingester,
36 logger,
37 oauthClient,
38 }
39
40 const app: Express = express()
41
42 // Set the application to trust the reverse proxy
43 app.set('trust proxy', true)
44
45 // TODO: middleware for sqlite server
46 // TODO: middleware for OAuth
47
48 // Middlewares
49 app.use(express.json())
50 app.use(express.urlencoded({ extended: true }))
51 app.use(cors({ origin: env.CORS_ORIGIN, credentials: true }))
52 app.use(
53 helmet({
54 contentSecurityPolicy: {
55 directives: {
56 // allow oauth redirect when submitting login form
57 formAction: null,
58 },
59 },
60 }),
61 )
62
63 // Request logging
64 app.use(requestLogger)
65
66 // Routes
67 const router = createRouter(ctx)
68 app.use(router)
69
70 // Error handlers
71 app.use(errorHandler())
72
73 const server = app.listen(env.PORT)
74 await events.once(server, 'listening')
75 logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`)
76
77 return new Server(app, server, ctx)
78 }
79
80 async close() {
81 this.ctx.logger.info('sigint received, shutting down')
82 this.ctx.ingester.destroy()
83 return new Promise<void>((resolve) => {
84 this.server.close(() => {
85 this.ctx.logger.info('server closed')
86 resolve()
87 })
88 })
89 }
90}