소스 검색

add static content serving from server

Aneurin Barker Snook 1 년 전
부모
커밋
67390162ef
6개의 변경된 파일40개의 추가작업 그리고 3개의 파일을 삭제
  1. 2 1
      .gitignore
  2. 1 0
      package.json
  3. 13 1
      src/http.ts
  4. 4 0
      src/index.ts
  5. 13 1
      src/main.ts
  6. 7 0
      src/types.ts

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
+/public/
+/out/
 node_modules/
 node_modules/
-out/
 .env
 .env

+ 1 - 0
package.json

@@ -5,6 +5,7 @@
   "description": "Keep on top of it all with Herda",
   "description": "Keep on top of it all with Herda",
   "scripts": {
   "scripts": {
     "build": "tsc",
     "build": "tsc",
+    "build-statics": "cd web && npm run build -- --outDir ../public",
     "dev": "nodemon --watch src -e ts --exec 'ts-node src/index.ts'",
     "dev": "nodemon --watch src -e ts --exec 'ts-node src/index.ts'",
     "lint": "eslint --ext .ts src",
     "lint": "eslint --ext .ts src",
     "start": "node out/index.js",
     "start": "node out/index.js",

+ 13 - 1
src/http.ts

@@ -9,7 +9,7 @@ import { version } from '../package.json'
 import type { ErrorRequestHandler, RequestHandler } from 'express'
 import type { ErrorRequestHandler, RequestHandler } from 'express'
 
 
 /** Create an Express application. */
 /** Create an Express application. */
-function createExpress(ctx: Context) {
+function createExpress(ctx: Context, staticsPath?: string) {
   // Initialize app with JSON, CORS, and auth middleware
   // Initialize app with JSON, CORS, and auth middleware
   const app = express()
   const app = express()
   app.use(express.json())
   app.use(express.json())
@@ -18,6 +18,9 @@ function createExpress(ctx: Context) {
 
 
   const prefix = ctx.config.api.prefix
   const prefix = ctx.config.api.prefix
 
 
+  // Static frontend
+  if (staticsPath) app.use(express.static(staticsPath, { fallthrough: true }))
+
   // Misc APIs
   // Misc APIs
   app.get(prefix, index)
   app.get(prefix, index)
 
 
@@ -49,6 +52,15 @@ function createExpress(ctx: Context) {
   // Authentication APIs
   // Authentication APIs
   app.post(`${prefix}/login/account`, account.loginAccount(ctx))
   app.post(`${prefix}/login/account`, account.loginAccount(ctx))
 
 
+  // Static fallback to route to index.html
+  if (staticsPath) app.get('/*', (req, res, next) => {
+    if (!res.headersSent) {
+      res.sendFile(`${staticsPath}/index.html`, err => {
+        next(err)
+      })
+    } else next()
+  })
+
   // Add middleware to handle any errors forwarded from previous handlers via `next(err)`
   // Add middleware to handle any errors forwarded from previous handlers via `next(err)`
   const catchError: ErrorRequestHandler = (err, req, res, next) => {
   const catchError: ErrorRequestHandler = (err, req, res, next) => {
     if (!res.headersSent) {
     if (!res.headersSent) {

+ 4 - 0
src/index.ts

@@ -1,6 +1,7 @@
 import crypto from 'crypto'
 import crypto from 'crypto'
 import dotenv from 'dotenv'
 import dotenv from 'dotenv'
 import main from './main'
 import main from './main'
+import path from 'path'
 
 
 dotenv.config()
 dotenv.config()
 
 
@@ -33,6 +34,9 @@ main({
     },
     },
   },
   },
   cors: undefined,
   cors: undefined,
+  fs: {
+    root: path.dirname(__dirname),
+  },
   http: {
   http: {
     host: process.env.HTTP_HOST || '',
     host: process.env.HTTP_HOST || '',
     port: parseInt(process.env.HTTP_PORT || '5001'),
     port: parseInt(process.env.HTTP_PORT || '5001'),

+ 13 - 1
src/main.ts

@@ -3,6 +3,7 @@ import createAuth from './auth'
 import createDatabase from './db'
 import createDatabase from './db'
 import createExpress from './http'
 import createExpress from './http'
 import createLogger from './log'
 import createLogger from './log'
+import fs from 'fs/promises'
 import process from 'process'
 import process from 'process'
 import type { Config, Context } from './types'
 import type { Config, Context } from './types'
 
 
@@ -28,7 +29,18 @@ async function main(config: Config): Promise<void> {
   log.info('Connected to MongoDB', config.mongo)
   log.info('Connected to MongoDB', config.mongo)
 
 
   // Initialize Express app
   // Initialize Express app
-  const app = createExpress(ctx)
+  let staticsPath: string | undefined = `${ctx.config.fs.root}/public`
+  try {
+    const stat = await fs.stat(staticsPath)
+    if (!stat.isDirectory()) {
+      ctx.log.warn(`Statics path ${staticsPath} is not a directory, not serving static assets`)
+      staticsPath = undefined
+    }
+  } catch (err) {
+    ctx.log.warn(`Statics path ${staticsPath} not found, not serving static assets`)
+    staticsPath = undefined
+  }
+  const app = createExpress(ctx, staticsPath)
 
 
   // Start processes.
   // Start processes.
   // This promise can only be rejected, signifying that the app has stopped
   // This promise can only be rejected, signifying that the app has stopped

+ 7 - 0
src/types.ts

@@ -29,6 +29,13 @@ export interface Config {
    * @see https://www.npmjs.com/package/cors#usage
    * @see https://www.npmjs.com/package/cors#usage
    */
    */
   cors: Parameters<typeof cors>[0]
   cors: Parameters<typeof cors>[0]
+  fs: {
+    /**
+     * Filesystem root directory (AKA location of package.json).
+     * This is detected at startup and not configurable.
+     */
+    root: string
+  }
   http: {
   http: {
     /** HTTP bind host (default: empty) */
     /** HTTP bind host (default: empty) */
     host: string
     host: string