Files
crm-server/src/routes/ai-kurzy.routes.js
richardtekula f463467264 refactor: Extract shared multer/upload config from routes
Create src/config/upload.js with createUpload() factory and shared
ALLOWED_FILE_TYPES constant. Replace duplicated multer configs in 5
route files with calls to the shared factory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 07:21:35 +01:00

223 lines
5.8 KiB
JavaScript

import express from 'express';
import path from 'path';
import * as aiKurzyController from '../controllers/ai-kurzy.controller.js';
import { authenticate } from '../middlewares/auth/authMiddleware.js';
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
import { validateBody, validateParams, validateQuery } from '../middlewares/security/validateInput.js';
import { z } from 'zod';
import { createUpload, ALLOWED_FILE_TYPES } from '../config/upload.js';
const router = express.Router();
const upload = createUpload({
maxSizeMB: 10,
allowedTypes: ALLOWED_FILE_TYPES,
errorMessage: 'Nepovolený typ súboru. Povolené: PDF, Word, Excel, obrázky, CSV, TXT.',
diskPath: path.join(process.cwd(), 'uploads', 'ai-kurzy'),
});
// Validation schemas
const kurzIdSchema = z.object({
kurzId: z.string().regex(/^\d+$/),
});
const ucastnikIdSchema = z.object({
ucastnikId: z.string().regex(/^\d+$/),
});
const registraciaIdSchema = z.object({
registraciaId: z.string().regex(/^\d+$/),
});
const createKurzSchema = z.object({
nazov: z.string().min(1).max(255),
typKurzu: z.string().min(1).max(100),
popis: z.string().optional().nullable(),
cena: z.string().or(z.number()),
maxKapacita: z.number().int().positive().optional().nullable(),
aktivny: z.boolean().optional(),
farba: z.string().max(20).optional().nullable(),
});
const updateKurzSchema = createKurzSchema.partial();
const createUcastnikSchema = z.object({
titul: z.string().max(50).optional().nullable(),
meno: z.string().min(1).max(100),
priezvisko: z.string().min(1).max(100),
email: z.string().email().max(255),
telefon: z.string().max(50).optional().nullable(),
firma: z.string().max(255).optional().nullable(),
firmaIco: z.string().max(20).optional().nullable(),
firmaDic: z.string().max(20).optional().nullable(),
firmaIcDph: z.string().max(25).optional().nullable(),
firmaSidlo: z.string().optional().nullable(),
mesto: z.string().max(100).optional().nullable(),
ulica: z.string().max(255).optional().nullable(),
psc: z.string().max(10).optional().nullable(),
});
const updateUcastnikSchema = createUcastnikSchema.partial();
const createRegistraciaSchema = z.object({
kurzId: z.number().int().positive(),
ucastnikId: z.number().int().positive(),
datumOd: z.string().optional().nullable(),
datumDo: z.string().optional().nullable(),
formaKurzu: z.enum(['prezencne', 'online', 'hybridne']).optional(),
pocetUcastnikov: z.number().int().positive().optional(),
fakturaCislo: z.string().max(100).optional().nullable(),
fakturaVystavena: z.boolean().optional(),
zaplatene: z.boolean().optional(),
stav: z.enum(['potencialny', 'registrovany', 'potvrdeny', 'absolvoval', 'zruseny']).optional(),
poznamka: z.string().optional().nullable(),
});
const updateRegistraciaSchema = createRegistraciaSchema.partial();
const registracieQuerySchema = z.object({
kurzId: z.string().regex(/^\d+$/).optional(),
});
// All routes require authentication and admin role
router.use(authenticate);
router.use(requireAdmin);
// ==================== STATISTICS ====================
router.get('/stats', aiKurzyController.getStats);
// ==================== KURZY ====================
router.get('/kurzy', aiKurzyController.getAllKurzy);
router.post(
'/kurzy',
validateBody(createKurzSchema),
aiKurzyController.createKurz
);
router.get(
'/kurzy/:kurzId',
validateParams(kurzIdSchema),
aiKurzyController.getKurzById
);
router.put(
'/kurzy/:kurzId',
validateParams(kurzIdSchema),
validateBody(updateKurzSchema),
aiKurzyController.updateKurz
);
router.delete(
'/kurzy/:kurzId',
validateParams(kurzIdSchema),
aiKurzyController.deleteKurz
);
// ==================== UCASTNICI ====================
router.get('/ucastnici', aiKurzyController.getAllUcastnici);
router.post(
'/ucastnici',
validateBody(createUcastnikSchema),
aiKurzyController.createUcastnik
);
router.get(
'/ucastnici/:ucastnikId',
validateParams(ucastnikIdSchema),
aiKurzyController.getUcastnikById
);
router.put(
'/ucastnici/:ucastnikId',
validateParams(ucastnikIdSchema),
validateBody(updateUcastnikSchema),
aiKurzyController.updateUcastnik
);
router.delete(
'/ucastnici/:ucastnikId',
validateParams(ucastnikIdSchema),
aiKurzyController.deleteUcastnik
);
// ==================== REGISTRACIE ====================
router.get(
'/registracie',
validateQuery(registracieQuerySchema),
aiKurzyController.getAllRegistracie
);
router.post(
'/registracie',
validateBody(createRegistraciaSchema),
aiKurzyController.createRegistracia
);
router.get(
'/registracie/:registraciaId',
validateParams(registraciaIdSchema),
aiKurzyController.getRegistraciaById
);
router.put(
'/registracie/:registraciaId',
validateParams(registraciaIdSchema),
validateBody(updateRegistraciaSchema),
aiKurzyController.updateRegistracia
);
router.delete(
'/registracie/:registraciaId',
validateParams(registraciaIdSchema),
aiKurzyController.deleteRegistracia
);
// ==================== COMBINED TABLE (Excel-style) ====================
router.get('/table', aiKurzyController.getCombinedTable);
const updateFieldSchema = z.object({
field: z.string(),
value: z.any(),
});
router.patch(
'/table/:registraciaId/field',
validateParams(registraciaIdSchema),
validateBody(updateFieldSchema),
aiKurzyController.updateField
);
// ==================== PRILOHY (Documents) ====================
const prilohaIdSchema = z.object({
prilohaId: z.string().regex(/^\d+$/),
});
router.get(
'/registracie/:registraciaId/prilohy',
validateParams(registraciaIdSchema),
aiKurzyController.getPrilohy
);
router.post(
'/registracie/:registraciaId/prilohy',
validateParams(registraciaIdSchema),
upload.single('file'),
aiKurzyController.uploadPriloha
);
router.delete(
'/prilohy/:prilohaId',
validateParams(prilohaIdSchema),
aiKurzyController.deletePriloha
);
export default router;