UNPKG

@mieweb/wikigdrive

Version:

Google Drive to MarkDown synchronization

332 lines (331 loc) 11 kB
import process from 'node:process'; import { instrumentAndWrap } from '../../../telemetry.js'; export const HttpStatus = { OK: 200, CREATED: 201, NO_CONTENT: 204 }; function getMethods(obj) { const res = {}; for (const m of Object.getOwnPropertyNames(obj.constructor.prototype)) { res[m] = obj[m]; } return res; } export class ErrorHandler { constructor() { Object.defineProperty(this, "req", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "res", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "subPath", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: void 0 }); } async catch(err) { throw err; } } export class ControllerCallContext { constructor(route, subPath, req, res, logger) { Object.defineProperty(this, "route", { enumerable: true, configurable: true, writable: true, value: route }); Object.defineProperty(this, "subPath", { enumerable: true, configurable: true, writable: true, value: subPath }); Object.defineProperty(this, "req", { enumerable: true, configurable: true, writable: true, value: req }); Object.defineProperty(this, "res", { enumerable: true, configurable: true, writable: true, value: res }); Object.defineProperty(this, "logger", { enumerable: true, configurable: true, writable: true, value: logger }); } async routeParamMethod() { return this.req.method.toLowerCase(); } async routeParamPath(name) { return this.req.params[name]; } async routeParamBody() { return this.req.body; } async routeParamQuery(name) { return this.req.query[name]; } async routeParamUser() { return this.req.user; } } function addSwaggerRoute(mainPath, route) { // SwaggerDocService.addRoute(mainPath, route); } export class Controller { constructor(subPath) { Object.defineProperty(this, "subPath", { enumerable: true, configurable: true, writable: true, value: subPath }); } getRoute(instance, methodFunc) { const classType = instance.constructor.prototype; if (!classType.controllerId) { classType.controllerId = 'controller_' + Controller.counter; Controller.counter++; } const key = classType.controllerId + '.' + methodFunc; if (!Controller.routes[key]) { Controller.routes[key] = { hidden: false, roles: [], errorHandlers: [], methodFunc, responseObjectType: 'object', responseStatus: HttpStatus.OK, responseContentType: 'application/json; charset=utf-8' }; } return Controller.routes[key]; } async getRouter() { const controllerId = this.constructor.prototype.controllerId; const { Router } = await import('express'); const router = Router(); for (const key in Controller.routes) { if (!key.startsWith(controllerId + '.')) { continue; } const route = Controller.routes[key]; if (!route.hidden) { addSwaggerRoute(this.subPath, route); } const handlers = []; if (route.roles.length > 0) { handlers.push((req, res, next) => { if (req.user && route.roles.indexOf(req.user.global_role) > -1) { next(); } else { const logger = req['logger']; logger.error('User does not have any of those roles: ' + JSON.stringify(route.roles) + ', only: ' + req.user.global_role); // throw Boom.forbidden(req.t('auth.youNeedToBeAuthorized')); } }); } handlers.push(async (req, res, next) => { try { const methods = getMethods(this.constructor.prototype); const bound = this[route.methodFunc].bind({ ...methods, ...this, }); res.header('Content-type', route.responseContentType); const args = []; const logger = req['logger']; const ctx = new ControllerCallContext(route, this.subPath, req, res, logger); args.push(ctx); let retVal; if (process.env.ZIPKIN_URL) { const spanName = req.originalUrl + '.' + route.methodFunc; await instrumentAndWrap(spanName, req, res, async () => { retVal = await bound(...args); }); } else { retVal = await bound(...args); } if ('stream' === route.responseObjectType) { return; } if ('void' === route.responseObjectType) { res.status(HttpStatus.NO_CONTENT).send(); return; } if ('html' === route.responseObjectType) { res.status(route.responseStatus).send(retVal); return; } res.status(route.responseStatus).json(retVal); } catch (err) { let err1 = err; for (const inputFilter of route.errorHandlers) { try { const boundErrorHandler = inputFilter.catch.bind({ ...this, ...inputFilter, subPath: this.subPath, req, res }); await boundErrorHandler(err); } catch (subErr) { err1 = subErr; } } next(err1); } }); switch (route.method) { case 'GET': router.get(route.routePath, ...handlers); break; case 'POST': router.post(route.routePath, ...handlers); break; case 'PUT': router.put(route.routePath, ...handlers); break; case 'DELETE': router.delete(route.routePath, ...handlers); break; case 'USE': router.use(route.routePath, ...handlers); break; } } return router; } } Object.defineProperty(Controller, "routes", { enumerable: true, configurable: true, writable: true, value: {} }); Object.defineProperty(Controller, "counter", { enumerable: true, configurable: true, writable: true, value: 1 }); export function RouteUse(routePath, docs = {}) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.routePath = routePath; route.method = 'USE'; route.routeDocs = docs; }); }; } export function RouteGet(routePath, docs = {}) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.routePath = routePath; route.method = 'GET'; route.routeDocs = docs; }); }; } export function RoutePut(routePath, docs = {}) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.routePath = routePath; route.method = 'PUT'; route.routeDocs = docs; }); }; } export function RoutePost(routePath, docs = {}) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.routePath = routePath; route.method = 'POST'; route.routeDocs = docs; }); }; } export function RouteDelete(routePath, docs = {}) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.routePath = routePath; route.method = 'DELETE'; route.routeDocs = docs; }); }; } export function RouteDocsHidden() { return function (controller, methodProp) { const route = controller.getRoute(controller, methodProp); route.hidden = true; }; } export function RouteHasRole(roles) { return function (controller, methodProp) { const route = controller.getRoute(controller, methodProp); route.roles = roles; }; } export function RouteResponse(objType = 'object', docs = {}, contentType = 'application/json; charset=utf-8') { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.responseObjectType = objType; route.responseContentType = contentType; route.responseDocs = docs; }); }; } // TODO: remove export function RouteErrorHandler(errorHandler) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.errorHandlers.push(errorHandler); }); }; } export function RouteResponseStatus(status = HttpStatus.OK) { return function (_func, ctx) { ctx.addInitializer(function () { const route = this.getRoute(this, ctx.name); route.responseStatus = status; }); }; }