All files / redis-smq-monitor-app/src/common routing.ts

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

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 141 142 143 144 145 146 147 148 149 150 151 152 15357x   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 { posix } from 'path';
import * as Koa from 'koa';
 
export enum ERouteControllerActionPayload {
  QUERY = 'query',
  BODY = 'body',
  PATH = 'path',
}
 
export enum ERouteControllerActionMethod {
  GET = 'get',
  POST = 'post',
  DELETE = 'delete',
}
 
export interface IResponseBodyError {
  code: number;
  message: string;
  details?: Record<string, any>;
}
 
export interface IResponseBody<Data = Record<string, any>> {
  data?: Data;
  error?: IResponseBodyError;
}
 
export interface IContextState<DTO> extends Koa.DefaultState {
  dto: DTO;
}
 
export type TMiddleware<DTO = Record<string, any>> = Koa.Middleware<
  IContextState<DTO>,
  Koa.DefaultContext & { params: Record<string, string> },
  IResponseBody
>;
 
export type TRequestContext<
  RequestDTO,
  ResponseDTO extends TResponseDTO,
> = Koa.ParameterizedContext<
  IContextState<RequestDTO>,
  Koa.DefaultContext,
  ResponseDTO['body']
>;
 
export type TResponseDTO<Body = any> = {
  status: number;
  body: IResponseBody<Body> | void;
};
 
export type TRequestDTO = Record<string, any>;
 
export type TRouteControllerActionHandler<
  RequestDTO extends TRequestDTO,
  ResponseDTO extends TResponseDTO,
> = (
  app: Koa,
) => (
  ctx: TRequestContext<RequestDTO, ResponseDTO>,
) => Promise<
  ResponseDTO['body'] extends Record<any, any>
    ? ResponseDTO['body']['data']
    : void
>;
 
export type TRouteControllerAction<
  RequestDTO extends TRequestDTO,
  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 TRequestDTO,
  ResponseDTO extends TResponseDTO,
>(app: Koa, 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: Koa.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: Koa,
  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: Koa,
  controllers: IRouteController[],
): Router {
  const applicationRouter = new Router();
  for (const controller of controllers) {
    registerControllerRoutes(
      app,
      controller,
      applicationRouter,
      controller.path,
    );
  }
  return applicationRouter;
}