Moved backend to a separate directory
This commit is contained in:
46
backend/.eslintrc.js
Normal file
46
backend/.eslintrc.js
Normal 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
81
backend/src/app.ts
Normal 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;
|
||||
});
|
||||
|
||||
|
||||
21
backend/src/healthcheck.ts
Normal file
21
backend/src/healthcheck.ts
Normal 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
42
backend/src/index.ts
Normal 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);
|
||||
}
|
||||
11
backend/src/routes/api/user.ts
Normal file
11
backend/src/routes/api/user.ts
Normal 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;
|
||||
10
backend/src/routes/healthcheck.ts
Normal file
10
backend/src/routes/healthcheck.ts
Normal 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;
|
||||
19
backend/src/routes/index.ts
Normal file
19
backend/src/routes/index.ts
Normal 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
23
backend/src/types/extend-request.d.ts
vendored
Normal 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 {};
|
||||
18
backend/test/routes/healthcheck.ts
Normal file
18
backend/test/routes/healthcheck.ts
Normal 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
18
backend/tsconfig.json
Normal 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
6
backend/views/error.pug
Normal file
@@ -0,0 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
7
backend/views/index.pug
Normal file
7
backend/views/index.pug
Normal file
@@ -0,0 +1,7 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= title
|
||||
p Welcome to #{title}!
|
||||
a(href="/logout") Want so say goodbye?
|
||||
p This name is fetched on server-side: #{email}
|
||||
17
backend/views/layout.pug
Normal file
17
backend/views/layout.pug
Normal 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')
|
||||
Reference in New Issue
Block a user