initial commit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
BOHA
2026-03-23 08:46:51 +01:00
commit 4608494a3f
130 changed files with 40361 additions and 0 deletions

142
src/server.ts Normal file
View File

@@ -0,0 +1,142 @@
import Fastify from 'fastify';
import cors from '@fastify/cors';
import cookie from '@fastify/cookie';
import rateLimit from '@fastify/rate-limit';
import { config } from './config/env';
import { securityHeaders } from './middleware/security';
// Route imports
import authRoutes from './routes/admin/auth';
import usersRoutes from './routes/admin/users';
import rolesRoutes from './routes/admin/roles';
import attendanceRoutes from './routes/admin/attendance';
import customersRoutes from './routes/admin/customers';
import invoicesRoutes from './routes/admin/invoices';
import quotationsRoutes from './routes/admin/quotations';
import ordersRoutes from './routes/admin/orders';
import projectsRoutes from './routes/admin/projects';
import tripsRoutes from './routes/admin/trips';
import vehiclesRoutes from './routes/admin/vehicles';
import leaveRequestsRoutes from './routes/admin/leave-requests';
import bankAccountsRoutes from './routes/admin/bank-accounts';
import companySettingsRoutes from './routes/admin/company-settings';
import receivedInvoicesRoutes from './routes/admin/received-invoices';
import dashboardRoutes from './routes/admin/dashboard';
import auditLogRoutes from './routes/admin/audit-log';
import profileRoutes from './routes/admin/profile';
import sessionsRoutes from './routes/admin/sessions';
import totpRoutes from './routes/admin/totp';
import scopeTemplatesRoutes from './routes/admin/scope-templates';
import invoicesPdfRoutes from './routes/admin/invoices-pdf';
import offersPdfRoutes from './routes/admin/offers-pdf';
const app = Fastify({
logger: {
level: config.isProduction ? 'warn' : 'info',
},
trustProxy: true,
});
async function start() {
// --- Plugins ---
await app.register(cors, {
origin: config.appEnv === 'local'
? [/^http:\/\/127\.0\.0\.1:\d+$/, /^http:\/\/localhost:\d+$/, /^http:\/\/192\.168\.\d+\.\d+:\d+$/]
: config.cors.origins,
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
});
await app.register(cookie);
await app.register(rateLimit, {
max: 100,
timeWindow: '1 minute',
});
// --- Security headers ---
app.addHook('onRequest', securityHeaders);
// --- Global error handler — consistent { success, error } format ---
app.setErrorHandler((err: Error & { statusCode?: number }, request, reply) => {
const statusCode = err.statusCode ?? 500;
if (statusCode >= 500) {
request.log.error(err);
}
reply.status(statusCode).send({
success: false,
error: statusCode >= 500 ? 'Interní chyba serveru' : (err.message || 'Chyba požadavku'),
});
});
// --- API Routes ---
await app.register(authRoutes, { prefix: '/api/admin' });
await app.register(usersRoutes, { prefix: '/api/admin/users' });
await app.register(rolesRoutes, { prefix: '/api/admin/roles' });
await app.register(attendanceRoutes, { prefix: '/api/admin/attendance' });
await app.register(customersRoutes, { prefix: '/api/admin/customers' });
await app.register(invoicesRoutes, { prefix: '/api/admin/invoices' });
await app.register(quotationsRoutes, { prefix: '/api/admin/offers' });
await app.register(ordersRoutes, { prefix: '/api/admin/orders' });
await app.register(projectsRoutes, { prefix: '/api/admin/projects' });
await app.register(tripsRoutes, { prefix: '/api/admin/trips' });
await app.register(vehiclesRoutes, { prefix: '/api/admin/vehicles' });
await app.register(leaveRequestsRoutes, { prefix: '/api/admin/leave-requests' });
await app.register(bankAccountsRoutes, { prefix: '/api/admin/bank-accounts' });
await app.register(companySettingsRoutes, { prefix: '/api/admin/company-settings' });
await app.register(receivedInvoicesRoutes, { prefix: '/api/admin/received-invoices' });
await app.register(dashboardRoutes, { prefix: '/api/admin/dashboard' });
await app.register(auditLogRoutes, { prefix: '/api/admin/audit-log' });
await app.register(profileRoutes, { prefix: '/api/admin/profile' });
await app.register(sessionsRoutes, { prefix: '/api/admin/sessions' });
await app.register(totpRoutes, { prefix: '/api/admin/totp' });
await app.register(scopeTemplatesRoutes, { prefix: '/api/admin/offers-templates' });
await app.register(invoicesPdfRoutes, { prefix: '/api/admin/invoices-pdf' });
await app.register(offersPdfRoutes, { prefix: '/api/admin/offers-pdf' });
// --- Health check ---
app.get('/api/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
// --- Frontend: Vite dev middleware (dev only) ---
if (!config.isProduction) {
const viteModule = await (Function('return import("vite")')() as Promise<any>);
const createViteServer = viteModule.createServer as (opts: any) => Promise<any>;
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'spa',
});
app.addHook('onRequest', (request, reply, done) => {
if (request.url.startsWith('/api/')) {
done();
return;
}
vite.middlewares(request.raw, reply.raw, done);
});
app.setNotFoundHandler((request, reply) => {
if (request.url.startsWith('/api/')) {
return reply.status(404).send({ success: false, error: 'Not found' });
}
if (!reply.raw.headersSent) {
vite.middlewares(request.raw, reply.raw, () => {
reply.raw.statusCode = 404;
reply.raw.end();
});
}
});
}
// --- Start ---
const port = config.isProduction ? config.port : 3000;
try {
await app.listen({ port, host: config.host });
console.log(`Server running on http://${config.host}:${port}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
start();