chore: initial Vector 2.0 monorepo
Ground-up TypeScript rewrite of the Vector hardware parts inventory
system. Ships the full roadmap (Phases 0-8) in one initial commit:
- pnpm + Turbo monorepo: apps/{api,web,e2e}, packages/{db,shared,ui,config}
- Express 5 + Prisma 5 + zod validation + JWT w/ refresh-token rotation
- React 19 + Vite + shadcn/ui + TanStack Query/Table + nuqs URL state
- Repair/RMA, tags, bulk ops, saved views, CSV audit export
- Analytics dashboard on Recharts + EOL tracking
- Signed webhook subscriptions (HMAC-SHA256) with in-process emitter
- Vitest unit tests (shared schemas, api services/helpers) + Playwright skeleton
- Gitea Actions CI (lint, typecheck, test+coverage, build) + Renovate
Deferred follow-ups: Postgres cutover (data-migration script ready),
BullMQ worker for webhook delivery, @react-pdf PDF export, CSV import wizard.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import express from 'express';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
import helmet from 'helmet';
|
||||
import { pinoHttp } from 'pino-http';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { prisma } from '@vector/db';
|
||||
|
||||
import { env } from './env.js';
|
||||
import { logger } from './lib/logger.js';
|
||||
import { requestId } from './middleware/request-id.js';
|
||||
import { requireCsrf } from './middleware/csrf.js';
|
||||
import { errorHandler } from './middleware/error.js';
|
||||
import authRoutes from './routes/auth.js';
|
||||
import userRoutes from './routes/users.js';
|
||||
import manufacturerRoutes from './routes/manufacturers.js';
|
||||
import siteRoutes from './routes/sites.js';
|
||||
import roomRoutes from './routes/rooms.js';
|
||||
import binRoutes from './routes/bins.js';
|
||||
import partRoutes from './routes/parts.js';
|
||||
import tagRoutes from './routes/tags.js';
|
||||
import categoryRoutes from './routes/categories.js';
|
||||
import hostRoutes from './routes/hosts.js';
|
||||
import repairRoutes from './routes/repairs.js';
|
||||
import savedViewRoutes from './routes/saved-views.js';
|
||||
import analyticsRoutes from './routes/analytics.js';
|
||||
import webhookRoutes from './routes/webhooks.js';
|
||||
import auditRoutes from './routes/audit.js';
|
||||
|
||||
export const app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
app.use(helmet({ contentSecurityPolicy: false, crossOriginResourcePolicy: { policy: 'same-site' } }));
|
||||
app.use(
|
||||
cors({
|
||||
origin: env.CLIENT_ORIGIN,
|
||||
credentials: true,
|
||||
}),
|
||||
);
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
app.use(cookieParser());
|
||||
app.use(requestId);
|
||||
app.use(
|
||||
pinoHttp({
|
||||
logger,
|
||||
customProps: (req) => ({ requestId: (req as express.Request).requestId }),
|
||||
customLogLevel: (_req, res, err) => {
|
||||
if (err || res.statusCode >= 500) return 'error';
|
||||
if (res.statusCode >= 400) return 'warn';
|
||||
return 'info';
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
app.get('/healthz', (_req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
app.get('/readyz', async (_req, res) => {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
res.json({ status: 'ok', db: 'ok' });
|
||||
} catch {
|
||||
res.status(503).json({ status: 'error', db: 'unreachable' });
|
||||
}
|
||||
});
|
||||
|
||||
const authLimiter = rateLimit({
|
||||
windowMs: 60 * 1000,
|
||||
limit: env.NODE_ENV === 'production' ? 5 : 50,
|
||||
standardHeaders: 'draft-7',
|
||||
legacyHeaders: false,
|
||||
message: { code: 'RATE_LIMITED', message: 'Too many auth requests. Try again soon.' },
|
||||
});
|
||||
|
||||
app.use('/api/auth', authLimiter, authRoutes);
|
||||
app.use('/api', requireCsrf);
|
||||
app.use('/api/users', userRoutes);
|
||||
app.use('/api/manufacturers', manufacturerRoutes);
|
||||
app.use('/api/sites', siteRoutes);
|
||||
app.use('/api/rooms', roomRoutes);
|
||||
app.use('/api/bins', binRoutes);
|
||||
app.use('/api/parts', partRoutes);
|
||||
app.use('/api/tags', tagRoutes);
|
||||
app.use('/api/categories', categoryRoutes);
|
||||
app.use('/api/hosts', hostRoutes);
|
||||
app.use('/api/repairs', repairRoutes);
|
||||
app.use('/api/saved-views', savedViewRoutes);
|
||||
app.use('/api/analytics', analyticsRoutes);
|
||||
app.use('/api/admin/webhooks', webhookRoutes);
|
||||
app.use('/api/admin/audit', auditRoutes);
|
||||
|
||||
app.use(errorHandler);
|
||||
Reference in New Issue
Block a user