`make openapi` écrit apps/backend/openapi.json, et un test compare le fichier versionné au schéma généré. Une route qui change son contrat public le montre donc dans la diff d'une pull request, et une PR qui oublie de régénérer échoue en CI : le fichier vit sous apps/backend, que le filtre de chemins de backend.yml couvre. Le schéma exporté ne lit ni le .env du poste ni les variables APP_ : tout ce qui l'atteint est posé par settings_du_contrat(), sans quoi le fichier changerait de machine en machine. main() réclamait un mot de passe avant de lire la commande. Le branchement passe devant, sinon l'export serait resté bloqué sur getpass.
1151 lines
33 KiB
JSON
1151 lines
33 KiB
JSON
{
|
|
"openapi": "3.1.0",
|
|
"info": {
|
|
"title": "EnerVision API",
|
|
"summary": "Collecte, analyse et restitution de séries temporelles énergétiques.",
|
|
"description": "\nToutes les routes sont préfixées par `/api/v1`.\n\n**Authentification.** Le jeton d'accès se présente dans l'en-tête `Authorization: Bearer ...`.\nLe jeton de rafraîchissement est un cookie `HttpOnly` que le code client ne voit jamais : il\nsuffit d'émettre les requêtes avec les identifiants de session. `POST /auth/refresh` rend un\nnouveau jeton d'accès et fait tourner le cookie.\n\n**Rôles.** `lecteur`, puis `operateur`, puis `admin`. Chaque rôle couvre les droits du\nprécédent.\n\n**Erreurs.** Le corps porte toujours une clé `detail`. Un `403` dont le `detail` vaut\n`password_change_required` n'est pas un refus de droits : il exige le changement du mot de passe\nprovisoire avant toute autre action.\n\nLe parcours de session complet est décrit dans\n`docs/architecture/31-contrat-authentification.md`.\n",
|
|
"version": "0.1.0"
|
|
},
|
|
"paths": {
|
|
"/api/v1/health/live": {
|
|
"get": {
|
|
"tags": [
|
|
"health"
|
|
],
|
|
"summary": "Sonde de vivacité",
|
|
"operationId": "liveness_api_v1_health_live_get",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/LivenessStatus"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/health/ready": {
|
|
"get": {
|
|
"tags": [
|
|
"health"
|
|
],
|
|
"summary": "Sonde de disponibilité",
|
|
"operationId": "readiness_api_v1_health_ready_get",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ReadinessStatus"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"503": {
|
|
"description": "Base injoignable, ou extension TimescaleDB absente de la base.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/auth/login": {
|
|
"post": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Ouvre une session",
|
|
"operationId": "login_api_v1_auth_login_post",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/LoginRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TokenResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"422": {
|
|
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ValidationErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Identifiants faux, compte inconnu ou compte désactivé. Le message est le même dans les trois cas, et n'apprend donc rien sur l'existence du compte.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"429": {
|
|
"description": "Trop de tentatives sur cette fenêtre glissante.",
|
|
"headers": {
|
|
"Retry-After": {
|
|
"description": "Secondes à attendre avant une nouvelle tentative.",
|
|
"schema": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
},
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/auth/refresh": {
|
|
"post": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Fait tourner la session",
|
|
"operationId": "refresh_api_v1_auth_refresh_post",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TokenResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Cookie absent, session expirée, révoquée, ou jeton déjà tourné. Dans ce dernier cas toute la famille de sessions est révoquée et le cookie est effacé avec la réponse.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Cookie de rafraîchissement": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/auth/logout": {
|
|
"post": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Ferme la session courante",
|
|
"operationId": "logout_api_v1_auth_logout_post",
|
|
"responses": {
|
|
"204": {
|
|
"description": "Successful Response"
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Cookie de rafraîchissement": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/auth/logout-all": {
|
|
"post": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Ferme toutes les sessions du compte",
|
|
"operationId": "logout_all_api_v1_auth_logout_all_post",
|
|
"responses": {
|
|
"204": {
|
|
"description": "Successful Response"
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/auth/me": {
|
|
"get": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Décrit le compte connecté",
|
|
"operationId": "me_api_v1_auth_me_get",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PrincipalResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/auth/password": {
|
|
"post": {
|
|
"tags": [
|
|
"auth"
|
|
],
|
|
"summary": "Change son propre mot de passe",
|
|
"operationId": "change_password_api_v1_auth_password_post",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/PasswordChangeRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TokenResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"422": {
|
|
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ValidationErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton d'accès invalide, ou mot de passe courant faux.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/users": {
|
|
"get": {
|
|
"tags": [
|
|
"users"
|
|
],
|
|
"summary": "Liste les comptes",
|
|
"operationId": "list_users_api_v1_users_get",
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"items": {
|
|
"$ref": "#/components/schemas/UserResponse"
|
|
},
|
|
"type": "array",
|
|
"title": "Response List Users Api V1 Users Get"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
]
|
|
},
|
|
"post": {
|
|
"tags": [
|
|
"users"
|
|
],
|
|
"summary": "Crée un compte avec un mot de passe provisoire",
|
|
"operationId": "create_user_api_v1_users_post",
|
|
"requestBody": {
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UserCreateRequest"
|
|
}
|
|
}
|
|
},
|
|
"required": true
|
|
},
|
|
"responses": {
|
|
"201": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TemporaryPasswordResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"422": {
|
|
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ValidationErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"409": {
|
|
"description": "Adresse déjà portée par un autre compte.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"/api/v1/users/{user_id}": {
|
|
"patch": {
|
|
"tags": [
|
|
"users"
|
|
],
|
|
"summary": "Change le rôle ou l'activation",
|
|
"operationId": "update_user_api_v1_users__user_id__patch",
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "user_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"title": "User Id"
|
|
}
|
|
}
|
|
],
|
|
"requestBody": {
|
|
"required": true,
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UserUpdateRequest"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/UserResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"422": {
|
|
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ValidationErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Aucun compte ne porte cet identifiant.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"400": {
|
|
"description": "Corps vide, aucune modification demandée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"409": {
|
|
"description": "L'opération laisserait la plateforme sans administrateur actif, qu'il s'agisse de rétrograder le dernier ou de le désactiver.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"/api/v1/users/{user_id}/password-reset": {
|
|
"post": {
|
|
"tags": [
|
|
"users"
|
|
],
|
|
"summary": "Réinitialise le mot de passe et ferme les sessions",
|
|
"operationId": "reset_password_api_v1_users__user_id__password_reset_post",
|
|
"security": [
|
|
{
|
|
"Jeton d'accès": []
|
|
}
|
|
],
|
|
"parameters": [
|
|
{
|
|
"name": "user_id",
|
|
"in": "path",
|
|
"required": true,
|
|
"schema": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"title": "User Id"
|
|
}
|
|
}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Successful Response",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/TemporaryPasswordResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"500": {
|
|
"description": "Erreur interne. `correlation` identifie la trace côté serveur, qui n'est pas renvoyée au client.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/InternalErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"401": {
|
|
"description": "Jeton absent, illisible, périmé, ou rendu caduc par un changement de rôle ou une désactivation. L'en-tête `WWW-Authenticate` porte la cause dans `error=`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"403": {
|
|
"description": "Droits insuffisants, ou mot de passe provisoire à changer quand `detail` vaut `password_change_required`.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"422": {
|
|
"description": "Corps invalide. Le détail nomme le champ fautif et le type d'erreur, jamais la valeur envoyée.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ValidationErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"404": {
|
|
"description": "Aucun compte ne porte cet identifiant.",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"$ref": "#/components/schemas/ErrorResponse"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"components": {
|
|
"schemas": {
|
|
"AccountKind": {
|
|
"type": "string",
|
|
"enum": [
|
|
"human",
|
|
"service"
|
|
],
|
|
"title": "AccountKind"
|
|
},
|
|
"ErrorResponse": {
|
|
"properties": {
|
|
"detail": {
|
|
"type": "string",
|
|
"title": "Detail"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"detail"
|
|
],
|
|
"title": "ErrorResponse"
|
|
},
|
|
"FieldError": {
|
|
"properties": {
|
|
"champ": {
|
|
"type": "string",
|
|
"title": "Champ"
|
|
},
|
|
"type": {
|
|
"type": "string",
|
|
"title": "Type"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"champ",
|
|
"type"
|
|
],
|
|
"title": "FieldError"
|
|
},
|
|
"InternalErrorResponse": {
|
|
"properties": {
|
|
"detail": {
|
|
"type": "string",
|
|
"title": "Detail"
|
|
},
|
|
"correlation": {
|
|
"type": "string",
|
|
"title": "Correlation"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"detail",
|
|
"correlation"
|
|
],
|
|
"title": "InternalErrorResponse"
|
|
},
|
|
"LivenessStatus": {
|
|
"properties": {
|
|
"status": {
|
|
"type": "string",
|
|
"const": "ok",
|
|
"title": "Status"
|
|
},
|
|
"service": {
|
|
"type": "string",
|
|
"title": "Service"
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"title": "Version"
|
|
},
|
|
"environment": {
|
|
"type": "string",
|
|
"title": "Environment"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"status",
|
|
"service",
|
|
"version",
|
|
"environment"
|
|
],
|
|
"title": "LivenessStatus"
|
|
},
|
|
"LoginRequest": {
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"title": "Email"
|
|
},
|
|
"password": {
|
|
"type": "string",
|
|
"maxLength": 128,
|
|
"minLength": 1,
|
|
"title": "Password"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"email",
|
|
"password"
|
|
],
|
|
"title": "LoginRequest"
|
|
},
|
|
"PasswordChangeRequest": {
|
|
"properties": {
|
|
"current_password": {
|
|
"type": "string",
|
|
"maxLength": 128,
|
|
"minLength": 1,
|
|
"title": "Current Password"
|
|
},
|
|
"new_password": {
|
|
"type": "string",
|
|
"maxLength": 128,
|
|
"minLength": 12,
|
|
"title": "New Password"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"current_password",
|
|
"new_password"
|
|
],
|
|
"title": "PasswordChangeRequest"
|
|
},
|
|
"PrincipalResponse": {
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"title": "Id"
|
|
},
|
|
"email": {
|
|
"type": "string",
|
|
"title": "Email"
|
|
},
|
|
"role": {
|
|
"$ref": "#/components/schemas/Role"
|
|
},
|
|
"kind": {
|
|
"$ref": "#/components/schemas/AccountKind"
|
|
},
|
|
"must_change_password": {
|
|
"type": "boolean",
|
|
"title": "Must Change Password"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"id",
|
|
"email",
|
|
"role",
|
|
"kind",
|
|
"must_change_password"
|
|
],
|
|
"title": "PrincipalResponse"
|
|
},
|
|
"ReadinessStatus": {
|
|
"properties": {
|
|
"status": {
|
|
"type": "string",
|
|
"const": "ready",
|
|
"title": "Status"
|
|
},
|
|
"database": {
|
|
"type": "string",
|
|
"const": "reachable",
|
|
"title": "Database"
|
|
},
|
|
"timescaledb": {
|
|
"type": "string",
|
|
"const": "loaded",
|
|
"title": "Timescaledb"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"status",
|
|
"database",
|
|
"timescaledb"
|
|
],
|
|
"title": "ReadinessStatus"
|
|
},
|
|
"Role": {
|
|
"type": "string",
|
|
"enum": [
|
|
"lecteur",
|
|
"operateur",
|
|
"admin"
|
|
],
|
|
"title": "Role"
|
|
},
|
|
"TemporaryPasswordResponse": {
|
|
"properties": {
|
|
"user": {
|
|
"$ref": "#/components/schemas/UserResponse"
|
|
},
|
|
"temporary_password": {
|
|
"type": "string",
|
|
"title": "Temporary Password"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"user",
|
|
"temporary_password"
|
|
],
|
|
"title": "TemporaryPasswordResponse"
|
|
},
|
|
"TokenResponse": {
|
|
"properties": {
|
|
"access_token": {
|
|
"type": "string",
|
|
"title": "Access Token"
|
|
},
|
|
"token_type": {
|
|
"type": "string",
|
|
"const": "bearer",
|
|
"title": "Token Type",
|
|
"default": "bearer"
|
|
},
|
|
"expires_in": {
|
|
"type": "integer",
|
|
"title": "Expires In"
|
|
},
|
|
"principal": {
|
|
"$ref": "#/components/schemas/PrincipalResponse"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"access_token",
|
|
"expires_in",
|
|
"principal"
|
|
],
|
|
"title": "TokenResponse"
|
|
},
|
|
"UserCreateRequest": {
|
|
"properties": {
|
|
"email": {
|
|
"type": "string",
|
|
"format": "email",
|
|
"title": "Email"
|
|
},
|
|
"role": {
|
|
"$ref": "#/components/schemas/Role"
|
|
},
|
|
"full_name": {
|
|
"anyOf": [
|
|
{
|
|
"type": "string",
|
|
"maxLength": 200
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Full Name"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"email",
|
|
"role"
|
|
],
|
|
"title": "UserCreateRequest"
|
|
},
|
|
"UserResponse": {
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"format": "uuid",
|
|
"title": "Id"
|
|
},
|
|
"email": {
|
|
"type": "string",
|
|
"title": "Email"
|
|
},
|
|
"role": {
|
|
"$ref": "#/components/schemas/Role"
|
|
},
|
|
"kind": {
|
|
"$ref": "#/components/schemas/AccountKind"
|
|
},
|
|
"is_active": {
|
|
"type": "boolean",
|
|
"title": "Is Active"
|
|
},
|
|
"must_change_password": {
|
|
"type": "boolean",
|
|
"title": "Must Change Password"
|
|
},
|
|
"full_name": {
|
|
"anyOf": [
|
|
{
|
|
"type": "string"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Full Name"
|
|
},
|
|
"last_login_at": {
|
|
"anyOf": [
|
|
{
|
|
"type": "string",
|
|
"format": "date-time"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Last Login At"
|
|
},
|
|
"created_at": {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"title": "Created At"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"id",
|
|
"email",
|
|
"role",
|
|
"kind",
|
|
"is_active",
|
|
"must_change_password",
|
|
"full_name",
|
|
"last_login_at",
|
|
"created_at"
|
|
],
|
|
"title": "UserResponse"
|
|
},
|
|
"UserUpdateRequest": {
|
|
"properties": {
|
|
"role": {
|
|
"anyOf": [
|
|
{
|
|
"$ref": "#/components/schemas/Role"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
]
|
|
},
|
|
"is_active": {
|
|
"anyOf": [
|
|
{
|
|
"type": "boolean"
|
|
},
|
|
{
|
|
"type": "null"
|
|
}
|
|
],
|
|
"title": "Is Active"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"title": "UserUpdateRequest"
|
|
},
|
|
"ValidationErrorResponse": {
|
|
"properties": {
|
|
"detail": {
|
|
"items": {
|
|
"$ref": "#/components/schemas/FieldError"
|
|
},
|
|
"type": "array",
|
|
"title": "Detail"
|
|
}
|
|
},
|
|
"type": "object",
|
|
"required": [
|
|
"detail"
|
|
],
|
|
"title": "ValidationErrorResponse"
|
|
}
|
|
},
|
|
"securitySchemes": {
|
|
"Cookie de rafraîchissement": {
|
|
"type": "apiKey",
|
|
"description": "Cookie `HttpOnly` posé par `/auth/login` et tourné par `/auth/refresh`. Il prend le préfixe `__Secure-` dès que l'API tourne derrière TLS, et n'est émis que vers `/api/v1/auth`.",
|
|
"in": "cookie",
|
|
"name": "ev_refresh"
|
|
},
|
|
"Jeton d'accès": {
|
|
"type": "http",
|
|
"scheme": "bearer"
|
|
}
|
|
}
|
|
},
|
|
"tags": [
|
|
{
|
|
"name": "health",
|
|
"description": "Sondes d'infrastructure, publiques. `live` prouve que le processus répond, `ready` que la base répond et que l'extension TimescaleDB est chargée."
|
|
},
|
|
{
|
|
"name": "auth",
|
|
"description": "Ouverture, rotation et fermeture de session, et changement de son propre mot de passe."
|
|
},
|
|
{
|
|
"name": "users",
|
|
"description": "Administration des comptes. Réservé au rôle `admin`."
|
|
}
|
|
]
|
|
}
|