feat: add backend

This commit is contained in:
Anselm Stordeur
2019-01-08 12:26:15 +01:00
committed by Rainer Killinger
commit 16bbb7e9e3
66 changed files with 6939 additions and 0 deletions

159
src/routes/Route.ts Normal file
View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
SCInternalServerErrorResponse,
SCMethodNotAllowedErrorResponse,
SCRoute,
SCValidationErrorResponse,
} from '@openstapps/core';
import { SCValidator } from '@openstapps/core-validator';
import { Application, Router } from 'express';
import PromiseRouter from 'express-promise-router';
import { logger } from '../common';
import { isHttpMethod } from './HTTPTypes';
/**
* Creates a router from a route class (model of a route) and a handler function which implements the logic
*
* The given router performs a request and respone validation, sets status codes and checks if the given handler
* only returns errors that are allowed for the client to see
*
* @param routeClass
* @param handler
*/
export function createRoute<RETURNTYPE>(
routeClass: SCRoute,
handler: (validatedBody: any, app: Application, params?: { [parameterName: string]: string }) => Promise<RETURNTYPE>,
): Router {
// create router
const router = PromiseRouter({ mergeParams: true });
// create route
// the given type has no index signature so we have to cast to get the IRouteHandler when a HTTP method is given
const route = router.route(routeClass.urlFragment);
// get route parameters (path parameters)
if (Array.isArray(routeClass.obligatoryParameters) && routeClass.obligatoryParameters.length > 0) {
routeClass.obligatoryParameters.forEach((parameterName) => {
router.param(parameterName, async (_req, _res, next, _parameterValue: string) => {
// if (typeof req.params === 'undefined') {
// req.params = {};
// }
// set parameter values on request object
// req.params[parameterName] = parameterValue;
// hand over the request to the next handler (our method route handler)
next();
});
});
}
const verb = routeClass.method.toLowerCase();
// check if route has a valid http verb
if (isHttpMethod(verb)) {
// create a route handler for the given HTTP method
route[verb](async (req, res) => {
try {
// get the core validator from the app
const validator: SCValidator = req.app.get('validator');
// validate request
const requestValidation = validator.validate(req.body, routeClass.requestBodyName.substring(2));
if (requestValidation.errors.length > 0) {
const error = new SCValidationErrorResponse(
requestValidation.errors,
req.app.get('isProductiveEnvironment'),
);
res.status(error.statusCode);
res.json(error);
logger.warn(error);
return;
}
const params: { [parameterName: string]: string } = {};
if (Array.isArray(routeClass.obligatoryParameters) && routeClass.obligatoryParameters) {
// copy over parameter values from request object
// the parameter values were set in the parameter handler of the route
routeClass.obligatoryParameters.forEach((parameterName) => {
params[parameterName] = req.params[parameterName];
});
}
// hand over request to handler with path parameters
const response = await handler(req.body, req.app, params);
// validate response generated by handler
const responseValidation = validator.validate(response, routeClass.responseBodyName.substring(2));
if (responseValidation.errors.length > 0) {
const validationError = new SCValidationErrorResponse(
responseValidation.errors,
req.app.get('isProductiveEnvironment'),
);
const internalServerError = new SCInternalServerErrorResponse(
validationError,
req.app.get('isProductiveEnvironment'),
);
res.status(internalServerError.statusCode);
res.json(internalServerError);
logger.warn(internalServerError);
return;
}
// set status code
res.status(routeClass.statusCodeSuccess);
// respond
res.json(response);
} catch (error) {
// if the error response is allowed on the route
if (routeClass.errorNames.indexOf(error.constructor.name) > -1) {
// respond with the error from the handler
res.status(error.statusCode);
res.json(error);
logger.warn(error);
} else {
// the error is not allowed so something went wrong
const internalServerError = new SCInternalServerErrorResponse(
error,
req.app.get('isProductiveEnvironment'),
);
res.status(internalServerError.statusCode);
res.json(internalServerError);
logger.error(error);
}
}
});
} else {
throw new Error('Invalid HTTP verb in route definition. Please check route definitions in `@openstapps/core`');
}
// return a SCMethodNotAllowedErrorResponse on all other HTTP methods
route.all((req, res) => {
const error = new SCMethodNotAllowedErrorResponse(req.app.get('isProductiveEnvironment'));
res.status(error.statusCode);
res.json(error);
logger.warn(error);
});
// return router
return router;
}