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}