import { Router } from "express"; import type { ResultSetHeader, RowDataPacket } from "mysql2"; import pool from "../db/pool.js"; import { mapDbError } from "../errors/db.js"; import { requireAuth, requireReferenteAcademy } from "../middleware/auth.js"; import { validateBody } from "../validation/middleware.js"; import { validateCorsoCreate, validateCorsoUpdate, type CorsoCreateInput, type CorsoUpdateInput, } from "../validation/corso.js"; const router = Router(); interface CorsoRow extends RowDataPacket { CorsoID: number; Titolo: string; Descrizione: string | null; Categoria: string; DurataOre: number; Obbligatorio: number; Attivo: number; } function toCorsoResponse(row: CorsoRow) { return { id: row.CorsoID, titolo: row.Titolo, descrizione: row.Descrizione, categoria: row.Categoria, durataOre: row.DurataOre, obbligatorio: Boolean(row.Obbligatorio), attivo: Boolean(row.Attivo), }; } function parseIdParam(value: string | string[]): number | null { const raw = Array.isArray(value) ? value[0] : value; const id = Number(raw); if (!Number.isInteger(id) || id <= 0) return null; return id; } function parseAttivoFilter(value: unknown): boolean | null { if (value === "true" || value === true) return true; if (value === "false" || value === false) return false; return null; } async function findCorsoById(id: number): Promise { const [rows] = await pool.execute( `SELECT CorsoID, Titolo, Descrizione, Categoria, DurataOre, Obbligatorio, Attivo FROM corso WHERE CorsoID = ?`, [id] ); return rows[0]; } router.use(requireAuth); router.get("/", async (req, res) => { const conditions: string[] = []; const values: (string | number)[] = []; const categoria = req.query.categoria?.toString().trim(); if (categoria) { conditions.push("Categoria = ?"); values.push(categoria); } const attivo = parseAttivoFilter(req.query.attivo); if (attivo !== null) { conditions.push("Attivo = ?"); values.push(attivo ? 1 : 0); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const [rows] = await pool.execute( `SELECT CorsoID, Titolo, Descrizione, Categoria, DurataOre, Obbligatorio, Attivo FROM corso ${whereClause} ORDER BY Categoria, Titolo`, values ); res.json(rows.map(toCorsoResponse)); }); router.get("/:id", async (req, res) => { const id = parseIdParam(req.params.id); if (!id) { res.status(400).json({ error: "ID non valido" }); return; } const corso = await findCorsoById(id); if (!corso) { res.status(404).json({ error: "Corso non trovato" }); return; } res.json(toCorsoResponse(corso)); }); router.post("/", requireReferenteAcademy, validateBody(validateCorsoCreate), async (_req, res) => { const body = res.locals.validatedBody as CorsoCreateInput; try { const [result] = await pool.execute( `INSERT INTO corso (Titolo, Descrizione, Categoria, DurataOre, Obbligatorio, Attivo) VALUES (?, ?, ?, ?, ?, ?)`, [ body.titolo, body.descrizione, body.categoria, body.durataOre, body.obbligatorio ? 1 : 0, body.attivo ? 1 : 0, ] ); const created = await findCorsoById(result.insertId); if (!created) { res.status(500).json({ error: "Errore durante la creazione del corso" }); return; } res.status(201).json(toCorsoResponse(created)); } catch (error) { const mapped = mapDbError(error); if (mapped) { res.status(mapped.status).json({ error: mapped.error, fields: mapped.fields, }); return; } throw error; } }); router.put("/:id", requireReferenteAcademy, validateBody(validateCorsoUpdate), async (req, res) => { const id = parseIdParam(req.params.id); if (!id) { res.status(400).json({ error: "ID non valido" }); return; } const existing = await findCorsoById(id); if (!existing) { res.status(404).json({ error: "Corso non trovato" }); return; } const updates = res.locals.validatedBody as CorsoUpdateInput; const setClauses: string[] = []; const values: (string | number | null)[] = []; if (updates.titolo !== undefined) { setClauses.push("Titolo = ?"); values.push(updates.titolo); } if (updates.descrizione !== undefined) { setClauses.push("Descrizione = ?"); values.push(updates.descrizione); } if (updates.categoria !== undefined) { setClauses.push("Categoria = ?"); values.push(updates.categoria); } if (updates.durataOre !== undefined) { setClauses.push("DurataOre = ?"); values.push(updates.durataOre); } if (updates.obbligatorio !== undefined) { setClauses.push("Obbligatorio = ?"); values.push(updates.obbligatorio ? 1 : 0); } if (updates.attivo !== undefined) { setClauses.push("Attivo = ?"); values.push(updates.attivo ? 1 : 0); } try { await pool.execute( `UPDATE corso SET ${setClauses.join(", ")} WHERE CorsoID = ?`, [...values, id] ); const updated = await findCorsoById(id); if (!updated) { res.status(500).json({ error: "Errore durante l'aggiornamento del corso" }); return; } res.json(toCorsoResponse(updated)); } catch (error) { const mapped = mapDbError(error); if (mapped) { res.status(mapped.status).json({ error: mapped.error, fields: mapped.fields, }); return; } throw error; } }); router.put("/:id/disattiva", requireReferenteAcademy, async (req, res) => { const id = parseIdParam(req.params.id); if (!id) { res.status(400).json({ error: "ID non valido" }); return; } const existing = await findCorsoById(id); if (!existing) { res.status(404).json({ error: "Corso non trovato" }); return; } await pool.execute("UPDATE corso SET Attivo = 0 WHERE CorsoID = ?", [id]); const updated = await findCorsoById(id); res.json(toCorsoResponse(updated!)); }); router.delete("/:id", requireReferenteAcademy, async (req, res) => { const id = parseIdParam(req.params.id); if (!id) { res.status(400).json({ error: "ID non valido" }); return; } const existing = await findCorsoById(id); if (!existing) { res.status(404).json({ error: "Corso non trovato" }); return; } try { await pool.execute("DELETE FROM corso WHERE CorsoID = ?", [id]); res.status(204).send(); } catch (error) { const mapped = mapDbError(error); if (mapped) { res.status(mapped.status).json({ error: mapped.error }); return; } throw error; } }); export default router;