258 lines
6.6 KiB
TypeScript
258 lines
6.6 KiB
TypeScript
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<CorsoRow | undefined> {
|
|
const [rows] = await pool.execute<CorsoRow[]>(
|
|
`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<CorsoRow[]>(
|
|
`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<ResultSetHeader>(
|
|
`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<ResultSetHeader>(
|
|
`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;
|