1
0

main.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import type { SignalConstants } from 'os'
  2. import createAuth from './auth'
  3. import createDatabase from './db'
  4. import createExpress from './http'
  5. import createLogger from './log'
  6. import fs from 'fs/promises'
  7. import process from 'process'
  8. import type { Config, Context } from './types'
  9. /** Server application entrypoint. */
  10. async function main(config: Config): Promise<void> {
  11. // Create context
  12. const ctx = <Context>{ config }
  13. ctx.ctx = () => ctx
  14. // Initialize logger
  15. const log = createLogger(ctx)
  16. ctx.log = log
  17. // Initialize auth
  18. const auth = createAuth(ctx)
  19. ctx.auth = auth
  20. // Initialize database connection
  21. const { mongo, db, model } = await createDatabase(ctx)
  22. ctx.mongo = mongo
  23. ctx.db = db
  24. ctx.model = model
  25. log.info('Connected to MongoDB', config.mongo)
  26. // Initialize Express app
  27. let staticsPath: string | undefined = `${ctx.config.fs.root}/public`
  28. try {
  29. const stat = await fs.stat(staticsPath)
  30. if (!stat.isDirectory()) {
  31. ctx.log.warn(`Statics path ${staticsPath} is not a directory, not serving static assets`)
  32. staticsPath = undefined
  33. }
  34. } catch (err) {
  35. ctx.log.warn(`Statics path ${staticsPath} not found, not serving static assets`)
  36. staticsPath = undefined
  37. }
  38. const app = createExpress(ctx, staticsPath)
  39. // Start processes.
  40. // This promise can only be rejected, signifying that the app has stopped
  41. log.info('Starting app')
  42. return new Promise((res, rej) => {
  43. // Start HTTP server
  44. const server = app.listen(config.http.port, config.http.host)
  45. log.info('Listening for HTTP connections', config.http)
  46. // Shutdown function
  47. async function stop(e?: keyof SignalConstants) {
  48. // Force shutdown if any task hangs
  49. const t = setTimeout(() => {
  50. rej(new Error(`Waited ${config.shutdownTimeout}ms to shut down, forcing exit`))
  51. }, config.shutdownTimeout)
  52. await Promise.all([
  53. // Close database connection
  54. (async (): Promise<void> => {
  55. try {
  56. await mongo.close()
  57. log.info('Closed MongoDB connection')
  58. } catch (err) {
  59. log.error('Failed to close MongoDB connection', err)
  60. }
  61. })(),
  62. // Stop HTTP server
  63. new Promise<void>((res, rej) => {
  64. server.close(err => {
  65. if (err) {
  66. log.error('Failed to stop HTTP server', err)
  67. rej(err)
  68. } else {
  69. log.info('Stopped HTTP server')
  70. res()
  71. }
  72. })
  73. }),
  74. ])
  75. clearTimeout(t)
  76. if (e) log.error(`Received ${e}`)
  77. rej()
  78. }
  79. // Shut down on interrupt or terminate
  80. process.on('SIGINT', e => stop(e).catch(rej))
  81. process.on('SIGTERM', e => stop(e).catch(rej))
  82. })
  83. }
  84. export default main