Skip to main content

koprogo_api/infrastructure/web/handlers/
auth_handlers.rs

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