Moved backend to a separate directory

This commit is contained in:
Sebastian Seedorf
2020-11-24 00:39:20 +01:00
parent f7d790818a
commit ef3af370fd
16 changed files with 9 additions and 17 deletions

46
backend/.eslintrc.js Normal file
View File

@@ -0,0 +1,46 @@
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
'no-null',
'promise',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:promise/recommended',
],
rules: {
'no-console': 'error',
'max-len': ['error', {'code': 128}],
'no-process-env': 'error',
'no-process-exit': 'error',
'no-null/no-null': 'error',
'no-useless-return': 'error',
'prefer-arrow-callback': 'warn',
'consistent-return': 'error',
'@typescript-eslint/explicit-function-return-type': [
'error', {
'allowExpressions': true,
},
],
'no-void': 'error',
'comma-spacing': 'error',
'comma-dangle': ['error', 'always-multiline'],
'comma-style': 'error',
'semi': 'error',
'no-implicit-coercion': 'error',
'quotes': ['error', 'single', 'avoid-escape'],
'keyword-spacing': 'error',
'semi-spacing': 'error',
'arrow-spacing': 'error',
'object-curly-spacing': 'error',
'array-bracket-spacing': 'error',
'key-spacing': 'error',
'block-spacing': 'error',
'promise/always-return': 'off',
},
};

81
backend/src/app.ts Normal file
View File

@@ -0,0 +1,81 @@
import * as createError from 'http-errors';
import * as express from 'express';
import {NextFunction, Request, Response} from 'express';
import * as path from 'path';
import * as sassMiddleware from 'node-sass-middleware';
import * as compression from 'compression';
import indexRouter from './routes';
import {AuthProxy, AutoReloader, DefaultConfig, HttpLogger, Polyfill, PermManager} from 'pkg-express-utils';
export const app = express();
// Permissions
PermManager
.grant('user')
.execute('read').on('userinfo')
.grant('coe_bs')
.extend('user')
.execute('write').on('userinfo');
// view engine setup
app.set('views', path.join(__dirname, '../views'));
app.set('view engine', 'pug');
app.use((req, res, next) => {
res.locals.DefaultConfig = DefaultConfig;
next();
});
// http logger
app.use(HttpLogger);
app.use(express.json());
app.use(express.urlencoded({extended: false}));
// compression
app.use(compression());
const router = express.Router();
// auth proxy middleware
router.use(AuthProxy.router);
// auto reloader (when running in debug mode)
router.use(AutoReloader.getRouter({
frontendDirs: './public',
}));
// session
//router.use(Session.getRouter());
// static config
router.use('/js/polyfill.js', Polyfill.getRouter('./public/js/bundle.js'));
router.use(sassMiddleware({
src: path.join(__dirname, '../../public'),
dest: path.join(__dirname, '../../public'),
indentedSyntax: true, // true = .sass and false = .scss
sourceMap: true,
}));
router.use(express.static(path.join(__dirname, '../../public')));
router.use(indexRouter);
app.use(DefaultConfig.BASE_PATH, router);
// catch 404 and forward to error handler
app.use((req, res, next) => {
next(createError(404));
});
// error handler
app.use((err: Error&{status?: number}, req: Request, res: Response, next: NextFunction) => {
if (res.headersSent) {
return next(err);
}
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = !DefaultConfig.isProduction ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
return undefined;
});

View File

@@ -0,0 +1,21 @@
/* eslint-disable no-process-exit,no-console */
import * as http from 'http';
import {DefaultConfig, urlJoin} from 'pkg-express-utils';
const options = {
host: DefaultConfig.HOSTNAME,
port: DefaultConfig.PORT,
path: urlJoin(DefaultConfig.BASE_PATH, '/health'),
timeout: 2000,
};
const healthCheck = http.request(options, (res) => {
process.exit(res.statusCode == 200 ? 0 : 1);
});
healthCheck.on('error', (err) => {
console.error('HEALTHCHECK ERROR', err);
process.exit(1);
});
healthCheck.end();

42
backend/src/index.ts Normal file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env node
/* eslint-disable no-process-exit */
import * as http from 'http';
import {app} from './app';
import {HttpError} from 'http-errors';
import {DefaultConfig, Logger} from 'pkg-express-utils';
app.set('port', DefaultConfig.PORT);
const server = http.createServer(app);
server.on('error', onError);
server.on('listening', onListening);
server.listen(DefaultConfig.PORT);
function onError(error: HttpError): void {
if (error.syscall !== 'listen') {
Logger.error(error);
}
switch (error.code) {
case 'EACCES':
Logger.error(DefaultConfig.PORT + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
Logger.error(DefaultConfig.PORT + ' is already in use');
process.exit(2);
break;
default:
Logger.error(error);
process.exit(3);
}
}
function onListening(): void {
const addr = server.address();
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + (addr ?? {port: undefined}).port;
Logger.debug('Listening on ' + bind);
}

View File

@@ -0,0 +1,11 @@
import * as express from 'express';
import {PermManager} from 'pkg-express-utils';
const userRouter = express.Router();
/* GET user info. */
userRouter.get('/', PermManager.getRouter('userinfo', {action: 'read'}), async (req, res) => {
res.json(await req.getUserInfo() || {});
});
export default userRouter;

View File

@@ -0,0 +1,10 @@
import * as express from 'express';
const healthRouter = express.Router();
healthRouter.get('/', async (req, res) => {
req.noHttpLogging = true;
res.sendStatus(200);
});
export default healthRouter;

View File

@@ -0,0 +1,19 @@
import * as express from 'express';
import userRouter from './api/user';
import healthRouter from './healthcheck';
const router = express.Router();
export default router;
router.use('/api/user', userRouter);
router.use('/health', healthRouter);
/* GET home page. */
router.get('/', async (req, res) => {
const email = (await req.getUserInfo())?.email ?? 'No email found!';
res.render('index', {title: 'Express', email});
});
router.get('/logout', (req, res) => {
res.initLogout();
});

23
backend/src/types/extend-request.d.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
// extend-request.d.ts
declare global {
namespace Express {
interface Request {
// fetch user info (needs AuthProxy and some headers)
getUserInfo(): Promise<import('pkg-express-utils').UserInfo|undefined>;
// fetch user info (needed for HttpLogger)
noHttpLogging: boolean|undefined;
// permission details (after a Permission route)
permissionDetails: import('role-acl').Permission;
// switch session to another user;does not close old session (after Session router)
setSessionById(sessionId: string): Promise<boolean>;
}
interface Response {
// initialize a logout (needs AuthProxy and some headers)
initLogout(): boolean;
}
}
}
export {};

View File

@@ -0,0 +1,18 @@
import healthRouter from '../../src/routes/healthcheck';
import * as request from 'supertest';
import * as express from 'express';
describe('backend:routes/healthcheck', () => {
it('should return 200', (done) => {
const app = express();
app.use(healthRouter);
request(app)
.get("/")
.set('Accept', 'application/json')
.expect(200)
.end((err, res) => {
if (err) throw err;
done();
});
});
});

18
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"sourceMap": true,
"outDir": "./out",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"strict": true
},
"include": [
"./src/**/*.ts"
],
"exclude": [
"../node_modules",
"./public"
]
}

6
backend/views/error.pug Normal file
View File

@@ -0,0 +1,6 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

7
backend/views/index.pug Normal file
View File

@@ -0,0 +1,7 @@
extends layout
block content
h1= title
p Welcome to #{title}!&nbsp;
a(href="/logout") Want so say goodbye?
p This name is fetched on server-side: #{email}

17
backend/views/layout.pug Normal file
View File

@@ -0,0 +1,17 @@
- const baseUrl = DefaultConfig.EXTERNAL_BASE_URL;
doctype html
html
head
title= title
link(rel='stylesheet', href=baseUrl+'/styles/style.css')
body
block content
script(type='text/javascript', src=baseUrl+'/js/polyfill.js')
script(type='text/javascript', src=baseUrl+'/js/require-2.3.6.min.js')
script(type='text/javascript', src=baseUrl+'/js/bundle.js')
script.
require(['src/index'], function (index) {
index.setConfig({EXTERNAL_BASE_URL: '#{baseUrl}'});
});
if !DefaultConfig.isProduction
script(type='text/javascript', src=baseUrl+'/auto-reload/client.js')