mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
chore: apply prettier on the entire project
This commit is contained in:
+199
-153
@@ -1,60 +1,59 @@
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import multer from 'multer';
|
||||
import { logDebug, logWarn, logError } from './services/auditLog';
|
||||
import { enforceGlobalMfaPolicy } from './middleware/mfaPolicy';
|
||||
import { authenticate, verifyJwtAndLoadUser } from './middleware/auth';
|
||||
import { ADDON_IDS } from './addons';
|
||||
import { db } from './db/database';
|
||||
|
||||
import authRoutes from './routes/auth';
|
||||
import tripsRoutes from './routes/trips';
|
||||
import daysRoutes, { accommodationsRouter as accommodationsRoutes } from './routes/days';
|
||||
import placesRoutes from './routes/places';
|
||||
import assignmentsRoutes from './routes/assignments';
|
||||
import packingRoutes from './routes/packing';
|
||||
import todoRoutes from './routes/todo';
|
||||
import tagsRoutes from './routes/tags';
|
||||
import categoriesRoutes from './routes/categories';
|
||||
import adminRoutes from './routes/admin';
|
||||
import mapsRoutes from './routes/maps';
|
||||
import airportsRoutes from './routes/airports';
|
||||
import filesRoutes from './routes/files';
|
||||
import reservationsRoutes from './routes/reservations';
|
||||
import dayNotesRoutes from './routes/dayNotes';
|
||||
import settingsRoutes from './routes/settings';
|
||||
import budgetRoutes from './routes/budget';
|
||||
import collabRoutes from './routes/collab';
|
||||
import backupRoutes from './routes/backup';
|
||||
import oidcRoutes from './routes/oidc';
|
||||
import { oauthPublicRouter, oauthApiRouter } from './routes/oauth';
|
||||
import vacayRoutes from './routes/vacay';
|
||||
import atlasRoutes from './routes/atlas';
|
||||
import memoriesRoutes from './routes/memories/unified';
|
||||
import photoRoutes from './routes/photos';
|
||||
import notificationRoutes from './routes/notifications';
|
||||
import shareRoutes from './routes/share';
|
||||
import journeyRoutes from './routes/journey';
|
||||
import journeyPublicRoutes from './routes/journeyPublic';
|
||||
import publicConfigRoutes from './routes/publicConfig';
|
||||
import systemNoticesRoutes from './routes/systemNotices';
|
||||
import { mcpHandler } from './mcp';
|
||||
import { trekOAuthProvider, trekClientsStore } from './mcp/oauthProvider';
|
||||
import { Addon } from './types';
|
||||
import { getPhotoProviderConfig } from './services/memories/helpersService';
|
||||
import { ALL_SCOPES } from './mcp/scopes';
|
||||
import { authenticate, verifyJwtAndLoadUser } from './middleware/auth';
|
||||
import { enforceGlobalMfaPolicy } from './middleware/mfaPolicy';
|
||||
import adminRoutes from './routes/admin';
|
||||
import airportsRoutes from './routes/airports';
|
||||
import assignmentsRoutes from './routes/assignments';
|
||||
import atlasRoutes from './routes/atlas';
|
||||
import authRoutes from './routes/auth';
|
||||
import backupRoutes from './routes/backup';
|
||||
import budgetRoutes from './routes/budget';
|
||||
import categoriesRoutes from './routes/categories';
|
||||
import collabRoutes from './routes/collab';
|
||||
import dayNotesRoutes from './routes/dayNotes';
|
||||
import daysRoutes, { accommodationsRouter as accommodationsRoutes } from './routes/days';
|
||||
import filesRoutes from './routes/files';
|
||||
import journeyRoutes from './routes/journey';
|
||||
import journeyPublicRoutes from './routes/journeyPublic';
|
||||
import mapsRoutes from './routes/maps';
|
||||
import memoriesRoutes from './routes/memories/unified';
|
||||
import notificationRoutes from './routes/notifications';
|
||||
import { oauthPublicRouter, oauthApiRouter } from './routes/oauth';
|
||||
import oidcRoutes from './routes/oidc';
|
||||
import packingRoutes from './routes/packing';
|
||||
import photoRoutes from './routes/photos';
|
||||
import placesRoutes from './routes/places';
|
||||
import publicConfigRoutes from './routes/publicConfig';
|
||||
import reservationsRoutes from './routes/reservations';
|
||||
import settingsRoutes from './routes/settings';
|
||||
import shareRoutes from './routes/share';
|
||||
import systemNoticesRoutes from './routes/systemNotices';
|
||||
import tagsRoutes from './routes/tags';
|
||||
import todoRoutes from './routes/todo';
|
||||
import tripsRoutes from './routes/trips';
|
||||
import vacayRoutes from './routes/vacay';
|
||||
import { getCollabFeatures } from './services/adminService';
|
||||
import { isAddonEnabled } from './services/adminService';
|
||||
import { ADDON_IDS } from './addons';
|
||||
import { ALL_SCOPES } from './mcp/scopes';
|
||||
import { mcpAuthMetadataRouter } from '@modelcontextprotocol/sdk/server/auth/router';
|
||||
import { logDebug, logWarn, logError } from './services/auditLog';
|
||||
import { getPhotoProviderConfig } from './services/memories/helpersService';
|
||||
import { getMcpSafeUrl } from './services/notifications';
|
||||
import { Addon } from './types';
|
||||
import { authorizationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/authorize';
|
||||
import { clientRegistrationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/register';
|
||||
import { mcpAuthMetadataRouter } from '@modelcontextprotocol/sdk/server/auth/router';
|
||||
import type { OAuthMetadata } from '@modelcontextprotocol/sdk/shared/auth';
|
||||
import { getMcpSafeUrl } from './services/notifications';
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import multer from 'multer';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export function createApp(): express.Application {
|
||||
const app = express();
|
||||
@@ -65,8 +64,10 @@ export function createApp(): express.Application {
|
||||
}
|
||||
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
||||
? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()).filter(Boolean)
|
||||
: null;
|
||||
? process.env.ALLOWED_ORIGINS.split(',')
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean)
|
||||
: null;
|
||||
|
||||
let corsOrigin: cors.CorsOptions['origin'];
|
||||
if (allowedOrigins) {
|
||||
@@ -102,54 +103,65 @@ export function createApp(): express.Application {
|
||||
// answer OPTIONS without Access-Control-Allow-Origin before the SDK's own cors() runs.
|
||||
// All /.well-known/* paths get open CORS so clients probing openid-configuration or the
|
||||
// RFC 8414 path-suffixed AS metadata form don't get CORS-blocked (they get 404 JSON instead).
|
||||
app.use(
|
||||
(req: Request, _res: Response, next: NextFunction) => {
|
||||
if (
|
||||
req.path.startsWith('/.well-known/') ||
|
||||
req.path === '/oauth/register' ||
|
||||
req.path === '/oauth/authorize' ||
|
||||
req.path === '/oauth/userinfo' ||
|
||||
req.path === '/mcp'
|
||||
) {
|
||||
cors({ origin: '*', credentials: false })(req, _res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
);
|
||||
app.use((req: Request, _res: Response, next: NextFunction) => {
|
||||
if (
|
||||
req.path.startsWith('/.well-known/') ||
|
||||
req.path === '/oauth/register' ||
|
||||
req.path === '/oauth/authorize' ||
|
||||
req.path === '/oauth/userinfo' ||
|
||||
req.path === '/mcp'
|
||||
) {
|
||||
cors({ origin: '*', credentials: false })(req, _res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
app.use(cors({ origin: corsOrigin, credentials: true }));
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'wasm-unsafe-eval'", "'unsafe-eval'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://unpkg.com"],
|
||||
imgSrc: ["'self'", "data:", "blob:", "https:"],
|
||||
connectSrc: [
|
||||
"'self'", "ws:", "wss:",
|
||||
"https://nominatim.openstreetmap.org", "https://overpass-api.de",
|
||||
"https://places.googleapis.com", "https://api.openweathermap.org",
|
||||
"https://en.wikipedia.org", "https://commons.wikimedia.org",
|
||||
"https://*.basemaps.cartocdn.com", "https://*.tile.openstreetmap.org",
|
||||
"https://unpkg.com", "https://open-meteo.com", "https://api.open-meteo.com",
|
||||
"https://geocoding-api.open-meteo.com", "https://api.exchangerate-api.com",
|
||||
"https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson",
|
||||
"https://router.project-osrm.org/route/v1/",
|
||||
"https://api.mapbox.com", "https://*.tiles.mapbox.com", "https://events.mapbox.com"
|
||||
],
|
||||
workerSrc: ["'self'", "blob:"],
|
||||
childSrc: ["'self'", "blob:"],
|
||||
fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"],
|
||||
objectSrc: ["'none'"],
|
||||
frameSrc: ["'none'"],
|
||||
frameAncestors: ["'self'"],
|
||||
upgradeInsecureRequests: shouldForceHttps ? [] : null
|
||||
}
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
hsts: hstsActive ? { maxAge: 31536000, includeSubDomains: hstsIncludeSubdomains } : false,
|
||||
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
||||
}));
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'", "'wasm-unsafe-eval'", "'unsafe-eval'"],
|
||||
styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://unpkg.com'],
|
||||
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
|
||||
connectSrc: [
|
||||
"'self'",
|
||||
'ws:',
|
||||
'wss:',
|
||||
'https://nominatim.openstreetmap.org',
|
||||
'https://overpass-api.de',
|
||||
'https://places.googleapis.com',
|
||||
'https://api.openweathermap.org',
|
||||
'https://en.wikipedia.org',
|
||||
'https://commons.wikimedia.org',
|
||||
'https://*.basemaps.cartocdn.com',
|
||||
'https://*.tile.openstreetmap.org',
|
||||
'https://unpkg.com',
|
||||
'https://open-meteo.com',
|
||||
'https://api.open-meteo.com',
|
||||
'https://geocoding-api.open-meteo.com',
|
||||
'https://api.exchangerate-api.com',
|
||||
'https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_50m_admin_0_countries.geojson',
|
||||
'https://router.project-osrm.org/route/v1/',
|
||||
'https://api.mapbox.com',
|
||||
'https://*.tiles.mapbox.com',
|
||||
'https://events.mapbox.com',
|
||||
],
|
||||
workerSrc: ["'self'", 'blob:'],
|
||||
childSrc: ["'self'", 'blob:'],
|
||||
fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],
|
||||
objectSrc: ["'none'"],
|
||||
frameSrc: ["'none'"],
|
||||
frameAncestors: ["'self'"],
|
||||
upgradeInsecureRequests: shouldForceHttps ? [] : null,
|
||||
},
|
||||
},
|
||||
crossOriginEmbedderPolicy: false,
|
||||
hsts: hstsActive ? { maxAge: 31536000, includeSubDomains: hstsIncludeSubdomains } : false,
|
||||
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
||||
}),
|
||||
);
|
||||
|
||||
if (shouldForceHttps) {
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -166,7 +178,19 @@ export function createApp(): express.Application {
|
||||
|
||||
// Request logging with sensitive field redaction
|
||||
{
|
||||
const SENSITIVE_KEYS = new Set(['password', 'new_password', 'current_password', 'token', 'jwt', 'authorization', 'cookie', 'client_secret', 'mfa_token', 'code', 'smtp_pass']);
|
||||
const SENSITIVE_KEYS = new Set([
|
||||
'password',
|
||||
'new_password',
|
||||
'current_password',
|
||||
'token',
|
||||
'jwt',
|
||||
'authorization',
|
||||
'cookie',
|
||||
'client_secret',
|
||||
'mfa_token',
|
||||
'code',
|
||||
'smtp_pass',
|
||||
]);
|
||||
const redact = (value: unknown): unknown => {
|
||||
if (!value || typeof value !== 'object') return value;
|
||||
if (Array.isArray(value)) return (value as unknown[]).map(redact);
|
||||
@@ -245,12 +269,16 @@ export function createApp(): express.Application {
|
||||
|
||||
// Share-token path: require the token to cover the exact trip the
|
||||
// photo belongs to. Expired tokens fall through to 401.
|
||||
const photo = db.prepare('SELECT trip_id FROM photos WHERE filename = ?').get(safeName) as { trip_id: number } | undefined;
|
||||
const photo = db.prepare('SELECT trip_id FROM photos WHERE filename = ?').get(safeName) as
|
||||
| { trip_id: number }
|
||||
| undefined;
|
||||
if (!photo) return res.status(401).send('Authentication required');
|
||||
|
||||
const share = db.prepare(
|
||||
"SELECT trip_id FROM share_tokens WHERE token = ? AND (expires_at IS NULL OR expires_at > datetime('now'))"
|
||||
).get(rawToken) as { trip_id: number } | undefined;
|
||||
const share = db
|
||||
.prepare(
|
||||
"SELECT trip_id FROM share_tokens WHERE token = ? AND (expires_at IS NULL OR expires_at > datetime('now'))",
|
||||
)
|
||||
.get(rawToken) as { trip_id: number } | undefined;
|
||||
if (!share || share.trip_id !== photo.trip_id) {
|
||||
return res.status(401).send('Authentication required');
|
||||
}
|
||||
@@ -277,8 +305,8 @@ export function createApp(): express.Application {
|
||||
app.use('/api/trips/:tripId/reservations', reservationsRoutes);
|
||||
app.use('/api/trips/:tripId/days/:dayId/notes', dayNotesRoutes);
|
||||
app.get('/api/health', (_req: Request, res: Response) => {
|
||||
res.setHeader('Cache-Control', 'no-store, must-revalidate')
|
||||
res.json({ status: 'ok' })
|
||||
res.setHeader('Cache-Control', 'no-store, must-revalidate');
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
app.use('/api/config', publicConfigRoutes);
|
||||
app.use('/api', assignmentsRoutes);
|
||||
@@ -288,18 +316,28 @@ export function createApp(): express.Application {
|
||||
|
||||
// Addons list endpoint
|
||||
app.get('/api/addons', authenticate, (_req: Request, res: Response) => {
|
||||
const addons = db.prepare('SELECT id, name, type, icon, enabled FROM addons WHERE enabled = 1 ORDER BY sort_order').all() as Pick<Addon, 'id' | 'name' | 'type' | 'icon' | 'enabled'>[];
|
||||
const providers = db.prepare(`
|
||||
const addons = db
|
||||
.prepare('SELECT id, name, type, icon, enabled FROM addons WHERE enabled = 1 ORDER BY sort_order')
|
||||
.all() as Pick<Addon, 'id' | 'name' | 'type' | 'icon' | 'enabled'>[];
|
||||
const providers = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, name, icon, enabled, sort_order
|
||||
FROM photo_providers
|
||||
WHERE enabled = 1
|
||||
ORDER BY sort_order, id
|
||||
`).all() as Array<{ id: string; name: string; icon: string; enabled: number; sort_order: number }>;
|
||||
const fields = db.prepare(`
|
||||
`,
|
||||
)
|
||||
.all() as Array<{ id: string; name: string; icon: string; enabled: number; sort_order: number }>;
|
||||
const fields = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT provider_id, field_key, label, input_type, placeholder, hint, required, secret, settings_key, payload_key, sort_order
|
||||
FROM photo_provider_fields
|
||||
ORDER BY sort_order, id
|
||||
`).all() as Array<{
|
||||
`,
|
||||
)
|
||||
.all() as Array<{
|
||||
provider_id: string;
|
||||
field_key: string;
|
||||
label: string;
|
||||
@@ -323,15 +361,15 @@ export function createApp(): express.Application {
|
||||
res.json({
|
||||
collabFeatures: getCollabFeatures(),
|
||||
addons: [
|
||||
...addons.map(a => ({ ...a, enabled: !!a.enabled })),
|
||||
...providers.map(p => ({
|
||||
...addons.map((a) => ({ ...a, enabled: !!a.enabled })),
|
||||
...providers.map((p) => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
type: 'photo_provider',
|
||||
icon: p.icon,
|
||||
enabled: !!p.enabled,
|
||||
config: getPhotoProviderConfig(p.id),
|
||||
fields: (fieldsByProvider.get(p.id) || []).map(f => ({
|
||||
fields: (fieldsByProvider.get(p.id) || []).map((f) => ({
|
||||
key: f.field_key,
|
||||
label: f.label,
|
||||
input_type: f.input_type,
|
||||
@@ -351,10 +389,14 @@ export function createApp(): express.Application {
|
||||
// Addon routes
|
||||
app.use('/api/addons/vacay', vacayRoutes);
|
||||
app.use('/api/addons/atlas', atlasRoutes);
|
||||
app.use('/api/journeys', (req, res, next) => {
|
||||
if (!isAddonEnabled(ADDON_IDS.JOURNEY)) return res.status(404).json({ error: 'Journey addon is not enabled' });
|
||||
next();
|
||||
}, journeyRoutes);
|
||||
app.use(
|
||||
'/api/journeys',
|
||||
(req, res, next) => {
|
||||
if (!isAddonEnabled(ADDON_IDS.JOURNEY)) return res.status(404).json({ error: 'Journey addon is not enabled' });
|
||||
next();
|
||||
},
|
||||
journeyRoutes,
|
||||
);
|
||||
app.use('/api/public/journey', journeyPublicRoutes);
|
||||
app.use('/api/integrations/memories', memoriesRoutes);
|
||||
app.use('/api/photos', photoRoutes);
|
||||
@@ -391,16 +433,16 @@ export function createApp(): express.Application {
|
||||
if (_oauthMetadata) return _oauthMetadata;
|
||||
const base = getMcpSafeUrl().replace(/\/+$/, '');
|
||||
_oauthMetadata = {
|
||||
issuer: base,
|
||||
authorization_endpoint: `${base}/oauth/authorize`,
|
||||
token_endpoint: `${base}/oauth/token`,
|
||||
revocation_endpoint: `${base}/oauth/revoke`,
|
||||
registration_endpoint: `${base}/oauth/register`,
|
||||
response_types_supported: ['code'],
|
||||
grant_types_supported: ['authorization_code', 'refresh_token', 'client_credentials'],
|
||||
code_challenge_methods_supported: ['S256'],
|
||||
issuer: base,
|
||||
authorization_endpoint: `${base}/oauth/authorize`,
|
||||
token_endpoint: `${base}/oauth/token`,
|
||||
revocation_endpoint: `${base}/oauth/revoke`,
|
||||
registration_endpoint: `${base}/oauth/register`,
|
||||
response_types_supported: ['code'],
|
||||
grant_types_supported: ['authorization_code', 'refresh_token', 'client_credentials'],
|
||||
code_challenge_methods_supported: ['S256'],
|
||||
token_endpoint_auth_methods_supported: ['client_secret_post', 'none'],
|
||||
scopes_supported: ALL_SCOPES,
|
||||
scopes_supported: ALL_SCOPES,
|
||||
};
|
||||
return _oauthMetadata;
|
||||
}
|
||||
@@ -446,11 +488,11 @@ export function createApp(): express.Application {
|
||||
if (!isAddonEnabled(ADDON_IDS.MCP)) return res.status(404).end();
|
||||
const meta = getOAuthMetadata();
|
||||
res.json({
|
||||
resource: `${meta.issuer}/mcp`,
|
||||
authorization_servers: [meta.issuer],
|
||||
resource: `${meta.issuer}/mcp`,
|
||||
authorization_servers: [meta.issuer],
|
||||
bearer_methods_supported: ['header'],
|
||||
scopes_supported: ALL_SCOPES,
|
||||
resource_name: 'TREK MCP',
|
||||
scopes_supported: ALL_SCOPES,
|
||||
resource_name: 'TREK MCP',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -488,13 +530,15 @@ export function createApp(): express.Application {
|
||||
// Production static file serving
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const publicPath = path.join(__dirname, '../public');
|
||||
app.use(express.static(publicPath, {
|
||||
setHeaders: (res, filePath) => {
|
||||
if (filePath.endsWith('index.html')) {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
}
|
||||
},
|
||||
}));
|
||||
app.use(
|
||||
express.static(publicPath, {
|
||||
setHeaders: (res, filePath) => {
|
||||
if (filePath.endsWith('index.html')) {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
app.get('*', (_req: Request, res: Response) => {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.sendFile(path.join(publicPath, 'index.html'));
|
||||
@@ -502,21 +546,23 @@ export function createApp(): express.Application {
|
||||
}
|
||||
|
||||
// Global error handler
|
||||
app.use((err: Error & { status?: number; statusCode?: number }, _req: Request, res: Response, _next: NextFunction) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.error('Unhandled error:', err.message);
|
||||
} else {
|
||||
console.error('Unhandled error:', err);
|
||||
}
|
||||
if (err instanceof multer.MulterError) {
|
||||
const status = err.code === 'LIMIT_FILE_SIZE' ? 413 : 400;
|
||||
return res.status(status).json({ error: err.message });
|
||||
}
|
||||
const status = err.statusCode || err.status || 500;
|
||||
// Expose the message for client errors (4xx); keep 'Internal server error' for 5xx.
|
||||
const message = status < 500 ? err.message : 'Internal server error';
|
||||
res.status(status).json({ error: message });
|
||||
});
|
||||
app.use(
|
||||
(err: Error & { status?: number; statusCode?: number }, _req: Request, res: Response, _next: NextFunction) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.error('Unhandled error:', err.message);
|
||||
} else {
|
||||
console.error('Unhandled error:', err);
|
||||
}
|
||||
if (err instanceof multer.MulterError) {
|
||||
const status = err.code === 'LIMIT_FILE_SIZE' ? 413 : 400;
|
||||
return res.status(status).json({ error: err.message });
|
||||
}
|
||||
const status = err.statusCode || err.status || 500;
|
||||
// Expose the message for client errors (4xx); keep 'Internal server error' for 5xx.
|
||||
const message = status < 500 ? err.message : 'Internal server error';
|
||||
res.status(status).json({ error: message });
|
||||
},
|
||||
);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user