implementato Swagger nel backend
All checks were successful
Deploy to VPS / build (push) Successful in 17s
Deploy to VPS / deploy (push) Successful in 30s

This commit is contained in:
AV77web 2026-06-09 14:57:40 +02:00
parent cbf7318594
commit 5184669185
8 changed files with 773 additions and 5 deletions

11
dist/index.js vendored
View file

@ -1,8 +1,11 @@
import { ExpressAuth } from "@auth/express"; import { ExpressAuth } from "@auth/express";
import dotenv from "dotenv"; import dotenv from "dotenv";
import express from "express"; import express from "express";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { authConfig } from "./auth/config.js"; import { authConfig } from "./auth/config.js";
import { corsMiddleware } from "./config/cors.js"; import { corsMiddleware } from "./config/cors.js";
import { swaggerServe, swaggerSetup } from "./config/swagger.js";
import { errorHandler } from "./middleware/errorHandler.js"; import { errorHandler } from "./middleware/errorHandler.js";
import categorieRouter from "./routes/categorie.js"; import categorieRouter from "./routes/categorie.js";
import rimborsiRouter from "./routes/rimborsi.js"; import rimborsiRouter from "./routes/rimborsi.js";
@ -18,6 +21,14 @@ app.options(/.*/, corsMiddleware);
app.get("/api/health", (_req, res) => { app.get("/api/health", (_req, res) => {
res.json({ status: "ok" }); res.json({ status: "ok" });
}); });
const swaggerEnabled = process.env.SWAGGER_ENABLED !== "false";
if (swaggerEnabled) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.use("/api/docs", swaggerServe, swaggerSetup);
app.get("/api/docs/openapi.json", (_req, res) => {
res.sendFile(path.join(__dirname, "openapi/openapi.json"));
});
}
app.use("/api/auth", ExpressAuth(authConfig)); app.use("/api/auth", ExpressAuth(authConfig));
app.use("/api/utenti", utentiRouter); app.use("/api/utenti", utentiRouter);
app.use("/api/categorie-spesa", categorieRouter); app.use("/api/categorie-spesa", categorieRouter);

2
dist/index.js.map vendored
View file

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,cAAc,MAAM,sBAAsB,CAAC;AAClD,OAAO,iBAAiB,MAAM,yBAAyB,CAAC;AACxD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAE9C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;AAC1B,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AACxB,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AAElC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACnC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AACrC,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC;AACjD,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;AACzC,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;AAE/C,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE;IAC/B,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,eAAe,MAAM,uBAAuB,CAAC;AACpD,OAAO,cAAc,MAAM,sBAAsB,CAAC;AAClD,OAAO,iBAAiB,MAAM,yBAAyB,CAAC;AACxD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AAE9C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;AAC1B,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AACxB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AACxB,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AAElC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IACnC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,OAAO,CAAC;AAC/D,IAAI,cAAc,EAAE,CAAC;IACnB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IACjD,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAC9C,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9C,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AACrC,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,eAAe,CAAC,CAAC;AACjD,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;AACzC,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,CAAC;AAE/C,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE;IAC/B,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC,CAAC"}

View file

@ -21,6 +21,7 @@ COPY package*.json ./
RUN npm ci --omit=dev RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY --from=build /app/src/openapi ./dist/openapi
EXPOSE 3003 EXPOSE 3003

46
package-lock.json generated
View file

@ -15,13 +15,15 @@
"cors": "^2.8.6", "cors": "^2.8.6",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"express": "^5.2.1", "express": "^5.2.1",
"mysql2": "^3.22.5" "mysql2": "^3.22.5",
"swagger-ui-express": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/node": "^25.9.2", "@types/node": "^25.9.2",
"@types/swagger-ui-express": "^4.1.8",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"tsx": "^4.22.4", "tsx": "^4.22.4",
"typescript": "^6.0.3" "typescript": "^6.0.3"
@ -519,6 +521,13 @@
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
}, },
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@types/bcryptjs": { "node_modules/@types/bcryptjs": {
"version": "2.4.6", "version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
@ -633,6 +642,17 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/swagger-ui-express": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz",
"integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/serve-static": "*"
}
},
"node_modules/accepts": { "node_modules/accepts": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@ -2397,6 +2417,30 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/swagger-ui-dist": {
"version": "5.32.6",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
"integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
"integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
"license": "MIT",
"dependencies": {
"swagger-ui-dist": ">=5.0.0"
},
"engines": {
"node": ">= v0.10.32"
},
"peerDependencies": {
"express": ">=4.0.0 || >=5.0.0-beta"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",

View file

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "tsx watch src/index.ts", "dev": "tsx watch src/index.ts",
"build": "tsc", "build": "tsc && node --input-type=module -e \"import { cpSync } from 'node:fs'; cpSync('src/openapi', 'dist/openapi', { recursive: true });\"",
"start": "node dist/index.js" "start": "node dist/index.js"
}, },
"repository": { "repository": {
@ -23,15 +23,17 @@
"cors": "^2.8.6", "cors": "^2.8.6",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"express": "^5.2.1", "express": "^5.2.1",
"mysql2": "^3.22.5" "mysql2": "^3.22.5",
"swagger-ui-express": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.19", "@types/cors": "^2.8.19",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/node": "^25.9.2", "@types/node": "^25.9.2",
"@types/swagger-ui-express": "^4.1.8",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"tsx": "^4.22.4", "tsx": "^4.22.4",
"typescript": "^6.0.3" "typescript": "^6.0.3"
} }
} }

19
src/config/swagger.ts Normal file
View file

@ -0,0 +1,19 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import swaggerUi from "swagger-ui-express";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const specPath = path.join(__dirname, "../openapi/openapi.json");
const openApiDocument = JSON.parse(readFileSync(specPath, "utf-8"));
export const swaggerServe = swaggerUi.serve;
export const swaggerSetup = swaggerUi.setup(openApiDocument, {
customSiteTitle: "API Rimborsi Spese Documentazione",
swaggerOptions: {
persistAuthorization: true,
withCredentials: true,
},
});
export { openApiDocument };

View file

@ -1,8 +1,11 @@
import { ExpressAuth } from "@auth/express"; import { ExpressAuth } from "@auth/express";
import dotenv from "dotenv"; import dotenv from "dotenv";
import express from "express"; import express from "express";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { authConfig } from "./auth/config.js"; import { authConfig } from "./auth/config.js";
import { corsMiddleware } from "./config/cors.js"; import { corsMiddleware } from "./config/cors.js";
import { swaggerServe, swaggerSetup } from "./config/swagger.js";
import { errorHandler } from "./middleware/errorHandler.js"; import { errorHandler } from "./middleware/errorHandler.js";
import categorieRouter from "./routes/categorie.js"; import categorieRouter from "./routes/categorie.js";
import rimborsiRouter from "./routes/rimborsi.js"; import rimborsiRouter from "./routes/rimborsi.js";
@ -23,6 +26,15 @@ app.get("/api/health", (_req, res) => {
res.json({ status: "ok" }); res.json({ status: "ok" });
}); });
const swaggerEnabled = process.env.SWAGGER_ENABLED !== "false";
if (swaggerEnabled) {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.use("/api/docs", swaggerServe, swaggerSetup);
app.get("/api/docs/openapi.json", (_req, res) => {
res.sendFile(path.join(__dirname, "openapi/openapi.json"));
});
}
app.use("/api/auth", ExpressAuth(authConfig)); app.use("/api/auth", ExpressAuth(authConfig));
app.use("/api/utenti", utentiRouter); app.use("/api/utenti", utentiRouter);
app.use("/api/categorie-spesa", categorieRouter); app.use("/api/categorie-spesa", categorieRouter);

679
src/openapi/openapi.json Normal file
View file

@ -0,0 +1,679 @@
{
"openapi": "3.0.3",
"info": {
"title": "API Gestione Rimborsi Spese Aziendali",
"version": "1.0.0",
"description": "Backend prova pratica S5 Gestione rimborsi spese aziendali"
},
"servers": [
{
"url": "http://localhost:3003",
"description": "Sviluppo locale"
}
],
"tags": [
{ "name": "Sistema", "description": "Health check" },
{ "name": "Autenticazione", "description": "Login e sessione Auth.js" },
{ "name": "Utenti", "description": "Registrazione e profilo" },
{ "name": "Categorie", "description": "Categorie di spesa" },
{ "name": "Rimborsi", "description": "Richieste di rimborso" },
{ "name": "Statistiche", "description": "Report amministrativi" }
],
"components": {
"securitySchemes": {
"cookieAuth": {
"type": "apiKey",
"in": "cookie",
"name": "authjs.session-token",
"description": "Cookie di sessione impostato dopo il login. In produzione il nome può essere __Secure-authjs.session-token."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": { "type": "string" }
},
"required": ["error"]
},
"Message": {
"type": "object",
"properties": {
"message": { "type": "string" }
},
"required": ["message"]
},
"Ruolo": {
"type": "string",
"enum": ["DIPENDENTE", "RESPONSABILE_AMMINISTRATIVO"]
},
"StatoRichiesta": {
"type": "string",
"enum": ["IN_ATTESA", "APPROVATA", "RIFIUTATA", "LIQUIDATA"]
},
"Utente": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"nome": { "type": "string" },
"cognome": { "type": "string" },
"email": { "type": "string", "format": "email" },
"ruolo": { "$ref": "#/components/schemas/Ruolo" }
},
"required": ["id", "nome", "cognome", "email", "ruolo"]
},
"UtenteSintetico": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"nome": { "type": "string" },
"cognome": { "type": "string" }
},
"required": ["id", "nome", "cognome"]
},
"RegistrazioneInput": {
"type": "object",
"required": ["nome", "cognome", "email", "password", "confermaPassword"],
"properties": {
"nome": { "type": "string", "example": "Mario" },
"cognome": { "type": "string", "example": "Rossi" },
"email": { "type": "string", "format": "email", "example": "mario.rossi@azienda.it" },
"password": { "type": "string", "format": "password" },
"confermaPassword": { "type": "string", "format": "password" },
"ruolo": { "$ref": "#/components/schemas/Ruolo" }
}
},
"RegistrazioneResponse": {
"type": "object",
"properties": {
"message": { "type": "string" },
"utente": { "$ref": "#/components/schemas/Utente" }
},
"required": ["message", "utente"]
},
"CategoriaSpesa": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"descrizione": { "type": "string" }
},
"required": ["id", "descrizione"]
},
"Rimborso": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"dataInserimento": { "type": "string", "format": "date-time" },
"dataSpesa": { "type": "string", "format": "date" },
"categoriaId": { "type": "integer" },
"categoria": { "type": "string" },
"importo": { "type": "number" },
"descrizione": { "type": "string" },
"riferimentoGiustificativo": { "type": "string", "nullable": true },
"stato": { "$ref": "#/components/schemas/StatoRichiesta" },
"dipendenteId": { "type": "integer" },
"dipendenteNome": { "type": "string" },
"dataValutazione": { "type": "string", "format": "date-time", "nullable": true },
"responsabileValutazioneId": { "type": "integer", "nullable": true },
"responsabileNome": { "type": "string", "nullable": true },
"motivazioneRifiuto": { "type": "string", "nullable": true },
"dataLiquidazione": { "type": "string", "format": "date-time", "nullable": true }
},
"required": [
"id",
"dataInserimento",
"dataSpesa",
"categoriaId",
"categoria",
"importo",
"descrizione",
"stato",
"dipendenteId",
"dipendenteNome"
]
},
"RimborsoInput": {
"type": "object",
"required": ["dataSpesa", "categoriaId", "importo", "descrizione"],
"properties": {
"dataSpesa": { "type": "string", "format": "date", "example": "2025-06-01" },
"categoriaId": { "type": "integer", "example": 1 },
"importo": { "type": "number", "minimum": 0.01, "example": 45.5 },
"descrizione": { "type": "string", "example": "Pranzo di lavoro con cliente" },
"riferimentoGiustificativo": { "type": "string", "nullable": true, "example": "FATT-2025-001" }
}
},
"RifiutaInput": {
"type": "object",
"properties": {
"motivazione": { "type": "string", "nullable": true, "example": "Manca il giustificativo" }
}
},
"StatisticaRimborso": {
"type": "object",
"properties": {
"mese": { "type": "string", "example": "2025-06" },
"categoria": { "type": "string" },
"numeroRichieste": { "type": "integer" },
"totaleRichiesto": { "type": "number" },
"totaleApprovato": { "type": "number" },
"totaleLiquidato": { "type": "number" }
},
"required": [
"mese",
"categoria",
"numeroRichieste",
"totaleRichiesto",
"totaleApprovato",
"totaleLiquidato"
]
},
"CsrfResponse": {
"type": "object",
"properties": {
"csrfToken": { "type": "string" }
},
"required": ["csrfToken"]
}
},
"responses": {
"Unauthorized": {
"description": "Autenticazione richiesta",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
},
"Forbidden": {
"description": "Operazione non consentita per il ruolo corrente",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
},
"NotFound": {
"description": "Risorsa non trovata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
},
"BadRequest": {
"description": "Richiesta non valida",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
}
}
},
"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 login/logout",
"responses": {
"200": {
"description": "Token CSRF",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/CsrfResponse" }
}
}
}
}
}
},
"/api/auth/callback/credentials": {
"post": {
"tags": ["Autenticazione"],
"summary": "Login con email e password",
"description": "Invia i dati come application/x-www-form-urlencoded. Imposta il cookie di sessione in caso di successo.",
"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", "format": "password" },
"redirect": { "type": "string", "example": "false" }
}
}
}
}
},
"responses": {
"200": { "description": "Login riuscito" },
"401": { "description": "Credenziali non valide" }
}
}
},
"/api/auth/signout": {
"post": {
"tags": ["Autenticazione"],
"summary": "Logout",
"requestBody": {
"required": true,
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"required": ["csrfToken"],
"properties": {
"csrfToken": { "type": "string" }
}
}
}
}
},
"responses": {
"200": { "description": "Logout effettuato" }
}
}
},
"/api/utenti/register": {
"post": {
"tags": ["Utenti"],
"summary": "Registrazione nuovo utente",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/RegistrazioneInput" }
}
}
},
"responses": {
"201": {
"description": "Utente registrato",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/RegistrazioneResponse" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"409": {
"description": "Email già registrata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Error" }
}
}
}
}
}
},
"/api/utenti/me": {
"get": {
"tags": ["Utenti"],
"summary": "Profilo utente corrente",
"responses": {
"200": {
"description": "Profilo utente",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Utente" }
}
}
},
"401": { "$ref": "#/components/responses/Unauthorized" }
}
}
},
"/api/utenti/dipendenti": {
"get": {
"tags": ["Utenti"],
"summary": "Elenco dipendenti",
"description": "Solo per RESPONSABILE_AMMINISTRATIVO",
"security": [{ "cookieAuth": [] }],
"responses": {
"200": {
"description": "Lista dipendenti",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/UtenteSintetico" }
}
}
}
},
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/api/categorie-spesa": {
"get": {
"tags": ["Categorie"],
"summary": "Elenco categorie di spesa",
"security": [{ "cookieAuth": [] }],
"responses": {
"200": {
"description": "Lista categorie",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/CategoriaSpesa" }
}
}
}
},
"401": { "$ref": "#/components/responses/Unauthorized" }
}
}
},
"/api/rimborsi": {
"get": {
"tags": ["Rimborsi"],
"summary": "Elenco richieste di rimborso",
"security": [{ "cookieAuth": [] }],
"parameters": [
{
"name": "stato",
"in": "query",
"schema": { "$ref": "#/components/schemas/StatoRichiesta" }
},
{
"name": "categoriaId",
"in": "query",
"schema": { "type": "integer" }
},
{
"name": "mese",
"in": "query",
"description": "Formato YYYY-MM",
"schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}$", "example": "2025-06" }
},
{
"name": "dipendenteId",
"in": "query",
"schema": { "type": "integer" }
}
],
"responses": {
"200": {
"description": "Lista rimborsi",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/Rimborso" }
}
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" }
}
},
"post": {
"tags": ["Rimborsi"],
"summary": "Crea nuova richiesta di rimborso",
"description": "Solo per DIPENDENTE",
"security": [{ "cookieAuth": [] }],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/RimborsoInput" }
}
}
},
"responses": {
"201": {
"description": "Richiesta creata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/api/rimborsi/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "integer" }
}
],
"get": {
"tags": ["Rimborsi"],
"summary": "Dettaglio richiesta di rimborso",
"security": [{ "cookieAuth": [] }],
"responses": {
"200": {
"description": "Dettaglio rimborso",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
},
"put": {
"tags": ["Rimborsi"],
"summary": "Modifica richiesta (solo stato IN_ATTESA)",
"security": [{ "cookieAuth": [] }],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/RimborsoInput" }
}
}
},
"responses": {
"200": {
"description": "Richiesta aggiornata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
},
"delete": {
"tags": ["Rimborsi"],
"summary": "Elimina richiesta (solo stato IN_ATTESA)",
"security": [{ "cookieAuth": [] }],
"responses": {
"200": {
"description": "Richiesta eliminata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Message" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
},
"/api/rimborsi/{id}/approva": {
"put": {
"tags": ["Rimborsi"],
"summary": "Approva richiesta",
"description": "Solo RESPONSABILE_AMMINISTRATIVO. La richiesta deve essere IN_ATTESA.",
"security": [{ "cookieAuth": [] }],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "integer" }
}
],
"responses": {
"200": {
"description": "Richiesta approvata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/api/rimborsi/{id}/rifiuta": {
"put": {
"tags": ["Rimborsi"],
"summary": "Rifiuta richiesta",
"description": "Solo RESPONSABILE_AMMINISTRATIVO. La richiesta deve essere IN_ATTESA.",
"security": [{ "cookieAuth": [] }],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "integer" }
}
],
"requestBody": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/RifiutaInput" }
}
}
},
"responses": {
"200": {
"description": "Richiesta rifiutata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/api/rimborsi/{id}/liquida": {
"put": {
"tags": ["Rimborsi"],
"summary": "Liquida richiesta approvata",
"description": "Solo RESPONSABILE_AMMINISTRATIVO. La richiesta deve essere APPROVATA.",
"security": [{ "cookieAuth": [] }],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": { "type": "integer" }
}
],
"responses": {
"200": {
"description": "Richiesta liquidata",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Rimborso" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
},
"/api/statistiche/rimborsi": {
"get": {
"tags": ["Statistiche"],
"summary": "Statistiche rimborsi aggregate",
"description": "Solo RESPONSABILE_AMMINISTRATIVO",
"security": [{ "cookieAuth": [] }],
"parameters": [
{
"name": "mese",
"in": "query",
"description": "Formato YYYY-MM",
"schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}$", "example": "2025-06" }
},
{
"name": "categoriaId",
"in": "query",
"schema": { "type": "integer" }
},
{
"name": "dipendenteId",
"in": "query",
"schema": { "type": "integer" }
}
],
"responses": {
"200": {
"description": "Statistiche aggregate",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": { "$ref": "#/components/schemas/StatisticaRimborso" }
}
}
}
},
"401": { "$ref": "#/components/responses/Unauthorized" },
"403": { "$ref": "#/components/responses/Forbidden" }
}
}
}
}
}