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 = createResolver() 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}