controller.class.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import { HTTPStatus } from 'deps';
  2. import { isSuccessfulStatus } from 'deps';
  3. import { Logger, logger as defaultLogger } from 'infra/logger.ts';
  4. export interface ControllerOptions {
  5. status?: HTTPStatus;
  6. json?: boolean;
  7. }
  8. export type ControllerHandler = (
  9. req: Request,
  10. error?: string,
  11. headers?: Record<string, string>,
  12. ) => Response | Promise<Response>;
  13. export type ControllerHandlers = Record<string, ControllerHandler>;
  14. export default class Controller {
  15. constructor(
  16. private handlers: ControllerHandlers = {},
  17. private logger: Logger = defaultLogger,
  18. ) {}
  19. public setHandlers(handlers: ControllerHandlers): void {
  20. this.handlers = handlers;
  21. }
  22. public setHandler(
  23. path: string,
  24. method: string,
  25. handler: ControllerHandler,
  26. ): void {
  27. this.handlers[`${path}_${method}`] = handler;
  28. }
  29. public getHandlers(): ControllerHandlers {
  30. return this.handlers;
  31. }
  32. public getHandler(path: string, method: string): ControllerHandler {
  33. return this.handlers[`${path}_${method}`];
  34. }
  35. public hasHandler(path: string, method: string): boolean {
  36. return `${path}_${method}` in this.handlers;
  37. }
  38. public response = (
  39. req: Request,
  40. body: string,
  41. options: ControllerOptions = {},
  42. ) => {
  43. const url = new URL(req.url);
  44. const output = new TextEncoder().encode(body);
  45. const response = new Response(output, {
  46. status: options.status ?? HTTPStatus.OK,
  47. });
  48. const userName = Controller.getUser(req);
  49. if (options.json) {
  50. response.headers.set('Content-Type', 'application/json');
  51. }
  52. const userAgent = req.headers.get('User-Agent') ?? '-';
  53. const logMessage = Controller.getLogMessage(
  54. req.method,
  55. url.pathname,
  56. response.status,
  57. output.byteLength,
  58. userAgent,
  59. userName,
  60. );
  61. if (isSuccessfulStatus(response.status)) {
  62. if (Controller.isHealthCheck(userAgent)) {
  63. this.logger.debug(logMessage);
  64. } else {
  65. this.logger.info(logMessage);
  66. }
  67. } else {
  68. this.logger.error(logMessage);
  69. }
  70. return response;
  71. };
  72. public responseJSON = (
  73. req: Request,
  74. body: Record<string, unknown>,
  75. options: ControllerOptions = {},
  76. ) => {
  77. options.json = true;
  78. return this.response(req, JSON.stringify(body), options);
  79. };
  80. private static getUser = (req: Request): string | undefined => {
  81. const authHeader = req.headers.get('Authorization');
  82. if (authHeader && authHeader.startsWith('Basic ')) {
  83. const base64Credentials = authHeader.slice(6);
  84. const credentials = atob(base64Credentials);
  85. const [userName] = credentials.split(':');
  86. return userName;
  87. }
  88. return;
  89. };
  90. private static getLogMessage(
  91. method: string,
  92. path: string,
  93. status: number,
  94. length: number,
  95. userAgent: string,
  96. userName?: string,
  97. ) {
  98. const user = userName ? userName + ' ' : '';
  99. return `${method} ${path} ${user}${status} ${length} (${userAgent})`;
  100. }
  101. private static isHealthCheck(userAgent: string): boolean {
  102. return userAgent === Deno.env.get('DOCKER_USER_AGENT');
  103. }
  104. }