esameits24-26backend/src/config/openapi.ts

744 lines
22 KiB
TypeScript
Raw Normal View History

//======================================================
// File: openapi.ts
2026-07-14 15:09:07 +02:00
// Script per la gestione dello schema di configurazione
// di swagger.
// @author: "villari.andrea@libero.it"
// @version: "1.0.0 2026-07-14"
//======================================================
const errorSchema = {
type: "object",
properties: {
error: { type: "string" },
fields: {
type: "object",
additionalProperties: { type: "string" },
},
},
} as const;
const utenteSchema = {
type: "object",
properties: {
id: { type: "integer" },
nome: { type: "string" },
cognome: { type: "string" },
email: { type: "string", format: "email" },
ruolo: { type: "string", enum: ["Dipendente", "Referente Academy"] },
},
} as const;
const corsoSchema = {
type: "object",
properties: {
id: { type: "integer" },
titolo: { type: "string" },
descrizione: { type: "string", nullable: true },
categoria: { type: "string" },
durataOre: { type: "integer", minimum: 1 },
obbligatorio: { type: "boolean" },
attivo: { type: "boolean" },
},
} as const;
const assegnazioneSchema = {
type: "object",
properties: {
id: { type: "integer" },
corsoId: { type: "integer" },
dipendenteId: { type: "integer" },
dataAssegnazione: { type: "string", format: "date" },
dataScadenza: { type: "string", format: "date" },
stato: {
type: "string",
enum: ["Assegnato", "Completato", "Scaduto", "Annullato"],
},
dataCompletamento: { type: "string", format: "date", nullable: true },
corso: {
type: "object",
properties: {
id: { type: "integer" },
titolo: { type: "string" },
categoria: { type: "string" },
durataOre: { type: "integer" },
},
},
dipendente: {
type: "object",
properties: {
id: { type: "integer" },
nome: { type: "string" },
cognome: { type: "string" },
email: { type: "string", format: "email" },
},
},
},
} as const;
const statisticaSchema = {
type: "object",
properties: {
mese: { type: "string", example: "2026-05" },
categoria: { type: "string", example: "Sicurezza" },
numeroAssegnazioni: { type: "integer", example: 18 },
numeroCompletamenti: { type: "integer", example: 12 },
percentualeCompletamento: { type: "number", example: 66.67 },
},
} as const;
const idParam = {
name: "id",
in: "path",
required: true,
schema: { type: "integer", minimum: 1 },
} as const;
const responses = {
unauthorized: {
description: "Autenticazione richiesta",
content: { "application/json": { schema: errorSchema } },
},
forbidden: {
description: "Operazione non consentita per il ruolo corrente",
content: { "application/json": { schema: errorSchema } },
},
notFound: {
description: "Risorsa non trovata",
content: { "application/json": { schema: errorSchema } },
},
validationError: {
description: "Validazione fallita",
content: { "application/json": { schema: errorSchema } },
},
} as const;
export const openApiSpec = {
openapi: "3.0.3",
info: {
title: "Esame ITS 24-26 Academy API",
version: "1.0.0",
description: [
"API REST per la gestione dell'Academy aziendale (corsi, assegnazioni, statistiche).",
"",
"### Autenticazione",
"Le API protette usano sessioni Auth.js tramite cookie HTTP-only.",
"1. `GET /api/auth/csrf` per ottenere il token CSRF",
"2. `POST /api/auth/callback/credentials` con `email`, `password`, `csrfToken`",
"3. Le richieste successive inviano automaticamente il cookie di sessione",
"",
"### Utenti di test",
"| Email | Password | Ruolo |",
"|-------|----------|-------|",
"| admin@academy.it | Password123! | Referente Academy |",
"| maria.bianchi@academy.it | Password123! | Dipendente |",
].join("\n"),
},
servers: [{ url: "/", description: "Server corrente" }],
tags: [
{ name: "Sistema", description: "Health check e documentazione" },
{ name: "Autenticazione", description: "Login e sessione" },
{ name: "Profilo", description: "Utente autenticato" },
{ name: "Utenti", description: "Gestione utenti (Referente Academy)" },
{ name: "Corsi", description: "Catalogo corsi Academy" },
{ name: "Assegnazioni", description: "Assegnazioni corsi ai dipendenti" },
{ name: "Statistiche", description: "Riepiloghi formativi" },
],
components: {
securitySchemes: {
cookieAuth: {
type: "apiKey",
in: "cookie",
name: "authjs.session-token",
description:
"Cookie di sessione Auth.js. In produzione può essere __Secure-authjs.session-token.",
},
},
schemas: {
Error: errorSchema,
Utente: utenteSchema,
Corso: corsoSchema,
AssegnazioneCorso: assegnazioneSchema,
StatisticaAcademy: statisticaSchema,
UtenteCreate: {
type: "object",
required: ["nome", "cognome", "email", "password", "ruolo"],
properties: {
nome: { type: "string" },
cognome: { type: "string" },
email: { type: "string", format: "email" },
password: { type: "string", minLength: 8 },
ruolo: { type: "string", enum: ["Dipendente", "Referente Academy"] },
},
},
CorsoCreate: {
type: "object",
required: ["titolo", "categoria", "durataOre"],
properties: {
titolo: { type: "string" },
descrizione: { type: "string", nullable: true },
categoria: { type: "string" },
durataOre: { type: "integer", minimum: 1 },
obbligatorio: { type: "boolean", default: false },
attivo: { type: "boolean", default: true },
},
},
AssegnazioneCreate: {
type: "object",
required: ["corsoId", "dipendenteId", "dataAssegnazione", "dataScadenza"],
properties: {
corsoId: { type: "integer" },
dipendenteId: { type: "integer" },
dataAssegnazione: { type: "string", format: "date" },
dataScadenza: { type: "string", format: "date" },
},
},
AssegnazioneCompleta: {
type: "object",
properties: {
dataCompletamento: {
type: "string",
format: "date",
description: "Se omessa, viene usata la data odierna",
},
},
},
},
},
paths: {
"/api/health": {
get: {
tags: ["Sistema"],
summary: "Health check",
responses: {
200: {
description: "Servizio attivo",
content: {
"application/json": {
schema: {
type: "object",
properties: { status: { type: "string", example: "ok" } },
},
},
},
},
},
},
},
"/api/auth/csrf": {
get: {
tags: ["Autenticazione"],
summary: "Ottieni token CSRF per il login",
responses: {
200: {
description: "Token CSRF",
content: {
"application/json": {
schema: {
type: "object",
properties: { csrfToken: { type: "string" } },
},
},
},
},
},
},
},
"/api/auth/callback/credentials": {
post: {
tags: ["Autenticazione"],
summary: "Login con email e password",
requestBody: {
required: true,
content: {
"application/x-www-form-urlencoded": {
schema: {
type: "object",
required: ["csrfToken", "email", "password"],
properties: {
csrfToken: { type: "string" },
email: { type: "string", format: "email" },
password: { type: "string" },
redirect: { type: "string", example: "false" },
},
},
},
},
},
responses: {
200: { description: "Login riuscito, cookie di sessione impostato" },
401: { description: "Credenziali non valide" },
},
},
},
"/api/auth/signout": {
post: {
tags: ["Autenticazione"],
summary: "Logout",
security: [{ cookieAuth: [] }],
responses: {
200: { description: "Logout effettuato" },
},
},
},
"/api/me": {
get: {
tags: ["Profilo"],
summary: "Profilo utente autenticato",
security: [{ cookieAuth: [] }],
responses: {
200: {
description: "Profilo corrente",
content: { "application/json": { schema: utenteSchema } },
},
401: responses.unauthorized,
},
},
},
"/api/utenti": {
get: {
tags: ["Utenti"],
summary: "Elenco utenti",
security: [{ cookieAuth: [] }],
parameters: [
{
name: "ruolo",
in: "query",
schema: { type: "string", enum: ["Dipendente", "Referente Academy"] },
description: "Filtra per ruolo (es. Dipendente per l'elenco dipendenti)",
},
],
responses: {
200: {
description: "Lista utenti",
content: {
"application/json": {
schema: { type: "array", items: utenteSchema },
},
},
},
401: responses.unauthorized,
403: responses.forbidden,
},
},
post: {
tags: ["Utenti"],
summary: "Crea utente",
security: [{ cookieAuth: [] }],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/UtenteCreate" },
},
},
},
responses: {
201: {
description: "Utente creato",
content: { "application/json": { schema: utenteSchema } },
},
400: responses.validationError,
403: responses.forbidden,
409: { description: "Email già registrata" },
},
},
},
"/api/utenti/login": {
post: {
tags: ["Utenti"],
summary: "Login utente",
description:
"Autentica l'utente con email e password. Imposta il cookie di sessione.",
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
required: ["email", "password"],
properties: {
email: { type: "string", format: "email" },
password: { type: "string" },
},
},
},
},
},
responses: {
200: {
description: "Login riuscito",
content: { "application/json": { schema: utenteSchema } },
},
400: responses.validationError,
401: { description: "Credenziali non valide" },
},
},
},
"/api/utenti/register": {
post: {
tags: ["Utenti"],
summary: "Registra utente",
description:
"Crea un nuovo utente (Dipendente o Referente Academy). Richiede autenticazione Referente Academy.",
security: [{ cookieAuth: [] }],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/UtenteCreate" },
},
},
},
responses: {
201: {
description: "Utente registrato",
content: { "application/json": { schema: utenteSchema } },
},
400: responses.validationError,
403: responses.forbidden,
409: { description: "Email già registrata" },
},
},
},
"/api/utenti/{id}": {
get: {
tags: ["Utenti"],
summary: "Dettaglio utente",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
200: {
description: "Utente trovato",
content: { "application/json": { schema: utenteSchema } },
},
404: responses.notFound,
},
},
put: {
tags: ["Utenti"],
summary: "Aggiorna utente",
security: [{ cookieAuth: [] }],
parameters: [idParam],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/UtenteCreate" },
},
},
},
responses: {
200: {
description: "Utente aggiornato",
content: { "application/json": { schema: utenteSchema } },
},
400: responses.validationError,
404: responses.notFound,
},
},
delete: {
tags: ["Utenti"],
summary: "Elimina utente",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
204: { description: "Utente eliminato" },
400: { description: "Impossibile eliminare il proprio account" },
404: responses.notFound,
},
},
},
"/api/corsi": {
get: {
tags: ["Corsi"],
summary: "Elenco corsi",
security: [{ cookieAuth: [] }],
parameters: [
{ name: "categoria", in: "query", schema: { type: "string" } },
{ name: "attivo", in: "query", schema: { type: "boolean" } },
],
responses: {
200: {
description: "Lista corsi",
content: {
"application/json": {
schema: { type: "array", items: corsoSchema },
},
},
},
},
},
post: {
tags: ["Corsi"],
summary: "Crea corso",
security: [{ cookieAuth: [] }],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CorsoCreate" },
},
},
},
responses: {
201: {
description: "Corso creato",
content: { "application/json": { schema: corsoSchema } },
},
403: responses.forbidden,
},
},
},
"/api/corsi/{id}": {
get: {
tags: ["Corsi"],
summary: "Dettaglio corso",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
200: {
description: "Corso trovato",
content: { "application/json": { schema: corsoSchema } },
},
404: responses.notFound,
},
},
put: {
tags: ["Corsi"],
summary: "Modifica corso",
security: [{ cookieAuth: [] }],
parameters: [idParam],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/CorsoCreate" },
},
},
},
responses: {
200: {
description: "Corso aggiornato",
content: { "application/json": { schema: corsoSchema } },
},
403: responses.forbidden,
},
},
delete: {
tags: ["Corsi"],
summary: "Elimina corso",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
204: { description: "Corso eliminato" },
409: { description: "Corso con assegnazioni collegate" },
},
},
},
"/api/corsi/{id}/disattiva": {
put: {
tags: ["Corsi"],
summary: "Disattiva corso",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
200: {
description: "Corso disattivato",
content: { "application/json": { schema: corsoSchema } },
},
},
},
},
"/api/assegnazioni-corsi": {
get: {
tags: ["Assegnazioni"],
summary: "Elenco assegnazioni",
security: [{ cookieAuth: [] }],
parameters: [
{
name: "stato",
in: "query",
schema: {
type: "string",
enum: ["Assegnato", "Completato", "Scaduto", "Annullato"],
},
},
{ name: "categoria", in: "query", schema: { type: "string" } },
{ name: "corsoId", in: "query", schema: { type: "integer" } },
{
name: "dipendenteId",
in: "query",
schema: { type: "integer" },
description: "Solo Referente Academy",
},
],
responses: {
200: {
description: "Lista assegnazioni",
content: {
"application/json": {
schema: { type: "array", items: assegnazioneSchema },
},
},
},
},
},
post: {
tags: ["Assegnazioni"],
summary: "Assegna corso a dipendente",
security: [{ cookieAuth: [] }],
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/AssegnazioneCreate" },
},
},
},
responses: {
201: {
description: "Assegnazione creata",
content: { "application/json": { schema: assegnazioneSchema } },
},
403: responses.forbidden,
},
},
},
"/api/assegnazioni-corsi/{id}": {
get: {
tags: ["Assegnazioni"],
summary: "Dettaglio assegnazione",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
200: {
description: "Assegnazione trovata",
content: { "application/json": { schema: assegnazioneSchema } },
},
403: responses.forbidden,
404: responses.notFound,
},
},
put: {
tags: ["Assegnazioni"],
summary: "Modifica assegnazione",
security: [{ cookieAuth: [] }],
parameters: [idParam],
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
corsoId: { type: "integer" },
dipendenteId: { type: "integer" },
dataAssegnazione: { type: "string", format: "date" },
dataScadenza: { type: "string", format: "date" },
stato: {
type: "string",
enum: ["Assegnato", "Completato", "Scaduto", "Annullato"],
},
},
},
}
},
},
responses: {
200: {
description: "Assegnazione aggiornata",
content: { "application/json": { schema: assegnazioneSchema } },
},
},
},
delete: {
tags: ["Assegnazioni"],
summary: "Elimina assegnazione",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
204: { description: "Assegnazione eliminata" },
},
},
},
"/api/assegnazioni-corsi/{id}/completa": {
put: {
tags: ["Assegnazioni"],
summary: "Segna assegnazione come completata",
description: "Solo il dipendente titolare può completare la propria assegnazione.",
security: [{ cookieAuth: [] }],
parameters: [idParam],
requestBody: {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/AssegnazioneCompleta" },
},
},
},
responses: {
200: {
description: "Assegnazione completata",
content: { "application/json": { schema: assegnazioneSchema } },
},
403: responses.forbidden,
},
},
},
"/api/assegnazioni-corsi/{id}/annulla": {
put: {
tags: ["Assegnazioni"],
summary: "Annulla assegnazione",
security: [{ cookieAuth: [] }],
parameters: [idParam],
responses: {
200: {
description: "Assegnazione annullata",
content: { "application/json": { schema: assegnazioneSchema } },
},
},
},
},
"/api/statistiche/academy": {
get: {
tags: ["Statistiche"],
summary: "Riepilogo formazione per mese e categoria",
security: [{ cookieAuth: [] }],
parameters: [
{
name: "mese",
in: "query",
schema: { type: "string", example: "2026-05" },
description: "Filtra per mese (YYYY-MM)",
},
{
name: "daMese",
in: "query",
schema: { type: "string", example: "2026-01" },
},
{
name: "aMese",
in: "query",
schema: { type: "string", example: "2026-12" },
},
{ name: "categoria", in: "query", schema: { type: "string" } },
{ name: "dipendenteId", in: "query", schema: { type: "integer" } },
],
responses: {
200: {
description: "Statistiche aggregate",
content: {
"application/json": {
schema: {
type: "array",
items: { $ref: "#/components/schemas/StatisticaAcademy" },
},
},
},
},
403: responses.forbidden,
},
},
},
},
} as const;