This repository has been archived on 2026-05-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Nest/nest-backend/src/app.ts

138 lines
4.5 KiB
TypeScript

import express from 'express';
import cors from 'cors';
import authRouter from './routes/auth.js';
import usersRouter from './routes/users.js';
import forumRouter from './routes/forum.js';
import bugsRouter from './routes/bugs.js';
import feedRouter from './routes/feed.js';
import eventsRouter from './routes/events.js';
import teamRouter from './routes/team.js';
import settingsRouter from './routes/settings.js';
const app = express();
// ── Logger ─────────────────────────────────────────────────────────────────────
const R = '\x1b[0m';
const BOLD = '\x1b[1m';
const DIM = '\x1b[2m';
const RED = '\x1b[31m';
const GREEN = '\x1b[32m';
const YELLOW = '\x1b[33m';
const BLUE = '\x1b[34m';
const MAGENTA = '\x1b[35m';
const CYAN = '\x1b[36m';
const WHITE = '\x1b[37m';
const BG_RED = '\x1b[41m';
const BG_GREEN = '\x1b[42m';
const BG_YELLOW = '\x1b[43m';
const BG_BLUE = '\x1b[44m';
const BG_MAGENTA = '\x1b[45m';
const BG_CYAN = '\x1b[46m';
const METHOD_STYLE: Record<string, string> = {
GET: `${BG_BLUE}${WHITE}${BOLD}`,
POST: `${BG_GREEN}${WHITE}${BOLD}`,
PUT: `${BG_YELLOW}${WHITE}${BOLD}`,
PATCH: `${BG_MAGENTA}${WHITE}${BOLD}`,
DELETE: `${BG_RED}${WHITE}${BOLD}`,
};
function methodBadge(method: string): string {
const style = METHOD_STYLE[method] ?? `${BG_CYAN}${WHITE}${BOLD}`;
return `${style} ${method.padEnd(6)} ${R}`;
}
function statusBadge(code: number): string {
if (code < 300) return `${BG_GREEN}${WHITE}${BOLD} ${code} ${R}`;
if (code < 400) return `${BG_YELLOW}${WHITE}${BOLD} ${code} ${R}`;
return `${BG_RED}${WHITE}${BOLD} ${code} ${R}`;
}
function prettyJson(value: unknown): string {
return JSON.stringify(value, (k, v) => k === 'password' ? '***' : v, 2)
.split('\n')
.map((line, i) => i === 0 ? line : ` ${DIM} ${line}${R}`)
.join('\n');
}
const SEP = `${DIM}${'─'.repeat(60)}${R}`;
app.use((req, res, next) => {
const start = Date.now();
const ts = new Date().toISOString().replace('T', ' ').slice(0, 23);
// Skip health check noise
if (req.originalUrl === '/api/health') { next(); return; }
const originalJson = res.json.bind(res);
let resBody: unknown;
res.json = (body) => { resBody = body; return originalJson(body); };
res.on('finish', () => {
const ms = Date.now() - start;
const userId = req.user?.userId ? `${CYAN}${req.user.userId.slice(0, 8)}${R}` : `${DIM}anon${R}`;
const role = req.user?.role ? `${MAGENTA}${req.user.role}${R}` : `${DIM}-${R}`;
const hasBody = ['POST', 'PUT', 'PATCH'].includes(req.method)
&& req.body && Object.keys(req.body).length > 0;
const lines: string[] = [
SEP,
`${DIM}${ts}${R} ${methodBadge(req.method)} ${BOLD}${req.originalUrl}${R}`,
` ${DIM}┌ user ${R} ${userId} ${DIM}role:${R} ${role}`,
` ${DIM}└ status ${R} ${statusBadge(res.statusCode)} ${DIM}${ms}ms${R}`,
];
if (hasBody) {
lines.push(` ${GREEN}↑ REQUEST BODY${R}`);
lines.push(` ${DIM} ${prettyJson(req.body)}${R}`);
}
if (res.statusCode >= 400 && resBody) {
lines.push(` ${RED}↓ ERROR RESPONSE${R}`);
lines.push(` ${DIM} ${prettyJson(resBody)}${R}`);
} else if (res.statusCode < 300 && resBody && req.method !== 'GET') {
lines.push(` ${GREEN}↓ RESPONSE BODY${R}`);
lines.push(` ${DIM} ${prettyJson(resBody)}${R}`);
}
console.log(lines.join('\n'));
});
next();
});
app.use(cors({
origin: [
'http://localhost:5173', // nest-front dev
'http://localhost:5174', // nest-intra dev
process.env.FRONT_ORIGIN ?? '',
process.env.INTRA_ORIGIN ?? '',
].filter(Boolean),
credentials: true,
}));
app.use(express.json());
app.get('/api/health', (_req, res) => res.json({ ok: true }));
app.use('/api/auth', authRouter);
app.use('/api/users', usersRouter);
app.use('/api/forum', forumRouter);
app.use('/api/bugs', bugsRouter);
app.use('/api/feed', feedRouter);
app.use('/api/events', eventsRouter);
app.use('/api/team', teamRouter);
app.use('/api/settings', settingsRouter);
// 404
app.use((_req, res) => res.status(404).json({ error: 'Not found' }));
// Global error handler
app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error(`${BG_RED}${WHITE}${BOLD} UNHANDLED ERROR ${R}`, err);
res.status(500).json({ error: 'Internal server error' });
});
export default app;