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

View File

@@ -0,0 +1,46 @@
/*
* 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 { SCBulkAddRequest, SCBulkAddResponse, SCBulkAddRoute, SCNotFoundErrorResponse } from '@openstapps/core';
import { logger } from '../common';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const bulkRouteModel = new SCBulkAddRoute();
/**
* Implementation of the bulk add route (SCBulkAddRoute)
*/
export const bulkAddRouter = createRoute<SCBulkAddResponse>(
bulkRouteModel,
async (request: SCBulkAddRequest, app, params) => {
if (!params || typeof params.UID !== 'string') {
throw new Error('UID of Bulk was not given, but route with obligatory parameter was called');
}
const bulkMemory: BulkStorage = app.get('bulk');
const bulk = await bulkMemory.read(params.UID);
if (typeof bulk === 'undefined') {
logger.warn(`Bulk with ${params.UID} not found.`);
throw new SCNotFoundErrorResponse(app.get('isProductiveEnvironment'));
}
await bulkMemory.database.post(request, bulk);
return {};
},
);

View File

@@ -0,0 +1,46 @@
/*
* 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 { SCBulkDoneRequest, SCBulkDoneResponse, SCBulkDoneRoute, SCNotFoundErrorResponse } from '@openstapps/core';
import { logger } from '../common';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const bulkDoneRouteModel = new SCBulkDoneRoute();
/**
* Implementation of the bulk done request route (SCBulkDoneRoute)
*/
export const bulkDoneRouter = createRoute<SCBulkDoneResponse>(
bulkDoneRouteModel,
async (_request: SCBulkDoneRequest, app, params) => {
if (!params || typeof params.UID !== 'string') {
throw new Error('UID of Bulk was not given, but route with obligatory parameter was called');
}
const bulkMemory: BulkStorage = app.get('bulk');
const bulk = await bulkMemory.read(params.UID);
if (typeof bulk === 'undefined') {
logger.warn(`Bulk with ${params.UID} not found.`);
throw new SCNotFoundErrorResponse(app.get('isProductiveEnvironment'));
}
bulk.state = 'done';
await bulkMemory.markAsDone(bulk);
return {};
},
);

31
src/routes/BulkRoute.ts Normal file
View File

@@ -0,0 +1,31 @@
/*
* 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 { SCBulkRequest, SCBulkResponse, SCBulkRoute } from '@openstapps/core';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const bulkRouteModel = new SCBulkRoute();
/**
* Implementation of the bulk request route (SCBulkRoute)
*/
export const bulkRouter = createRoute<SCBulkResponse>(
bulkRouteModel,
async (request: SCBulkRequest, app) => {
const bulkMemory: BulkStorage = app.get('bulk');
return await bulkMemory.create(request);
},
);

43
src/routes/HTTPTypes.ts Normal file
View File

@@ -0,0 +1,43 @@
/*
* 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/>.
*/
export type HTTPVerb = 'all' |
'get' |
'post' |
'put' |
'delete' |
'patch' |
'options' |
'head' |
'checkout' |
'copy' |
'lock' |
'merge' |
'mkactivity' |
'mkcol' |
'move' |
'm-search' |
'notify' |
'purge' |
'report' |
'search' |
'subscribe' |
'trace' |
'unlock' |
'unsubscribe';
export function isHttpMethod(method: string): method is HTTPVerb {
return ['get', 'post', 'put'].indexOf(method) > -1;
}

32
src/routes/IndexRoute.ts Normal file
View File

@@ -0,0 +1,32 @@
/*
* 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 { SCConfigFile, SCIndexResponse, SCIndexRoute } from '@openstapps/core';
import * as config from 'config';
import { createRoute } from './Route';
const indexRouteModel = new SCIndexRoute();
/**
* Implementation of the index route (SCIndexRoute)
*/
export const indexRouter = createRoute<SCIndexResponse>(
indexRouteModel,
async (_request: SCIndexRoute, _app) => {
const configObject: SCConfigFile = config.util.toObject();
delete configObject.internal;
return configObject;
},
);

View File

@@ -0,0 +1,56 @@
/*
* 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 {
SCMultiSearchRequest,
SCMultiSearchResponse,
SCMultiSearchRoute,
SCSearchResponse,
SCTooManyRequestsErrorResponse,
} from '@openstapps/core';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const multiSearchRouteModel = new SCMultiSearchRoute();
/**
* Implementation of the multi search route (SCMultiSearchRoute)
*/
export const multiSearchRouter = createRoute<SCMultiSearchResponse | SCTooManyRequestsErrorResponse>(
multiSearchRouteModel,
async (request: SCMultiSearchRequest, app) => {
const bulkMemory: BulkStorage = app.get('bulk');
const queryNames = Object.keys(request);
if (queryNames.length > 5) {
return new SCTooManyRequestsErrorResponse(app.get('isProductiveEnvironment'));
}
// get a map of promises for each query
const searchRequests = queryNames.map((queryName) => {
return bulkMemory.database.search(request[queryName]);
});
const listOfSearchResponses = await Promise.all(searchRequests);
const response: { [queryName: string]: SCSearchResponse } = {};
queryNames.forEach((queryName, index) => {
response[queryName] = listOfSearchResponses[index];
});
return response;
},
);

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;
}

28
src/routes/SearchRoute.ts Normal file
View File

@@ -0,0 +1,28 @@
/*
* 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 { SCSearchRequest, SCSearchResponse, SCSearchRoute } from '@openstapps/core';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const searchRouteModel = new SCSearchRoute();
/**
* Implementation of the search route (SCSearchRoute)
*/
export const searchRouter = createRoute<SCSearchResponse>(searchRouteModel, async ( request: SCSearchRequest, app) => {
const bulkMemory: BulkStorage = app.get('bulk');
return await bulkMemory.database.search(request);
});

View File

@@ -0,0 +1,32 @@
/*
* 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 { SCThingUpdateRequest, SCThingUpdateResponse, SCThingUpdateRoute } from '@openstapps/core';
import { BulkStorage } from '../storage/BulkStorage';
import { createRoute } from './Route';
const thingUpdateRouteModel = new SCThingUpdateRoute();
/**
* Implementation of the thing update route (SCThingUpdateRoute)
*/
export const thingUpdateRouter = createRoute<SCThingUpdateResponse>(
thingUpdateRouteModel,
async (request: SCThingUpdateRequest, app) => {
const bulkMemory: BulkStorage = app.get('bulk');
await bulkMemory.database.put(request);
return {};
},
);