esameits24-26backend/src/config/openapi.ts
AV77web 03835bf426
All checks were successful
Deploy to VPS / build (push) Successful in 17s
Deploy to VPS / deploy (push) Successful in 23s
modifica login e inserimento creazione nuovo utente
2026-07-14 16:03:16 +02:00

743 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//======================================================
// File: openapi.ts
// 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;