diff --git a/Dockerfile b/Dockerfile index 64c2e63..825c36b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ EXPOSE ${PORT} STOPSIGNAL SIGINT ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] -RUN yarn build +RUN yarn build:nostatic COPY static /app/dist/static HEALTHCHECK --interval=30s --timeout=30s --start-period=5s \ diff --git a/README.md b/README.md index 432a93e..ba0e6bf 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,25 @@ STDOUT. Check the README there for more details and usages. ## Installation 1. Download the package, and expand it -2. Explore the settings inside of config.js, but the defaults should be good -3. `npm install` -4. `npm start` (you may specify an optional `` as well) +3. `yarn install` + +## Development + +1. Explore the settings inside of config.js, but the defaults should be good +2. `yarn install` +3. `yarn dev` (you may specify an optional `` as well) + +## Production + +1. Explore the settings inside of config.js, but the defaults should be good +2. `yarn install` +3. `yarn build` to build the package +4. `yarn start` to start the server + +## Production with Docker + +1. Explore the settings inside of config.js, but the defaults should be good +2. `docker compose up` ## Settings diff --git a/package.json b/package.json index b363f45..19ea8e0 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,8 @@ "copy:files": "copyFiles -u 1 static/**/* dist/static", "clean:files": "rimraf dist", "test": "jest --config config/jest.config.js", - "build": "yarn clean:files && tsc --project ./", + "build:nostatic": "yarn clean:files && tsc --project ./", + "build": "yarn clean:files && yarn copy:files && tsc --project ./", "dev": "nodemon src/server.ts", "start": "node dist/src/server.js", "lint": "eslint src --fix", diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index e40ec11..0000000 --- a/src/app.ts +++ /dev/null @@ -1,175 +0,0 @@ -import express, { Router, Express, Request } from 'express' -import * as fs from 'fs' -import * as winston from 'winston' -import uglify from 'uglify-js' -import connectSt from 'st' -import connectRateLimit from 'connect-ratelimit' -import getConfig from './lib/helpers/config' -import addLogging from './lib/helpers/log' -import build from './lib/document-handler/builder' -import DocumentHandler from './lib/document-handler' -import { Config } from './types/config' -import { - getStaticDirectory, - getStaticItemDirectory, -} from './lib/helpers/directory' - -class App { - public server: Express - - public config: Config - - documentHandler?: DocumentHandler - - constructor() { - this.config = getConfig() - this.server = express() - this.setLogging() - this.setDocumentHandler() - this.compressStaticAssets() - this.sendDocumentsToStore() - this.middlewares() - this.setRateLimits() - this.apiCalls() - this.staticPages() - } - - middlewares() { - this.server.use(express.json()) - } - - setLogging() { - if (this.config.logging) { - addLogging(this.config) - } - } - - setDocumentHandler = async () => { - this.documentHandler = await build(this.config) - } - - apiCalls() { - const router = Router() - - // get raw documents - support getting with extension - router.get('/raw/:id', async (request, response) => - this.documentHandler?.handleRawGet(request, response), - ) - - router.head('/raw/:id', (request, response) => - this.documentHandler?.handleRawGet(request, response), - ) - - // // add documents - router.post('/documents', (request, response) => - this.documentHandler?.handlePost(request, response), - ) - - // get documents - router.get('/documents/:id', (request, response) => - this.documentHandler?.handleGet(request, response), - ) - - router.head('/documents/:id', (request, response) => - this.documentHandler?.handleGet(request, response), - ) - - this.server.use(router) - } - - setRateLimits() { - if (this.config.rateLimits) { - this.config.rateLimits.end = true - this.server.use(connectRateLimit(this.config.rateLimits)) - } - } - - compressStaticAssets() { - // Compress the static javascript assets - if (this.config.recompressStaticAssets) { - const list = fs.readdirSync(getStaticDirectory(__dirname)) - for (let j = 0; j < list.length; j += 1) { - const item = list[j] - if ( - item.indexOf('.js') === item.length - 3 && - item.indexOf('.min.js') === -1 - ) { - const dest = `${item.substring( - 0, - item.length - 3, - )}.min${item.substring(item.length - 3)}` - const origCode = fs.readFileSync( - getStaticItemDirectory(__dirname, item), - 'utf8', - ) - - fs.writeFileSync( - getStaticItemDirectory(__dirname, dest), - uglify.minify(origCode).code, - 'utf8', - ) - winston.info(`compressed ${item} into ${dest}`) - } - } - } - } - - sendDocumentsToStore() { - // Send the static documents into the preferred store, skipping expirations - let documentPath - let data - - Object.keys(this.config.documents).forEach(name => { - documentPath = this.config.documents[name] - data = fs.readFileSync(documentPath, 'utf8') - winston.info('loading static document', { name, path: documentPath }) - - if (data) { - this.documentHandler?.store?.set( - name, - data, - cb => { - winston.debug('loaded static document', { success: cb }) - }, - true, - ) - } else { - winston.warn('failed to load static document', { - name, - path: documentPath, - }) - } - }) - } - - staticPages() { - - // Otherwise, try to match static files - this.server.use( - connectSt({ - path: getStaticDirectory(__dirname), - content: { maxAge: this.config.staticMaxAge }, - passthrough: true, - index: false, - }), - ) - - // Then we can loop back - and everything else should be a token, - // so route it back to / - this.server.get('/:id', (request: Request, response, next) => { - request.sturl = '/' - next() - }) - - // And match index - this.server.use( - connectSt({ - path: getStaticDirectory(__dirname), - content: { maxAge: this.config.staticMaxAge }, - index: 'index.html', - }), - ) - } -} - -export default App diff --git a/src/server.ts b/src/server.ts index a882feb..2347147 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,8 +1,142 @@ +import express, { Express, Request } from 'express' +import * as fs from 'fs' import * as winston from 'winston' -import App from './app' - -const { server, config } = new App() +import uglify from 'uglify-js' +import connectSt from 'st' +import connectRateLimit from 'connect-ratelimit' +import getConfig from './lib/helpers/config' +import addLogging from './lib/helpers/log' +import buildDocumenthandler from './lib/document-handler/builder' +import DocumentHandler from './lib/document-handler' +import { Config } from './types/config' +import { + getStaticDirectory, + getStaticItemDirectory, +} from './lib/helpers/directory' -server.listen(config.port, config.host, () => { +const config: Config = getConfig() + +if (config.logging) { + addLogging(config) +} + +let documentHandler: DocumentHandler + +buildDocumenthandler(config).then((handler) => { + documentHandler = handler +}) + +// Compress the static javascript assets +if (config.recompressStaticAssets) { + + const list = fs.readdirSync(getStaticDirectory(__dirname)) + for (let j = 0; j < list.length; j += 1) { + const item = list[j] + if ( + item.indexOf('.js') === item.length - 3 && + item.indexOf('.min.js') === -1 + ) { + const dest = `${item.substring(0, item.length - 3)}.min${item.substring( + item.length - 3, + )}` + const origCode = fs.readFileSync( + getStaticItemDirectory(__dirname, item), + 'utf8', + ) + + fs.writeFileSync( + getStaticItemDirectory(__dirname, dest), + uglify.minify(origCode).code, + 'utf8', + ) + winston.info(`compressed ${item} into ${dest}`) + } + } +} + +// Send the static documents into the preferred store, skipping expirations +let documentPath +let data + +Object.keys(config.documents).forEach(name => { + documentPath = config.documents[name] + data = fs.readFileSync(documentPath, 'utf8') + winston.info('loading static document', { name, path: documentPath }) + + if (data) { + documentHandler?.store?.set( + name, + data, + cb => { + winston.debug('loaded static document', { success: cb }) + }, + true, + ) + } else { + winston.warn('failed to load static document', { + name, + path: documentPath, + }) + } +}) + +const app: Express = express() + +// Rate limit all requests +if (config.rateLimits) { + config.rateLimits.end = true + app.use(connectRateLimit(config.rateLimits)) +} + +// get raw documents - support getting with extension +app.get('/raw/:id', async (request, response) => + documentHandler?.handleRawGet(request, response), +) + +app.head('/raw/:id', (request, response) => + documentHandler?.handleRawGet(request, response), +) + +// // add documents +app.post('/documents', (request, response) => + documentHandler?.handlePost(request, response), +) + +// get documents +app.get('/documents/:id', (request, response) => + documentHandler?.handleGet(request, response), +) + +app.head('/documents/:id', (request, response) => + documentHandler?.handleGet(request, response), +) + +// Otherwise, try to match static files +app.use( + connectSt({ + path: getStaticDirectory(__dirname), + content: { maxAge: config.staticMaxAge }, + passthrough: true, + index: false, + }), +) + +// Then we can loop back - and everything else should be a token, +// so route it back to / +app.get('/:id', (request: Request, response, next) => { + request.sturl = '/' + next() +}) + +// And match index +app.use( + connectSt({ + path: getStaticDirectory(__dirname), + content: { maxAge: config.staticMaxAge }, + index: 'index.html', + }), +) + +app.listen(config.port, config.host, () => { winston.info(`listening on ${config.host}:${config.port}`) })