mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-08 22:42:54 +00:00
149 lines
5.3 KiB
TypeScript
149 lines
5.3 KiB
TypeScript
/*
|
|
* 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 {Application, Router} from 'express';
|
|
import PromiseRouter from 'express-promise-router';
|
|
import {ValidationError} from 'jsonschema';
|
|
import {isTestEnvironment, logger, validator} 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 {
|
|
// validate request
|
|
const requestValidation = validator.validate(req.body, routeClass.requestBodyName);
|
|
|
|
if (requestValidation.errors.length > 0) {
|
|
const error = new SCValidationErrorResponse(
|
|
requestValidation.errors,
|
|
isTestEnvironment,
|
|
);
|
|
res.status(error.statusCode);
|
|
res.json(error);
|
|
logger.warn(error);
|
|
return;
|
|
}
|
|
|
|
// hand over request to handler with path parameters
|
|
const response = await handler(req.body, req.app, req.params);
|
|
|
|
// validate response generated by handler
|
|
const responseErrors: ValidationError[] = validator.validate(response, routeClass.responseBodyName).errors;
|
|
|
|
if (responseErrors.length > 0) {
|
|
const validationError = new SCValidationErrorResponse(
|
|
responseErrors,
|
|
isTestEnvironment,
|
|
);
|
|
// The validation error is not caused by faulty user input, but through an error that originates somewhere in
|
|
// the backend, therefor we use this "stacked" error.
|
|
const internalServerError = new SCInternalServerErrorResponse(
|
|
validationError,
|
|
isTestEnvironment,
|
|
);
|
|
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.some((constructorType) => error instanceof constructorType)) {
|
|
// 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,
|
|
isTestEnvironment,
|
|
);
|
|
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(isTestEnvironment);
|
|
res.status(error.statusCode);
|
|
res.json(error);
|
|
logger.warn(error);
|
|
});
|
|
|
|
// return router
|
|
return router;
|
|
}
|