From fe1bcc0ff73ea173e8c633825647855981cde471 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Sun, 29 Mar 2026 23:56:11 +0200 Subject: [PATCH] Adding Swagger --- SWAGGER_GUIDE.md | 210 ++++++++++++++++++++++ app.js | 11 +- config/env/.gitignore | 9 +- package-lock.json | 219 ++++++++++++++++++++++- package.json | 3 + src/config/swagger.js | 130 ++++++++++++++ src/swagger/articles.yaml | 366 ++++++++++++++++++++++++++++++++++++++ src/swagger/clans.yaml | 242 +++++++++++++++++++++++++ src/swagger/members.yaml | 270 ++++++++++++++++++++++++++++ src/swagger/template.md | 209 ++++++++++++++++++++++ src/swagger/users.yaml | 230 ++++++++++++++++++++++++ 11 files changed, 1883 insertions(+), 16 deletions(-) create mode 100644 SWAGGER_GUIDE.md create mode 100644 src/config/swagger.js create mode 100644 src/swagger/articles.yaml create mode 100644 src/swagger/clans.yaml create mode 100644 src/swagger/members.yaml create mode 100644 src/swagger/template.md create mode 100644 src/swagger/users.yaml diff --git a/SWAGGER_GUIDE.md b/SWAGGER_GUIDE.md new file mode 100644 index 0000000..2738ad6 --- /dev/null +++ b/SWAGGER_GUIDE.md @@ -0,0 +1,210 @@ +# Guide Swagger - Documentation OpenAPI pour Adastra API + +## Accès à la documentation +Une fois le serveur lancé, accédez à : **http://localhost:3200/api-docs** + +## Exemple de documentation pour une route + +Voici un exemple de comment documenter une GET route avec des paramètres de query : + +```javascript +/** + * @swagger + * /api/v1/articles: + * get: + * summary: Récupère la liste des articles + * description: Retourne une liste paginée de tous les articles + * tags: + * - Articles + * parameters: + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * description: Numéro de page + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * description: Nombre d'articles par page + * responses: + * 200: + * description: Liste des articles récupérée avec succès + * content: + * application/json: + * schema: + * type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * id: + * type: integer + * title: + * type: string + * content: + * type: string + * total: + * type: integer + * 400: + * description: Erreur de validation + * 401: + * description: Non authentifié + * 500: + * description: Erreur serveur + */ +router.get('/', async (req, res) => { + // ... logique du contrôleur +}); +``` + +## Exemple avec POST et authentification + +```javascript +/** + * @swagger + * /api/v1/articles: + * post: + * summary: Crée un nouvel article + * description: Crée un nouvel article avec les données fournies + * tags: + * - Articles + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - title + * - content + * properties: + * title: + * type: string + * example: "Mon Article" + * content: + * type: string + * example: "Contenu de l'article" + * tags: + * type: array + * items: + * type: string + * example: ["tag1", "tag2"] + * responses: + * 201: + * description: Article créé avec succès + * content: + * application/json: + * schema: + * type: object + * properties: + * id: + * type: integer + * title: + * type: string + * content: + * type: string + * 400: + * description: Données invalides + * 401: + * description: Non authentifié + */ +router.post('/', authenticate, async (req, res) => { + // ... logique du contrôleur +}); +``` + +## Exemple avec paramètres URL + +```javascript +/** + * @swagger + * /api/v1/articles/{id}: + * get: + * summary: Récupère un article par ID + * tags: + * - Articles + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * description: ID de l'article + * responses: + * 200: + * description: Article récupéré avec succès + * 404: + * description: Article non trouvé + * 500: + * description: Erreur serveur + */ +router.get('/:id', async (req, res) => { + // ... logique du contrôleur +}); +``` + +## Types de schémas courants + +### Objets simples +```javascript +schema: { + type: object + properties: + id: { type: integer } + name: { type: string } + email: { type: string, format: email } + age: { type: integer, minimum: 0 } + isActive: { type: boolean } +} +``` + +### Arrays +```javascript +schema: { + type: array + items: + type: object + properties: + id: { type: integer } + name: { type: string } +} +``` + +### Enums +```javascript +schema: { + type: string + enum: ["pending", "approved", "rejected"] +} +``` + +## Tags disponibles +Les tags disponibles dans la config Swagger sont : +- **Articles** - Gestion des articles +- **Users** - Gestion des utilisateurs +- **Products** - Gestion des produits +- **Clans** - Gestion des clans +- **Hero Wars** - Données Hero Wars + +## Instructions générales + +1. **Placez les commentaires JSDoc directement au-dessus de la définition de la route** +2. **Utilisez le tag `@swagger`** pour indiquer que c'est de la documentation OpenAPI +3. **Commencez par le chemin d'accès** : `/api/v1/articles`, `/api/v1/articles/{id}`, etc. +4. **Spécifiez la méthode HTTP** : `get`, `post`, `put`, `delete`, `patch` +5. **Incluez toujours** : `summary`, `description`, `tags`, `responses` +6. **Pour les routes protégées**, utilisez `security: [{ bearerAuth: [] }]` ou `security: [{ apiKeyAuth: [] }]` + +## Pour plus de détails +Consultez la documentation officielle : https://swagger.io/specification/ + +--- + +**Note** : Une fois que vous aurez ajouté les commentaires Swagger à vos routes, redémarrez le serveur et accédez à `/api-docs` pour voir la documentation mise à jour. diff --git a/app.js b/app.js index bd646e6..40c6d0f 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,9 @@ var express = require('express'), const DateUtilities = require('./src/utils/dateUtilities'), exceptionHandler = require('./src/middlewares/exceptions.handler'), connectDb = require('./src/database/connectDb'), - config = require('./src/config'); + config = require('./src/config'), + swaggerUi = require('swagger-ui-express'), + swaggerSpec = require('./src/config/swagger'); // Create global app object var app = express(); @@ -57,6 +59,13 @@ require('./src/database/models/mongo'); config.initAuth(); //config.initApiKey(); +// Setup Swagger documentation +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, { + swaggerOptions: { + persistAuthorization: true, + }, +})); + // Register routes AFTER connections are established const setupRoutes = () => { app.use(require('./src/routes')); diff --git a/config/env/.gitignore b/config/env/.gitignore index f577158..c8ab88d 100644 --- a/config/env/.gitignore +++ b/config/env/.gitignore @@ -1,12 +1,5 @@ # Environment files - keep .example files only -.env -.env.local -.env.development -.env.production -.env.test +.env* # Keep example files for documentation !.env.example -!.env.local.example -!.env.development.example -!.env.production.example diff --git a/package-lock.json b/package-lock.json index 3ab2517..ebfb86f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.18.2", "express-jwt": "^8.4.1", "express-session": "^1.18.0", + "js-yaml": "^4.1.1", "jsonwebtoken": "^9.0.2", "method-override": "3.0.0", "methods": "1.1.2", @@ -34,6 +35,8 @@ "sequelize": "^6.37.3", "sequelize-auto": "^0.8.8", "slug": "^8.2.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "underscore": "^1.13.6", "uuid": "^9.0.1" }, @@ -58,6 +61,46 @@ "node": ">=0.10.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", @@ -990,6 +1033,11 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -1133,6 +1181,12 @@ "node": "*" } }, + "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 + }, "node_modules/@sendgrid/client": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz", @@ -1747,6 +1801,11 @@ "@types/ms": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.6", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", @@ -1927,8 +1986,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -2199,6 +2257,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2557,6 +2620,17 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -2908,7 +2982,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4118,10 +4191,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dependencies": { "argparse": "^2.0.1" }, @@ -4312,6 +4384,12 @@ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", @@ -4337,6 +4415,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -5037,6 +5120,12 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -6428,6 +6517,86 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.32.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.1.tgz", + "integrity": "sha512-6HQoo7+j8PA2QqP5kgAb9dl1uxUjvR0SAoL/WUp1sTEvm0F6D5npgU2OGCLwl++bIInqGlEUQ2mpuZRZYtyCzQ==", + "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==", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/tar": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", @@ -6863,6 +7032,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6899,6 +7076,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/package.json b/package.json index fa8d0fa..15b3365 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "express": "^4.18.2", "express-jwt": "^8.4.1", "express-session": "^1.18.0", + "js-yaml": "^4.1.1", "jsonwebtoken": "^9.0.2", "method-override": "3.0.0", "methods": "1.1.2", @@ -49,6 +50,8 @@ "sequelize": "^6.37.3", "sequelize-auto": "^0.8.8", "slug": "^8.2.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "underscore": "^1.13.6", "uuid": "^9.0.1" }, diff --git a/src/config/swagger.js b/src/config/swagger.js new file mode 100644 index 0000000..62d265b --- /dev/null +++ b/src/config/swagger.js @@ -0,0 +1,130 @@ +const swaggerJsdoc = require('swagger-jsdoc'); +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); + +// Fonction pour charger tous les fichiers YAML du dossier swagger +const loadSwaggerFiles = () => { + const swaggerDir = path.join(__dirname, '../swagger'); + const files = fs.readdirSync(swaggerDir).filter(file => file.endsWith('.yaml')); + + const merged = { paths: {}, components: { schemas: {} } }; + + files.forEach(file => { + try { + const content = fs.readFileSync(path.join(swaggerDir, file), 'utf8'); + const doc = yaml.load(content); + + // Fusionner les paths + if (doc.paths) { + Object.assign(merged.paths, doc.paths); + } + + // Fusionner les schemas + if (doc.components && doc.components.schemas) { + Object.assign(merged.components.schemas, doc.components.schemas); + } + } catch (error) { + console.error(`Erreur lors du chargement de ${file}:`, error); + } + }); + + return merged; +}; + +const swaggerDefinitions = loadSwaggerFiles(); + +const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'Adastra API', + version: '1.0.0', + description: 'Adastra API Documentation - Express.js API with Sequelize (MySQL) and Mongoose (MongoDB)', + contact: { + name: 'Julien Gautier', + email: 'jgautier.webdev@gmail.com', + }, + }, + servers: [ + { + url: 'http://localhost:3201', + description: 'Development server', + }, + { + url: 'http://localhost:3200', + description: 'Local development server', + }, + { + url: 'https://api.example.com', + description: 'Production server', + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + description: 'JWT Bearer token authentication', + }, + apiKeyAuth: { + type: 'apiKey', + in: 'header', + name: 'x-api-key', + description: 'API Key authentication', + }, + }, + schemas: { + Error: { + type: 'object', + properties: { + error: { + type: 'string', + }, + message: { + type: 'string', + }, + statusCode: { + type: 'integer', + }, + }, + }, + ...swaggerDefinitions.components.schemas, + }, + }, + paths: swaggerDefinitions.paths, + tags: [ + { + name: 'Articles', + description: 'Article management endpoints', + }, + { + name: 'Clans', + description: 'Clan management endpoints', + }, + { + name: 'Members', + description: 'Member management endpoints', + }, + { + name: 'Users', + description: 'User management endpoints', + }, + { + name: 'Products', + description: 'Product management endpoints', + }, + { + name: 'Hero Wars', + description: 'Hero Wars game data endpoints', + }, + ], + }, + // Les définitions sont chargées depuis les fichiers YAML + apis: [], +}; + +const swaggerSpec = swaggerJsdoc(options); + +module.exports = swaggerSpec; diff --git a/src/swagger/articles.yaml b/src/swagger/articles.yaml new file mode 100644 index 0000000..24826eb --- /dev/null +++ b/src/swagger/articles.yaml @@ -0,0 +1,366 @@ +paths: + /api/v2/articles: + get: + summary: Récupère la liste paginée des articles + description: Retourne une liste paginée de tous les articles. L'authentification est optionnelle. + tags: + - Articles + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: Numéro de page + - in: query + name: limit + schema: + type: integer + default: 20 + description: Nombre d'articles par page + - in: query + name: search + schema: + type: string + description: Terme de recherche pour les articles + responses: + '200': + description: Liste des articles récupérée avec succès + content: + application/json: + schema: + type: object + properties: + articles: + type: array + items: + $ref: '#/components/schemas/Article' + articlesCount: + type: integer + '400': + description: Erreur de validation + '500': + description: Erreur serveur + + post: + summary: Crée un nouvel article + description: Crée un nouvel article avec les données fournies. Authentification requise. + tags: + - Articles + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - article + properties: + article: + type: object + required: + - title + - description + - body + properties: + title: + type: string + example: "Titre de l'article" + description: + type: string + example: "Description courte" + body: + type: string + example: "Contenu complet de l'article" + tagList: + type: array + items: + type: string + example: ["tag1", "tag2"] + responses: + '201': + description: Article créé avec succès + content: + application/json: + schema: + type: object + properties: + article: + $ref: '#/components/schemas/Article' + '400': + description: Données invalides + '401': + description: Non authentifié + '422': + description: Erreur de validation des données + + /api/v2/articles/feed: + get: + summary: Récupère le fil d'actualité des articles + description: Retourne les articles des utilisateurs suivis par l'utilisateur authentifié + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: Numéro de page + - in: query + name: limit + schema: + type: integer + default: 20 + description: Nombre d'articles par page + responses: + '200': + description: Fil d'actualité récupéré avec succès + content: + application/json: + schema: + type: object + properties: + articles: + type: array + items: + $ref: '#/components/schemas/Article' + articlesCount: + type: integer + '401': + description: Non authentifié + '500': + description: Erreur serveur + + /api/v2/articles/{slug}: + get: + summary: Récupère un article par son slug + description: Retourne les détails complets d'un article spécifique + tags: + - Articles + parameters: + - in: path + name: slug + required: true + schema: + type: string + description: Le slug unique de l'article + responses: + '200': + description: Article récupéré avec succès + content: + application/json: + schema: + type: object + properties: + article: + $ref: '#/components/schemas/Article' + '404': + description: Article non trouvé + '500': + description: Erreur serveur + + put: + summary: Met à jour un article + description: Met à jour un article existant. Seul l'auteur peut modifier son article. Authentification requise. + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: slug + required: true + schema: + type: string + description: Le slug unique de l'article + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - article + properties: + article: + type: object + properties: + title: + type: string + description: + type: string + body: + type: string + tagList: + type: array + items: + type: string + responses: + '200': + description: Article mis à jour avec succès + content: + application/json: + schema: + type: object + properties: + article: + $ref: '#/components/schemas/Article' + '401': + description: Non authentifié + '403': + description: Vous n'êtes pas autorisé à modifier cet article + '404': + description: Article non trouvé + '422': + description: Erreur de validation des données + + delete: + summary: Supprime un article + description: Supprime un article existant. Seul l'auteur peut supprimer son article. Authentification requise. + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: slug + required: true + schema: + type: string + description: Le slug unique de l'article + responses: + '200': + description: Article supprimé avec succès + '401': + description: Non authentifié + '403': + description: Vous n'êtes pas autorisé à supprimer cet article + '404': + description: Article non trouvé + '500': + description: Erreur serveur + + /api/v2/articles/{slug}/favorite: + post: + summary: Ajoute un article aux favoris + description: Ajoute un article à la liste des favoris de l'utilisateur authentifié + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: slug + required: true + schema: + type: string + description: Le slug unique de l'article + responses: + '200': + description: Article ajouté aux favoris avec succès + content: + application/json: + schema: + type: object + properties: + article: + $ref: '#/components/schemas/Article' + '401': + description: Non authentifié + '404': + description: Article non trouvé + '500': + description: Erreur serveur + + delete: + summary: Retire un article des favoris + description: Retire un article de la liste des favoris de l'utilisateur authentifié + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: slug + required: true + schema: + type: string + description: Le slug unique de l'article + responses: + '200': + description: Article retiré des favoris avec succès + content: + application/json: + schema: + type: object + properties: + article: + $ref: '#/components/schemas/Article' + '401': + description: Non authentifié + '404': + description: Article non trouvé + '500': + description: Erreur serveur + +components: + schemas: + Article: + type: object + properties: + slug: + type: string + description: Identifiant unique de l'article (généré à partir du titre) + example: "mon-premier-article" + title: + type: string + description: Titre de l'article + example: "Mon Premier Article" + description: + type: string + description: Description courte de l'article + example: "Description courte pour le résumé" + body: + type: string + description: Contenu complet de l'article + example: "Contenu complet de l'article..." + tagList: + type: array + items: + type: string + description: Liste des tags associés à l'article + example: ["tag1", "tag2"] + author: + type: object + properties: + username: + type: string + image: + type: string + bio: + type: string + following: + type: boolean + description: Informations de l'auteur de l'article + createdAt: + type: string + format: date-time + description: Date/heure de création de l'article + example: "2024-01-15T10:30:00Z" + updatedAt: + type: string + format: date-time + description: Date/heure de la dernière mise à jour + example: "2024-01-15T10:30:00Z" + favorited: + type: boolean + description: Indique si l'article est mis en favori par l'utilisateur authentifié + favoritesCount: + type: integer + description: Nombre de fois que cet article a été ajouté aux favoris + example: 5 diff --git a/src/swagger/clans.yaml b/src/swagger/clans.yaml new file mode 100644 index 0000000..bd2caad --- /dev/null +++ b/src/swagger/clans.yaml @@ -0,0 +1,242 @@ +paths: + /api/v3/clans: + get: + summary: Récupère la liste de tous les clans + description: Retourne une liste de tous les clans. L'authentification est optionnelle. + tags: + - Clans + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: Numéro de page + - in: query + name: limit + schema: + type: integer + default: 20 + description: Nombre de clans par page + - in: query + name: search + schema: + type: string + description: Terme de recherche pour les clans + responses: + '200': + description: Liste des clans récupérée avec succès + content: + application/json: + schema: + type: object + properties: + clans: + type: array + items: + $ref: '#/components/schemas/Clan' + clansCount: + type: integer + '400': + description: Erreur de validation + '500': + description: Erreur serveur + + post: + summary: Crée un nouveau clan + description: Crée un nouveau clan avec les données fournies + tags: + - Clans + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - clan + properties: + clan: + type: object + required: + - name + properties: + name: + type: string + example: "Mon Clan" + description: + type: string + example: "Description du clan" + level: + type: integer + example: 5 + server: + type: string + example: "EU" + responses: + '201': + description: Clan créé avec succès + content: + application/json: + schema: + type: object + properties: + clan: + $ref: '#/components/schemas/Clan' + '400': + description: Données invalides + '422': + description: Erreur de validation des données + '500': + description: Erreur serveur + + /api/v3/clans/{clanId}: + get: + summary: Récupère les détails d'un clan + description: Retourne les détails complets d'un clan spécifique. Authentification requise. + tags: + - Clans + security: + - bearerAuth: [] + parameters: + - in: path + name: clanId + required: true + schema: + type: integer + description: L'ID unique du clan + responses: + '200': + description: Clan récupéré avec succès + content: + application/json: + schema: + type: object + properties: + clan: + $ref: '#/components/schemas/Clan' + '401': + description: Non authentifié + '404': + description: Clan non trouvé + '500': + description: Erreur serveur + + put: + summary: Met à jour un clan + description: Met à jour les informations d'un clan. Authentification requise. + tags: + - Clans + security: + - bearerAuth: [] + parameters: + - in: path + name: clanId + required: true + schema: + type: integer + description: L'ID unique du clan + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - clan + properties: + clan: + type: object + properties: + name: + type: string + description: + type: string + level: + type: integer + server: + type: string + responses: + '200': + description: Clan mis à jour avec succès + content: + application/json: + schema: + type: object + properties: + clan: + $ref: '#/components/schemas/Clan' + '401': + description: Non authentifié + '404': + description: Clan non trouvé + '422': + description: Erreur de validation des données + '500': + description: Erreur serveur + + delete: + summary: Supprime un clan + description: Supprime un clan. Authentification requise. + tags: + - Clans + security: + - bearerAuth: [] + parameters: + - in: path + name: clanId + required: true + schema: + type: integer + description: L'ID unique du clan + responses: + '200': + description: Clan supprimé avec succès + '401': + description: Non authentifié + '403': + description: Vous n'êtes pas autorisé à supprimer ce clan + '404': + description: Clan non trouvé + '500': + description: Erreur serveur + +components: + schemas: + Clan: + type: object + properties: + id: + type: integer + description: Identifiant unique du clan + name: + type: string + description: Nom du clan + example: "Mon Clan" + description: + type: string + description: Description du clan + level: + type: integer + description: Niveau du clan + example: 5 + server: + type: string + description: Serveur du clan + example: "EU" + membersCount: + type: integer + description: Nombre de membres du clan + example: 30 + foundedAt: + type: string + format: date-time + description: Date de création du clan + createdAt: + type: string + format: date-time + description: Date/heure de création du clan + updatedAt: + type: string + format: date-time + description: Date/heure de la dernière mise à jour diff --git a/src/swagger/members.yaml b/src/swagger/members.yaml new file mode 100644 index 0000000..8d8dac3 --- /dev/null +++ b/src/swagger/members.yaml @@ -0,0 +1,270 @@ +paths: + /api/v3/members: + get: + summary: Récupère la liste de tous les membres + description: Retourne une liste paginée de tous les membres. Authentification requise. + tags: + - Members + security: + - bearerAuth: [] + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: Numéro de page + - in: query + name: limit + schema: + type: integer + default: 20 + description: Nombre de membres par page + - in: query + name: clanId + schema: + type: integer + description: Filtrer les membres par clan + - in: query + name: search + schema: + type: string + description: Terme de recherche pour les membres + responses: + '200': + description: Liste des membres récupérée avec succès + content: + application/json: + schema: + type: object + properties: + members: + type: array + items: + $ref: '#/components/schemas/Member' + membersCount: + type: integer + '401': + description: Non authentifié + '400': + description: Erreur de validation + '500': + description: Erreur serveur + + post: + summary: Crée un nouveau membre + description: Crée un nouveau membre du clan. Authentification requise. + tags: + - Members + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - member + properties: + member: + type: object + required: + - userId + - clanId + properties: + userId: + type: integer + example: 1 + clanId: + type: integer + example: 1 + role: + type: string + enum: ["member", "officer", "leader"] + default: "member" + example: "member" + joinedAt: + type: string + format: date-time + responses: + '201': + description: Membre créé avec succès + content: + application/json: + schema: + type: object + properties: + member: + $ref: '#/components/schemas/Member' + '400': + description: Données invalides + '401': + description: Non authentifié + '422': + description: Erreur de validation des données + '500': + description: Erreur serveur + + /api/v3/members/{memberId}: + get: + summary: Récupère les détails d'un membre + description: Retourne les détails complets d'un membre spécifique. Authentification requise. + tags: + - Members + security: + - bearerAuth: [] + parameters: + - in: path + name: memberId + required: true + schema: + type: integer + description: L'ID unique du membre + responses: + '200': + description: Membre récupéré avec succès + content: + application/json: + schema: + type: object + properties: + member: + $ref: '#/components/schemas/Member' + '401': + description: Non authentifié + '404': + description: Membre non trouvé + '500': + description: Erreur serveur + + put: + summary: Met à jour un membre + description: Met à jour les informations d'un membre (rôle, date d'adhésion, etc.). Authentification requise. + tags: + - Members + security: + - bearerAuth: [] + parameters: + - in: path + name: memberId + required: true + schema: + type: integer + description: L'ID unique du membre + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - member + properties: + member: + type: object + properties: + role: + type: string + enum: ["member", "officer", "leader"] + joinedAt: + type: string + format: date-time + responses: + '200': + description: Membre mis à jour avec succès + content: + application/json: + schema: + type: object + properties: + member: + $ref: '#/components/schemas/Member' + '401': + description: Non authentifié + '404': + description: Membre non trouvé + '422': + description: Erreur de validation des données + '500': + description: Erreur serveur + + delete: + summary: Supprime un membre + description: Supprime un membre du clan. Authentification requise. + tags: + - Members + security: + - bearerAuth: [] + parameters: + - in: path + name: memberId + required: true + schema: + type: integer + description: L'ID unique du membre + responses: + '200': + description: Membre supprimé avec succès + '401': + description: Non authentifié + '403': + description: Vous n'êtes pas autorisé à supprimer ce membre + '404': + description: Membre non trouvé + '500': + description: Erreur serveur + +components: + schemas: + Member: + type: object + properties: + id: + type: integer + description: Identifiant unique du membre + userId: + type: integer + description: ID de l'utilisateur associé + user: + type: object + description: Informations de l'utilisateur + properties: + id: + type: integer + username: + type: string + email: + type: string + image: + type: string + clanId: + type: integer + description: ID du clan + clan: + type: object + description: Informations du clan + properties: + id: + type: integer + name: + type: string + level: + type: integer + role: + type: string + description: Rôle du membre dans le clan + enum: ["member", "officer", "leader"] + example: "member" + joinedAt: + type: string + format: date-time + description: Date d'adhésion au clan + createdAt: + type: string + format: date-time + description: Date/heure de création du membre + updatedAt: + type: string + format: date-time + description: Date/heure de la dernière mise à jour diff --git a/src/swagger/template.md b/src/swagger/template.md new file mode 100644 index 0000000..d3af567 --- /dev/null +++ b/src/swagger/template.md @@ -0,0 +1,209 @@ +# Template pour ajouter une nouvelle ressource Swagger + +Utilisez ce fichier comme base pour documenter les routes d'une nouvelle ressource. + +```yaml +paths: + /api/v1/articles: + get: + summary: Récupère la liste des articles + description: Retourne une liste paginée de tous les articles + tags: + - Articles + parameters: + - in: query + name: page + schema: + type: integer + default: 1 + description: Numéro de page + - in: query + name: limit + schema: + type: integer + default: 10 + description: Nombre d'éléments par page + responses: + '200': + description: Articles récupérés avec succès + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Article' + total: + type: integer + '400': + description: Erreur de validation + '500': + description: Erreur serveur + + post: + summary: Crée un nouvel article + description: Crée un nouvel article avec les données fournies + tags: + - Articles + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - title + - content + properties: + title: + type: string + example: "Titre de l'article" + content: + type: string + example: "Contenu de l'article" + tags: + type: array + items: + type: string + responses: + '201': + description: Article créé avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/Article' + '400': + description: Données invalides + '401': + description: Non authentifié + + /api/v1/articles/{id}: + get: + summary: Récupère un article par ID + tags: + - Articles + parameters: + - in: path + name: id + required: true + schema: + type: integer + description: ID de l'article + responses: + '200': + description: Article récupéré avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/Article' + '404': + description: Article non trouvé + + put: + summary: Met à jour un article + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: id + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + title: + type: string + content: + type: string + tags: + type: array + items: + type: string + responses: + '200': + description: Article mis à jour avec succès + content: + application/json: + schema: + $ref: '#/components/schemas/Article' + '404': + description: Article non trouvé + + delete: + summary: Supprime un article + tags: + - Articles + security: + - bearerAuth: [] + parameters: + - in: path + name: id + required: true + schema: + type: integer + responses: + '200': + description: Article supprimé avec succès + '404': + description: Article non trouvé + +components: + schemas: + Article: + type: object + properties: + id: + type: integer + title: + type: string + content: + type: string + author: + type: string + tags: + type: array + items: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time +``` + +## Instructions + +1. **Créer un nouveau fichier YAML** dans `src/swagger/` : + ```bash + touch src/swagger/articles.yaml + cp src/swagger/template.yaml src/swagger/articles.yaml + ``` + +2. **Modifier le fichier** pour adapter les routes à votre ressource + +3. **Redémarrer le serveur** : + ```bash + npm run local + ``` + +4. **Consulter les docs** : http://localhost:3200/api-docs + +## Conventions + +- **Un fichier YAML par ressource** (users.yaml, articles.yaml, products.yaml, etc.) +- **Réutiliser les schémas** : utilisez `$ref: '#/components/schemas/User'` au lieu de redéfinir +- **Utiliser les tags** pour regrouper les routes (Articles, Users, Products, etc.) +- **Utiliser les chemins corrects** : `/api/v1/...`, `/api/v2/...`, etc. +- **Documenter les réponses d'erreur** : 400, 401, 404, 500 diff --git a/src/swagger/users.yaml b/src/swagger/users.yaml new file mode 100644 index 0000000..e26ff78 --- /dev/null +++ b/src/swagger/users.yaml @@ -0,0 +1,230 @@ +paths: + /api/v1/user: + get: + summary: Récupère le profil de l'utilisateur connecté + description: Retourne les informations du profil de l'utilisateur authentifié + tags: + - Users + security: + - bearerAuth: [] + responses: + '200': + description: Profil utilisateur récupéré avec succès + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + '401': + description: Authentification requise + '500': + description: Erreur serveur + + put: + summary: Met à jour le profil de l'utilisateur connecté + description: Permet à l'utilisateur authentifié de mettre à jour les informations de son profil + tags: + - Users + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + user: + type: object + properties: + username: + type: string + email: + type: string + firstname: + type: string + lastname: + type: string + phone: + type: string + licence: + type: string + poids: + type: number + image: + type: string + bg_image: + type: string + password: + type: string + role: + type: string + responses: + '200': + description: Profil utilisateur mis à jour avec succès + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + '401': + description: Authentification requise + '500': + description: Erreur serveur + + /api/v1/users/login: + post: + summary: Authentifie un utilisateur + description: Authentifie un utilisateur avec email et mot de passe, retourne un JWT + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user + properties: + user: + type: object + required: + - email + - password + properties: + email: + type: string + format: email + password: + type: string + responses: + '200': + description: Authentification réussie, JWT retourné + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + '422': + description: Email ou mot de passe manquant/incorrect + '500': + description: Erreur serveur + + /api/v1/authenticate: + get: + summary: Authentification par clé API + description: Authentifie un utilisateur/application avec une clé API dans les headers + tags: + - Users + security: + - apiKeyAuth: [] + responses: + '200': + description: Authentification réussie + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + application: + type: object + '401': + description: Clé API invalide ou non autorisée + '500': + description: Erreur serveur + + /api/v1/users: + post: + summary: Crée un nouvel utilisateur + description: Crée un nouvel utilisateur avec les données fournies + tags: + - Users + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user + properties: + user: + type: object + required: + - email + - firstname + - lastname + - password + properties: + username: + type: string + email: + type: string + format: email + firstname: + type: string + lastname: + type: string + phone: + type: string + licence: + type: string + poids: + type: number + password: + type: string + responses: + '200': + description: Utilisateur créé avec succès + content: + application/json: + schema: + type: object + properties: + user: + $ref: '#/components/schemas/User' + '422': + description: Données invalides + '500': + description: Erreur serveur + +components: + schemas: + User: + type: object + properties: + id: + type: string + username: + type: string + email: + type: string + format: email + firstname: + type: string + lastname: + type: string + phone: + type: string + licence: + type: string + poids: + type: number + image: + type: string + bg_image: + type: string + role: + type: string + token: + type: string + description: JWT Bearer token pour les requêtes authentifiées