mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
feat: add backend
This commit is contained in:
159
src/routes/Route.ts
Normal file
159
src/routes/Route.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user