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}