import { ObjectId, } from 'bson'; import { NextFunction, Response, } from 'express'; import { Types, } from 'mongoose'; import { isString, } from './helpers/isString'; import { IApiError, } from './types/IApiError'; import { ApiDocument, ApiModel, } from './types/IApiModel'; import { IApiRequest, } from './types/IApiRequest'; abstract class ApiController { protected model: ApiModel; constructor(model: ApiModel) { this.model = model; } // ERROR Responses public respondServerError(res: Response, error: any): Response { return res.status(500).json({ error }); } public respondNotFound( id: string | number | ObjectId, res: Response, modelName: string, ): Response { const error: IApiError = { id: 'notFound', message: `${ modelName } ${ id } does not exist`, fields: {}, errors: [], }; return res.status(404).json({ error, }); } public respondInvalidId(res: Response): Response { const error: IApiError = { id: 'invalidId', message: 'Invalid id', fields: {}, errors: [], }; return res.status(400).json({ error, }); } public respondModelMissingError(res: Response): Response { const error: IApiError = { id: 'modelMissing', message: 'the model is missing in the request', fields: {}, errors: [], }; return res.status(400).json({ error, }); } public respondDeletionError(res: Response, err: Error): Response { const error: IApiError = { id: 'delete', message: err.message, fields: {}, errors: [], }; return res.status(400).json({ error, }); } public respondValidationError(err: any, res: Response, next: NextFunction): Response | void { if (err.name === 'ValidationError') { const error: IApiError = { id: 'validationError', message: err.message, fields: err.errors, errors: [], }; return res.status(400).json({ error, }); } if (err.code === 11000) { return res.status(400).json({ error: { id: 'duplicate', message: `${ this.model.modelName } already exists`, }, }); } else { return next(err); } } public apiResponse(req: IApiRequest, res: Response, _next: NextFunction): Response { const hasMetaError = req.meta && req.meta.error; let status = 200; if (hasMetaError) { status = 500; } return res.status(status).json({ meta: req.meta, data: req.data }); } public async populateMeta(req: IApiRequest, _res: Response, next: NextFunction): Promise { const qTotal = req.modelQuery?.total || {}; return this.model.countDocuments(qTotal) .then((total: number) => { const offset = req.modelQuery?.offset || 0; const limit = req.modelQuery?.limit || 100; req.meta = { total: total, count: req.data ? req.data.length : 0, offset: isString(offset) ? parseInt(offset, 10) : offset, limit: isString(limit) ? parseInt(limit, 10) : limit, }; return next(); }) .catch((err: unknown) => next(err)); } protected hasModel(model: ApiDocument | null | undefined): model is K { return typeof model !== 'undefined' && model !== null && model.constructor?.name === 'model' && Types.ObjectId.isValid(model._id); } } export default ApiController;