koprogo_api/infrastructure/web/handlers/
auth_handlers.rs

1use crate::application::dto::{
2    LoginRequest, RefreshTokenRequest, RegisterRequest, SwitchRoleRequest,
3};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder};
6use validator::Validate;
7
8#[utoipa::path(
9    post,
10    path = "/auth/login",
11    tag = "Auth",
12    summary = "Login",
13    request_body = LoginRequest,
14    responses(
15        (status = 201, description = "Resource created successfully"),
16        (status = 400, description = "Bad Request"),
17        (status = 404, description = "Not Found"),
18        (status = 500, description = "Internal Server Error"),
19    ),
20)]
21#[post("/auth/login")]
22pub async fn login(data: web::Data<AppState>, request: web::Json<LoginRequest>) -> impl Responder {
23    // Validate request
24    if let Err(errors) = request.validate() {
25        return HttpResponse::BadRequest().json(serde_json::json!({
26            "error": "Validation failed",
27            "details": errors.to_string()
28        }));
29    }
30
31    match data.auth_use_cases.login(request.into_inner()).await {
32        Ok(response) => HttpResponse::Ok().json(response),
33        Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({
34            "error": e
35        })),
36    }
37}
38
39#[utoipa::path(
40    post,
41    path = "/auth/register",
42    tag = "Auth",
43    summary = "Register",
44    request_body = RegisterRequest,
45    responses(
46        (status = 201, description = "Resource created successfully"),
47        (status = 400, description = "Bad Request"),
48        (status = 404, description = "Not Found"),
49        (status = 500, description = "Internal Server Error"),
50    ),
51)]
52#[post("/auth/register")]
53pub async fn register(
54    data: web::Data<AppState>,
55    request: web::Json<RegisterRequest>,
56) -> impl Responder {
57    // Validate request
58    if let Err(errors) = request.validate() {
59        return HttpResponse::BadRequest().json(serde_json::json!({
60            "error": "Validation failed",
61            "details": errors.to_string()
62        }));
63    }
64
65    match data.auth_use_cases.register(request.into_inner()).await {
66        Ok(response) => HttpResponse::Created().json(response),
67        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({
68            "error": e
69        })),
70    }
71}
72
73#[utoipa::path(
74    get,
75    path = "/auth/me",
76    tag = "Auth",
77    summary = "Get Current User",
78    responses(
79        (status = 200, description = "Success"),
80        (status = 400, description = "Bad Request"),
81        (status = 404, description = "Not Found"),
82        (status = 500, description = "Internal Server Error"),
83    ),
84)]
85#[get("/auth/me")]
86pub async fn get_current_user(data: web::Data<AppState>, req: HttpRequest) -> impl Responder {
87    // Extract Authorization header
88    let auth_header = match req.headers().get("Authorization") {
89        Some(header) => match header.to_str() {
90            Ok(s) => s,
91            Err(_) => {
92                return HttpResponse::BadRequest().json(serde_json::json!({
93                    "error": "Invalid authorization header"
94                }))
95            }
96        },
97        None => {
98            return HttpResponse::Unauthorized().json(serde_json::json!({
99                "error": "Missing authorization header"
100            }))
101        }
102    };
103
104    let token = auth_header.trim_start_matches("Bearer ").trim();
105
106    match data.auth_use_cases.verify_token(token) {
107        Ok(claims) => match uuid::Uuid::parse_str(&claims.sub) {
108            Ok(user_id) => match data.auth_use_cases.get_user_by_id(user_id).await {
109                Ok(user) => HttpResponse::Ok().json(user),
110                Err(e) => HttpResponse::NotFound().json(serde_json::json!({
111                    "error": e
112                })),
113            },
114            Err(e) => HttpResponse::BadRequest().json(serde_json::json!({
115                "error": format!("Invalid user ID: {}", e)
116            })),
117        },
118        Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({
119            "error": e
120        })),
121    }
122}
123
124#[utoipa::path(
125    post,
126    path = "/auth/refresh",
127    tag = "Auth",
128    summary = "Refresh Token",
129    request_body = RefreshTokenRequest,
130    responses(
131        (status = 201, description = "Resource created successfully"),
132        (status = 400, description = "Bad Request"),
133        (status = 404, description = "Not Found"),
134        (status = 500, description = "Internal Server Error"),
135    ),
136)]
137#[post("/auth/refresh")]
138pub async fn refresh_token(
139    data: web::Data<AppState>,
140    request: web::Json<RefreshTokenRequest>,
141) -> impl Responder {
142    match data
143        .auth_use_cases
144        .refresh_token(request.into_inner())
145        .await
146    {
147        Ok(response) => HttpResponse::Ok().json(response),
148        Err(e) => HttpResponse::Unauthorized().json(serde_json::json!({
149            "error": e
150        })),
151    }
152}
153
154#[utoipa::path(
155    post,
156    path = "/auth/switch-role",
157    tag = "Auth",
158    summary = "Switch Role",
159    request_body = SwitchRoleRequest,
160    responses(
161        (status = 201, description = "Resource created successfully"),
162        (status = 400, description = "Bad Request"),
163        (status = 404, description = "Not Found"),
164        (status = 500, description = "Internal Server Error"),
165    ),
166)]
167#[post("/auth/switch-role")]
168pub async fn switch_role(
169    data: web::Data<AppState>,
170    user: AuthenticatedUser,
171    request: web::Json<SwitchRoleRequest>,
172) -> impl Responder {
173    let payload = request.into_inner();
174
175    match data
176        .auth_use_cases
177        .switch_active_role(user.user_id, payload.role_id)
178        .await
179    {
180        Ok(response) => HttpResponse::Ok().json(response),
181        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({ "error": e })),
182    }
183}