diff --git a/package-lock.json b/package-lock.json index fc8eed2..cfca00d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,18 +43,102 @@ "@types/babel-types": "*" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz", + "integrity": "sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.3.tgz", + "integrity": "sha512-sHEsvEzjqN+zLbqP+8OXTipc10yH1QLR+hnr5uw29gi9AhCAAAdri8ClNV7iMdrJrIzXIQtlkPvq8tJGhj3QJQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/node": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.11.0.tgz", + "integrity": "sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==", + "dev": true + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "Base64": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", diff --git a/package.json b/package.json index a6f54f0..9a16fe1 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,11 @@ "sqlite3": "^4.1.1" }, "devDependencies": { + "@types/compression": "^1.7.0", + "@types/express": "^4.17.4", + "@types/graceful-fs": "^4.1.3", + "@types/lodash": "^4.14.149", + "@types/node": "^13.11.0", "bower": "^1.8.8", "escape-string-regexp": "^2.0.0", "grunt": "^1.0.4", diff --git a/server/@types/http-auth/index.d.ts b/server/@types/http-auth/index.d.ts new file mode 100644 index 0000000..4ef7555 --- /dev/null +++ b/server/@types/http-auth/index.d.ts @@ -0,0 +1,15 @@ +declare module "http-auth" { + import {RequestHandler} from "express" + + class Auth {} + + class BasicAuth extends Auth {} + class BasicAuthOptions {} + + type BasicAuthChecker = + (username: string, password: string, callback: BasicAuthCheckerCallback) => void + type BasicAuthCheckerCallback = (result: boolean | Error, customUser?: string) => void + + function basic(options: BasicAuthOptions, checker: BasicAuthChecker): BasicAuth + function connect(auth: Auth): RequestHandler +} diff --git a/server/app.js b/server/app.js deleted file mode 100644 index f15c4ef..0000000 --- a/server/app.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -const _ = require('lodash') -const auth = require('http-auth'); -const bodyParser = require('body-parser'); -const compress = require('compression'); -const express = require('express'); -const fs = require('graceful-fs') - -const config = require('./config').config - -const app = express(); - -module.exports = (() => { - const router = express.Router(); - - // urls beneath /internal are protected - const internalAuth = auth.basic( - { - realm: 'Knotenformular - Intern' - }, - function (username, password, callback) { - callback( - config.server.internal.active && - username === config.server.internal.user && - password === config.server.internal.password - ); - } - ); - router.use('/internal', auth.connect(internalAuth)); - - router.use(bodyParser.json()); - router.use(bodyParser.urlencoded({ extended: true })); - - const adminDir = __dirname + '/../admin'; - const clientDir = __dirname + '/../client'; - const templateDir = __dirname + '/templates'; - - const jsTemplateFiles = [ - '/config.js' - ]; - - router.use(compress()); - - function serveTemplate (mimeType, req, res, next) { - return fs.readFile(templateDir + '/' + req.path + '.template', 'utf8', function (err, body) { - if (err) { - return next(err); - } - - res.writeHead(200, { 'Content-Type': mimeType }); - res.end(_.template(body)({ config: config.client })); - - return null; // to suppress warning - }); - } - - router.use(function (req, res, next) { - if (jsTemplateFiles.indexOf(req.path) >= 0) { - return serveTemplate('application/javascript', req, res, next); - } - return next(); - }); - - router.use('/internal/admin', express.static(adminDir + '/')); - router.use('/', express.static(clientDir + '/')); - - app.use(config.server.rootPath, router); - - return app; -})() diff --git a/server/app.ts b/server/app.ts new file mode 100644 index 0000000..a616909 --- /dev/null +++ b/server/app.ts @@ -0,0 +1,67 @@ +import _ from "lodash" +import auth, {BasicAuthCheckerCallback} from "http-auth" +import bodyParser from "body-parser" +import compress from "compression" +import express, {Express, NextFunction, Request, Response} from "express" +import fs from "graceful-fs" + +const config = require('./config').config + +const app: Express = express(); + +const router = express.Router(); + +// urls beneath /internal are protected +const internalAuth = auth.basic( + { + realm: 'Knotenformular - Intern' + }, + function (username: string, password: string, callback: BasicAuthCheckerCallback): void { + callback( + config.server.internal.active && + username === config.server.internal.user && + password === config.server.internal.password + ); + } +); +router.use('/internal', auth.connect(internalAuth)); + +router.use(bodyParser.json()); +router.use(bodyParser.urlencoded({ extended: true })); + +const adminDir = __dirname + '/../admin'; +const clientDir = __dirname + '/../client'; +const templateDir = __dirname + '/templates'; + +const jsTemplateFiles = [ + '/config.js' +]; + +router.use(compress()); + +function serveTemplate (mimeType: string, req: Request, res: Response, next: NextFunction): void { + return fs.readFile(templateDir + '/' + req.path + '.template', 'utf8', function (err, body) { + if (err) { + return next(err); + } + + res.writeHead(200, { 'Content-Type': mimeType }); + res.end(_.template(body)({ config: config.client })); + + return null; // to suppress warning + }); +} + +router.use(function (req: Request, res: Response, next: NextFunction): void { + if (jsTemplateFiles.indexOf(req.path) >= 0) { + return serveTemplate('application/javascript', req, res, next); + } + return next(); +}); + +router.use('/internal/admin', express.static(adminDir + '/')); +router.use('/', express.static(clientDir + '/')); + +app.use(config.server.rootPath, router); + +export default app; diff --git a/server/init.js b/server/init.js new file mode 100644 index 0000000..0bd2b91 --- /dev/null +++ b/server/init.js @@ -0,0 +1,8 @@ +'use strict'; + +(function () { + // Use graceful-fs instead of fs also in all libraries to have more robust fs handling. + const realFs = require('fs'); + const gracefulFs = require('graceful-fs'); + gracefulFs.gracefulify(realFs); +})(); diff --git a/server/logger.js b/server/logger.js index 7d6de63..fa5c020 100644 --- a/server/logger.js +++ b/server/logger.js @@ -1,6 +1,6 @@ 'use strict'; -const app = require('./app'); +const app = require('./app').default; const config = require('./config').config; // Hack to allow proper logging of Error. diff --git a/server/main.js b/server/main.js deleted file mode 100755 index 3e0263b..0000000 --- a/server/main.js +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env node -/*jslint node: true */ -'use strict'; - -(function () { - // Use graceful-fs instead of fs also in all libraries to have more robust fs handling. - const realFs = require('fs'); - const gracefulFs = require('graceful-fs'); - gracefulFs.gracefulify(realFs); -})(); - -const config = require('./config').config; - -const Logger = require('./logger') -Logger.tag('main', 'startup').info('Server starting up...'); - -require('./db/database').init() -.then(() => { - Logger.tag('main').info('Initializing...'); - - const app = require('./app'); - - require('./jobs/scheduler').init(); - require('./router').init(); - - app.listen(config.server.port, '::'); - module.exports = app; -}) -.catch(error => { - console.error('Could not init database: ', error); - process.exit(1); -}); diff --git a/server/main.ts b/server/main.ts new file mode 100755 index 0000000..2485ff4 --- /dev/null +++ b/server/main.ts @@ -0,0 +1,23 @@ +import "./init" +import { config } from "./config" +import Logger from "./logger" +import db from "./db/database" +import scheduler from "./jobs/scheduler" +import { init as initRouter } from "./router" +import app from "./app" + +Logger.tag('main', 'startup').info('Server starting up...'); + +db.init() +.then(() => { + Logger.tag('main').info('Initializing...'); + + scheduler.init(); + initRouter(); + + app.listen(config.server.port, '::'); +}) +.catch(error => { + console.error('Could not init database: ', error); + process.exit(1); +}); diff --git a/server/router.js b/server/router.js deleted file mode 100644 index 32ebade..0000000 --- a/server/router.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -const express = require('express'); - -const app = require('./app') -const config = require('./config').config -const VersionResource = require('./resources/versionResource') -const StatisticsResource = require('./resources/statisticsResource') -const FrontendResource = require('./resources/frontendResource') -const NodeResource = require('./resources/nodeResource') -const MonitoringResource = require('./resources/monitoringResource') -const TaskResource = require('./resources/taskResource') -const MailResource = require('./resources/mailResource') - -module.exports = { - init () { - const router = express.Router(); - - router.post('/', FrontendResource.render); - - router.get('/api/version', VersionResource.get); - - router.post('/api/node', NodeResource.create); - router.put('/api/node/:token', NodeResource.update); - router.delete('/api/node/:token', NodeResource.delete); - router.get('/api/node/:token', NodeResource.get); - - router.put('/api/monitoring/confirm/:token', MonitoringResource.confirm); - router.put('/api/monitoring/disable/:token', MonitoringResource.disable); - - router.get('/internal/api/statistics', StatisticsResource.get); - - router.get('/internal/api/tasks', TaskResource.getAll); - router.put('/internal/api/tasks/run/:id', TaskResource.run); - router.put('/internal/api/tasks/enable/:id', TaskResource.enable); - router.put('/internal/api/tasks/disable/:id', TaskResource.disable); - - router.get('/internal/api/monitoring', MonitoringResource.getAll); - - router.get('/internal/api/mails', MailResource.getAll); - router.get('/internal/api/mails/:id', MailResource.get); - router.delete('/internal/api/mails/:id', MailResource.delete); - router.put('/internal/api/mails/reset/:id', MailResource.resetFailures); - - router.put('/internal/api/nodes/:token', NodeResource.update); - router.delete('/internal/api/nodes/:token', NodeResource.delete); - router.get('/internal/api/nodes', NodeResource.getAll); - router.get('/internal/api/nodes/:token', NodeResource.get); - - app.use(config.server.rootPath, router); - } -} diff --git a/server/router.ts b/server/router.ts new file mode 100644 index 0000000..b799630 --- /dev/null +++ b/server/router.ts @@ -0,0 +1,49 @@ +import express from "express" + +import app from "./app" +import {config} from "./config" + +import VersionResource from "./resources/versionResource" +import StatisticsResource from "./resources/statisticsResource" +import FrontendResource from "./resources/frontendResource" +import NodeResource from "./resources/nodeResource" +import MonitoringResource from "./resources/monitoringResource" +import TaskResource from "./resources/taskResource" +import MailResource from "./resources/mailResource" + +export function init (): void { + const router = express.Router(); + + router.post('/', FrontendResource.render); + + router.get('/api/version', VersionResource.get); + + router.post('/api/node', NodeResource.create); + router.put('/api/node/:token', NodeResource.update); + router.delete('/api/node/:token', NodeResource.delete); + router.get('/api/node/:token', NodeResource.get); + + router.put('/api/monitoring/confirm/:token', MonitoringResource.confirm); + router.put('/api/monitoring/disable/:token', MonitoringResource.disable); + + router.get('/internal/api/statistics', StatisticsResource.get); + + router.get('/internal/api/tasks', TaskResource.getAll); + router.put('/internal/api/tasks/run/:id', TaskResource.run); + router.put('/internal/api/tasks/enable/:id', TaskResource.enable); + router.put('/internal/api/tasks/disable/:id', TaskResource.disable); + + router.get('/internal/api/monitoring', MonitoringResource.getAll); + + router.get('/internal/api/mails', MailResource.getAll); + router.get('/internal/api/mails/:id', MailResource.get); + router.delete('/internal/api/mails/:id', MailResource.delete); + router.put('/internal/api/mails/reset/:id', MailResource.resetFailures); + + router.put('/internal/api/nodes/:token', NodeResource.update); + router.delete('/internal/api/nodes/:token', NodeResource.delete); + router.get('/internal/api/nodes', NodeResource.getAll); + router.get('/internal/api/nodes/:token', NodeResource.get); + + app.use(config.server.rootPath, router); +} diff --git a/server/tsconfig.json b/server/tsconfig.json index a46576d..a20dad5 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -43,7 +43,10 @@ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ + "typeRoots": [ /* List of folders to include type definitions from. */ + "../node_modules/@types", + "@types" + ], // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */