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 { pino } from 'pino' 5import type { OAuthClient } from '@atproto/oauth-client-node' 6 7import { createDb, migrateToLatest } from '#/db' 8import { env } from '#/env' 9import { Ingester } from '#/firehose/ingester' 10import { createRouter } from '#/routes' 11import { createClient } from '#/auth/client' 12import { createResolver, Resolver } from '#/firehose/resolver' 13import type { Database } from '#/db' 14 15export type AppContext = { 16 db: Database 17 ingester: Ingester 18 logger: pino.Logger 19 oauthClient: OAuthClient 20 resolver: Resolver 21} 22 23export class Server { 24 constructor( 25 public app: express.Application, 26 public server: http.Server, 27 public ctx: AppContext 28 ) {} 29 30 static async create() { 31 const { NODE_ENV, HOST, PORT, DB_PATH } = env 32 33 const logger = pino({ name: 'server start' }) 34 const db = createDb(DB_PATH) 35 await migrateToLatest(db) 36 const ingester = new Ingester(db) 37 const oauthClient = await createClient(db) 38 const resolver = createResolver() 39 ingester.start() 40 const ctx = { 41 db, 42 ingester, 43 logger, 44 oauthClient, 45 resolver, 46 } 47 48 const app: Express = express() 49 50 // Set the application to trust the reverse proxy 51 app.set('trust proxy', true) 52 53 // Middlewares 54 app.use(express.json()) 55 app.use(express.urlencoded({ extended: true })) 56 57 // Routes 58 const router = createRouter(ctx) 59 app.use(router) 60 61 // Error handlers 62 app.use((_req, res) => res.sendStatus(404)) 63 64 const server = app.listen(env.PORT) 65 await events.once(server, 'listening') 66 logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`) 67 68 return new Server(app, server, ctx) 69 } 70 71 async close() { 72 this.ctx.logger.info('sigint received, shutting down') 73 this.ctx.ingester.destroy() 74 return new Promise<void>((resolve) => { 75 this.server.close(() => { 76 this.ctx.logger.info('server closed') 77 resolve() 78 }) 79 }) 80 } 81} 82 83const run = async () => { 84 const server = await Server.create() 85 86 const onCloseSignal = async () => { 87 setTimeout(() => process.exit(1), 10000).unref() // Force shutdown after 10s 88 await server.close() 89 process.exit() 90 } 91 92 process.on('SIGINT', onCloseSignal) 93 process.on('SIGTERM', onCloseSignal) 94} 95 96run()