All files / redis-smq-api/src/lib routing.ts

100% Statements 37/37
81.81% Branches 9/11
100% Functions 8/8
100% Lines 37/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 14157x   57x 57x             57x     57x 57x 57x 57x     57x 57x 57x 57x                                 57x                                                                     1584x     57x             990x 990x     56x 56x 56x 36x 36x   20x 20x   56x       990x     57x           627x 1584x 594x             990x 990x         57x       33x 33x 33x             33x    
import * as Router from '@koa/router';
import { ClassConstructor } from 'class-transformer';
import { RequestValidator } from '../middlewares/request-validator';
import { ResponseValidator } from '../middlewares/response-validator';
import {
  IContext,
  IContextState,
  IResponseBody,
  TApplication,
} from '../types/common';
import { posix } from 'path';
import { Next, ParameterizedContext } from 'koa';
 
export enum ERouteControllerActionPayload {
  QUERY = 'query',
  BODY = 'body',
  PATH = 'path',
}
 
export enum ERouteControllerActionMethod {
  GET = 'get',
  POST = 'post',
  DELETE = 'delete',
}
 
export type TRequestContext<
  RequestDTO,
  ResponseDTO extends TResponseDTO,
> = ParameterizedContext<
  IContextState<RequestDTO>,
  IContext,
  ResponseDTO['body']
>;
 
export type TResponseDTO<Body = any> = {
  status: number;
  body: IResponseBody<Body> | void;
};
 
export class CRequestDTO {}
 
export type TRouteControllerActionHandler<
  RequestDTO extends CRequestDTO,
  ResponseDTO extends TResponseDTO,
> = (
  app: TApplication,
) => (
  ctx: TRequestContext<RequestDTO, ResponseDTO>,
) => Promise<
  ResponseDTO['body'] extends Record<any, any>
    ? ResponseDTO['body']['data']
    : void
>;
 
export type TRouteControllerAction<
  RequestDTO extends CRequestDTO,
  ResponseDTO extends TResponseDTO,
> = {
  path: string;
  method: ERouteControllerActionMethod;
  payload: ERouteControllerActionPayload[];
  Handler: TRouteControllerActionHandler<RequestDTO, ResponseDTO>;
  RequestDTO: ClassConstructor<any>;
  ResponseDTO: ClassConstructor<any>;
};
 
export interface IRouteController {
  path: string;
  actions: (IRouteController | TRouteControllerAction<any, any>)[];
}
 
function isRouteController(
  object: IRouteController | TRouteControllerAction<any, any>,
): object is IRouteController {
  return object.hasOwnProperty('actions');
}
 
export function getControllerActionRouter<
  RequestDTO extends CRequestDTO,
  ResponseDTO extends TResponseDTO,
>(
  app: TApplication,
  action: TRouteControllerAction<RequestDTO, ResponseDTO>,
): Router {
  const router = new Router();
  router[action.method](
    action.path,
    RequestValidator(action.RequestDTO, action.payload),
    async (ctx: TRequestContext<RequestDTO, ResponseDTO>, next: Next) => {
      const data = await action.Handler(app)(ctx);
      if (data !== undefined) {
        ctx.status = 200;
        ctx.body = { data };
      } else {
        ctx.status = 204;
        ctx.body = undefined;
      }
      await next();
    },
    ResponseValidator(action.ResponseDTO),
  );
  return router;
}
 
export function registerControllerRoutes(
  app: TApplication,
  controller: IRouteController,
  mainRouter: Router,
  path = '/',
): void {
  for (const item of controller.actions) {
    if (isRouteController(item)) {
      registerControllerRoutes(
        app,
        item,
        mainRouter,
        item.path === '/' ? path : posix.join(path, item.path),
      );
    } else {
      const router = getControllerActionRouter(app, item);
      mainRouter.use(path, router.routes());
    }
  }
}
 
export function getApplicationRouter(
  app: TApplication,
  controllers: IRouteController[],
): Router {
  const applicationRouter = new Router();
  for (const controller of controllers) {
    registerControllerRoutes(
      app,
      controller,
      applicationRouter,
      controller.path,
    );
  }
  return applicationRouter;
}