Phase 1a: shared schemas, service layer, server tooling

- shared/schemas/: move Zod schemas out of routes so client + server share them
- shared/types.ts: inferred types and enums for cross-package use
- server tsconfig rootDir raised to ".." so shared/ compiles in-tree
- server/src/services/: ticket, comment, cti, user, auth, notification (stub), search (stub)
- Routes thinned to validate-delegate-return; business logic now testable in isolation
- server/src/lib/httpError.ts: typed HttpError replaces ad-hoc throw shapes
- server/src/lib/logger.ts: pino structured logging replaces console.log
- autoClose job delegates to ticketService.closeStale()
- express-rate-limit on /api/auth/login (10 / 15min / IP)
- vitest + vitest-mock-extended; 20 service-level tests cover auth, ticket, comment, user flows
- CI: lint + test jobs before docker builds

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 15:34:57 -04:00
parent 27d2ab0f0d
commit aff52e5672
38 changed files with 1260 additions and 2119 deletions
+15 -2
View File
@@ -2,6 +2,8 @@ import 'express-async-errors';
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import pinoHttp from 'pino-http';
import rateLimit from 'express-rate-limit';
import authRoutes from './routes/auth';
import ticketRoutes from './routes/tickets';
@@ -10,20 +12,31 @@ import userRoutes from './routes/users';
import { authenticate } from './middleware/auth';
import { errorHandler } from './middleware/errorHandler';
import { startAutoCloseJob } from './jobs/autoClose';
import { logger } from './lib/logger';
dotenv.config();
if (!process.env.JWT_SECRET) {
console.error('FATAL: JWT_SECRET is not set');
logger.fatal('JWT_SECRET is not set');
process.exit(1);
}
const app = express();
app.use(pinoHttp({ logger }));
app.use(cors({ origin: process.env.CLIENT_URL || 'http://localhost:5173' }));
app.use(express.json());
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many login attempts. Try again in 15 minutes.' },
});
// Public
app.use('/api/auth/login', loginLimiter);
app.use('/api/auth', authRoutes);
// Protected
@@ -37,5 +50,5 @@ startAutoCloseJob();
const PORT = Number(process.env.PORT) || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
logger.info({ port: PORT }, 'Server started');
});