import { logger } from "@common/infrastructure/logger"; import { NextFunction, Request, Response } from "express"; import httpStatus from "http-status"; import { ApiError } from "./api-error"; export abstract class ExpressController { protected req!: Request; protected res!: Response; protected next!: NextFunction; protected abstract executeImpl(): Promise; protected ok(dto?: T) { return dto ? this.res.status(httpStatus.OK).json(dto) : this.res.status(httpStatus.OK).send(); } protected created(dto?: T) { return dto ? this.res.status(httpStatus.CREATED).json(dto) : this.res.status(httpStatus.CREATED).send(); } protected noContent() { return this.res.status(httpStatus.NO_CONTENT).send(); } protected errorResponse(apiError: ApiError) { logger.error(`[${apiError.status}] ${apiError.title}: ${apiError.detail}`); return this.res.status(apiError.status).json(apiError); } /** * 🔹 Respuesta para errores de cliente (400 Bad Request) */ protected clientError(message: string, errors?: any[]) { return this.errorResponse( new ApiError({ status: 400, title: "Bad Request", detail: message, errors, }) ); } /** * 🔹 Respuesta para errores de autenticación (401 Unauthorized) */ protected unauthorizedError(message?: string) { return this.errorResponse( new ApiError({ status: 401, title: "Unauthorized", detail: message ?? "You are not authorized to access this resource.", }) ); } /** * 🔹 Respuesta para errores de autorización (403 Forbidden) */ protected forbiddenError(message?: string) { return this.errorResponse( new ApiError({ status: 403, title: "Forbidden", detail: message ?? "You do not have permission to perform this action.", }) ); } /** * 🔹 Respuesta para recursos no encontrados (404 Not Found) */ protected notFoundError(message: string) { return this.errorResponse( new ApiError({ status: 404, title: "Not Found", detail: message, }) ); } /** * 🔹 Respuesta para conflictos (409 Conflict) */ protected conflictError(message: string, errors?: any[]) { return this.errorResponse( new ApiError({ status: 409, title: "Conflict", detail: message, errors, }) ); } /** * 🔹 Respuesta para errores de validación de entrada (422 Unprocessable Entity) */ protected invalidInputError(message: string, errors?: any[]) { return this.errorResponse( new ApiError({ status: 422, title: "Invalid Input", detail: message, errors, }) ); } /** * Respuesta para errores de servidor no disponible (503 Service Unavailable) * @param message * @returns */ protected unavailableError(message?: string) { return this.errorResponse( new ApiError({ status: 503, title: "Service Unavailable", detail: message ?? "The service is currently unavailable. Please try again later.", }) ); } /** * 🔹 Respuesta para errores internos del servidor (500 Internal Server Error) */ protected internalServerError(message?: string) { return this.errorResponse( new ApiError({ status: 500, title: "Internal Server Error", detail: message ?? "An unexpected error occurred. Please try again later.", }) ); } public execute(req: Request, res: Response, next: NextFunction): void { this.req = req; this.res = res; this.next = next; try { this.executeImpl(); } catch (error: unknown) { const _error = error as Error; logger.error(`Unhandled error in controller: ${_error.message}`); this.internalServerError(_error.message); } } }