- Sprint 1 (Quick Wins): Remove dead analytics modules, fix 4 unresolved imports, replace 3 console.warn with logger, remove mock-crc import - Sprint 2 (Architecture): Create MascotChatRepository, AnalysisRepository, 3 Zod schemas (mascot-chat, analysis, voice), deduplicate error classes, move 3 SQL queries from routes to repository - Sprint 3 (Complexity): Replace 7 any types with proper interfaces, extract 6 helpers from prepareMediaMessage (CC 85 -> ~15) - Sprint 4 (Config): Remove 22 dead env vars from .env, add 30 missing vars to .env.example, standardize naming Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import {
|
|
AppError,
|
|
UnauthorizedError,
|
|
ValidationError,
|
|
} from "@bete/shared/errors";
|
|
import { createChildLogger } from "@bete/shared/logger";
|
|
import type { NextFunction, Request, Response } from "express";
|
|
|
|
const logger = createChildLogger("middleware");
|
|
|
|
export function errorHandler(
|
|
err: Error,
|
|
_req: Request,
|
|
res: Response,
|
|
_next: NextFunction,
|
|
) {
|
|
if (err instanceof AppError) {
|
|
logger.warn({ code: err.code, statusCode: err.statusCode }, err.message);
|
|
return res.status(err.statusCode).json({
|
|
error: err.code,
|
|
message: err.message,
|
|
...(err instanceof ValidationError && { details: err.details }),
|
|
});
|
|
}
|
|
|
|
logger.error({ err }, "Unhandled error");
|
|
res.status(500).json({
|
|
error: "INTERNAL_SERVER_ERROR",
|
|
message: "An unexpected error occurred",
|
|
});
|
|
}
|
|
|
|
export function adminAuth(adminPassword: string) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
const password = req.headers["x-admin-password"] as string;
|
|
|
|
if (!password || password !== adminPassword) {
|
|
throw new UnauthorizedError("Invalid admin password");
|
|
}
|
|
|
|
next();
|
|
};
|
|
}
|
|
|
|
export function asyncHandler(
|
|
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
|
|
) {
|
|
return (req: Request, res: Response, next: NextFunction) => {
|
|
Promise.resolve(fn(req, res, next)).catch(next);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Validate that a value is a non-empty string, or throw a descriptive error.
|
|
* Use for both route params and query string values.
|
|
*/
|
|
export function requireParam(
|
|
value: unknown,
|
|
kind: string,
|
|
name: string,
|
|
): string {
|
|
if (typeof value !== "string" || value.length === 0) {
|
|
throw new Error(`Missing ${kind}: ${name}`);
|
|
}
|
|
return value;
|
|
}
|