Skip to main content

koprogo_api/application/dto/
auth_dto.rs

1use serde::{Deserialize, Serialize};
2use utoipa::ToSchema;
3use validator::Validate;
4
5#[derive(Debug, Deserialize, Validate, ToSchema)]
6pub struct LoginRequest {
7    #[validate(email(message = "Email must be valid"))]
8    pub email: String,
9
10    #[validate(length(min = 6, message = "Password must be at least 6 characters"))]
11    pub password: String,
12}
13
14#[derive(Debug, Deserialize, Validate, ToSchema)]
15pub struct RegisterRequest {
16    #[validate(email(message = "Email must be valid"))]
17    pub email: String,
18
19    #[validate(length(min = 6, message = "Password must be at least 6 characters"))]
20    pub password: String,
21
22    #[validate(length(min = 2, message = "First name must be at least 2 characters"))]
23    pub first_name: String,
24
25    #[validate(length(min = 2, message = "Last name must be at least 2 characters"))]
26    pub last_name: String,
27
28    pub role: String,
29
30    pub organization_id: Option<uuid::Uuid>,
31}
32
33#[derive(Debug, Serialize, Clone)]
34pub struct LoginResponse {
35    pub token: String,
36    pub refresh_token: String,
37    pub user: UserResponse,
38}
39
40#[derive(Debug, Deserialize, ToSchema)]
41pub struct RefreshTokenRequest {
42    pub refresh_token: String,
43}
44
45#[derive(Debug, Serialize, Deserialize, Clone)]
46pub struct UserRoleSummary {
47    pub id: uuid::Uuid,
48    pub role: String,
49    pub organization_id: Option<uuid::Uuid>,
50    pub is_primary: bool,
51}
52
53#[derive(Debug, Serialize, Deserialize, Clone)]
54pub struct UserResponse {
55    pub id: uuid::Uuid,
56    pub email: String,
57    pub first_name: String,
58    pub last_name: String,
59    pub role: String,
60    pub organization_id: Option<uuid::Uuid>,
61    pub is_active: bool,
62    /// Date d'inscription. Absente jusqu'au 2026-08-30 : la page profil
63    /// lisait l'utilisateur depuis la réponse de connexion et affichait donc
64    /// « Membre depuis : - ». `UserResponse` de `user_use_cases` l'exposait
65    /// déjà, ce DTO-ci l'avait simplement oubliée.
66    pub created_at: chrono::DateTime<chrono::Utc>,
67    pub roles: Vec<UserRoleSummary>,
68    pub active_role: Option<UserRoleSummary>,
69}
70
71#[derive(Debug, Deserialize, ToSchema)]
72pub struct SwitchRoleRequest {
73    pub role_id: uuid::Uuid,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Claims {
78    pub sub: String, // user id
79    pub email: String,
80    pub role: String,
81    pub organization_id: Option<uuid::Uuid>,
82    pub role_id: Option<uuid::Uuid>,
83    pub exp: i64, // expiration timestamp
84    pub iat: i64, // issued at
85}
86
87impl Claims {
88    /// L'appelant est-il superadministrateur de la plateforme ?
89    ///
90    /// Même définition que `AuthenticatedUser::is_superadmin`, et pour la même
91    /// raison : la comparaison littérale `role == "superadmin"` était écrite à
92    /// la main en soixante-treize endroits. Une seule d'entre elles qui se
93    /// trompe de casse, ou qui survit au renommage du rôle, ouvre un accès
94    /// sans que rien ne le dise.
95    ///
96    /// C'est la faiblesse qui a produit #814 — `community-moderator` comparé à
97    /// `community.moderator`, un trait d'union contre un point — et #836, où
98    /// dix rôles sur quatorze étaient travestis en `owner` faute d'être
99    /// déclarés au même endroit.
100    pub fn is_superadmin(&self) -> bool {
101        self.role == "superadmin"
102    }
103}