import { Response } from 'express'; import { AuthenticationError, PdsUnavailableError, } from '../../core/AuthenticationError'; export abstract class Controller { protected abstract executeImpl(req: any, res: Response): Promise; public async execute(req: any, res: Response): Promise { try { await this.executeImpl(req, res); } catch (err) { console.log(`[Controller]: Uncaught controller error`); console.log(err); this.fail(res, 'An unexpected error occurred'); } } public static jsonResponse(res: Response, code: number, message: string) { return res.status(code).json({ message }); } public ok(res: Response, dto?: T) { if (dto) { return res.status(200).json(dto); } else { return res.sendStatus(200); } } public created(res: Response, dto?: T) { if (dto) { return res.status(201).json(dto); } else { return res.sendStatus(201); } } public badRequest(res: Response, message?: string) { return Controller.jsonResponse(res, 400, message || 'Bad request'); } public unauthorized(res: Response, message?: string, code?: string) { if (code) { return res.status(401).json({ message: message || 'Unauthorized', code }); } return Controller.jsonResponse(res, 401, message || 'Unauthorized'); } public forbidden(res: Response, message?: string) { return Controller.jsonResponse(res, 403, message || 'Forbidden'); } public notFound(res: Response, message?: string, code?: string) { if (code) { return res.status(404).json({ message: message || 'Not found', code }); } return Controller.jsonResponse(res, 404, message || 'Not found'); } public conflict(res: Response, message?: string) { return Controller.jsonResponse(res, 409, message || 'Conflict'); } public tooMany(res: Response, message?: string) { return Controller.jsonResponse(res, 429, message || 'Too many requests'); } public fail(res: Response, error: Error | string) { console.log(error); return res.status(500).json({ message: error.toString(), }); } protected handleError(res: Response, error: Error): Response { // Must precede the AuthenticationError check (it is a subclass). A // transient PDS failure: keep the user signed in and let them retry. if (error instanceof PdsUnavailableError) { return res .status(503) .json({ message: error.message, code: 'PDS_UNAVAILABLE' }); } if (error instanceof AuthenticationError) { // AuthenticationError originates from PDS agent authentication (the // server-side ATProto OAuth session), not from the app's own JWT. // The distinct code lets the frontend prompt re-authentication // instead of treating this as a generic session expiry. return this.unauthorized(res, error.message, 'PDS_SESSION_EXPIRED'); } return this.fail(res, error); } }