koprogo_api/infrastructure/web/handlers/
unit_handlers.rs

1use crate::application::dto::{CreateUnitDto, PageRequest, PageResponse};
2use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
3use crate::infrastructure::web::{AppState, AuthenticatedUser};
4use actix_web::{get, post, put, web, HttpResponse, Responder};
5use uuid::Uuid;
6use validator::Validate;
7
8#[post("/units")]
9pub async fn create_unit(
10    state: web::Data<AppState>,
11    user: AuthenticatedUser, // JWT-extracted user info (SECURE!)
12    mut dto: web::Json<CreateUnitDto>,
13) -> impl Responder {
14    // Override the organization_id from DTO with the one from JWT token
15    // This prevents users from creating units in other organizations
16    let organization_id = match user.require_organization() {
17        Ok(org_id) => org_id,
18        Err(e) => {
19            return HttpResponse::Unauthorized().json(serde_json::json!({
20                "error": e.to_string()
21            }))
22        }
23    };
24    dto.organization_id = organization_id.to_string();
25
26    if let Err(errors) = dto.validate() {
27        return HttpResponse::BadRequest().json(serde_json::json!({
28            "error": "Validation failed",
29            "details": errors.to_string()
30        }));
31    }
32
33    match state.unit_use_cases.create_unit(dto.into_inner()).await {
34        Ok(unit) => {
35            // Audit log: successful unit creation
36            AuditLogEntry::new(
37                AuditEventType::UnitCreated,
38                Some(user.user_id),
39                Some(organization_id),
40            )
41            .with_resource("Unit", Uuid::parse_str(&unit.id).unwrap())
42            .log();
43
44            HttpResponse::Created().json(unit)
45        }
46        Err(err) => {
47            // Audit log: failed unit creation
48            AuditLogEntry::new(
49                AuditEventType::UnitCreated,
50                Some(user.user_id),
51                Some(organization_id),
52            )
53            .with_error(err.clone())
54            .log();
55
56            HttpResponse::BadRequest().json(serde_json::json!({
57                "error": err
58            }))
59        }
60    }
61}
62
63#[get("/units/{id}")]
64pub async fn get_unit(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
65    match state.unit_use_cases.get_unit(*id).await {
66        Ok(Some(unit)) => HttpResponse::Ok().json(unit),
67        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
68            "error": "Unit not found"
69        })),
70        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
71            "error": err
72        })),
73    }
74}
75
76#[get("/units")]
77pub async fn list_units(
78    state: web::Data<AppState>,
79    user: AuthenticatedUser,
80    page_request: web::Query<PageRequest>,
81) -> impl Responder {
82    let organization_id = user.organization_id;
83
84    match state
85        .unit_use_cases
86        .list_units_paginated(&page_request, organization_id)
87        .await
88    {
89        Ok((units, total)) => {
90            let response =
91                PageResponse::new(units, page_request.page, page_request.per_page, total);
92            HttpResponse::Ok().json(response)
93        }
94        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
95            "error": err
96        })),
97    }
98}
99
100#[get("/buildings/{building_id}/units")]
101pub async fn list_units_by_building(
102    state: web::Data<AppState>,
103    building_id: web::Path<Uuid>,
104) -> impl Responder {
105    match state
106        .unit_use_cases
107        .list_units_by_building(*building_id)
108        .await
109    {
110        Ok(units) => HttpResponse::Ok().json(units),
111        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
112            "error": err
113        })),
114    }
115}
116
117#[put("/units/{unit_id}/assign-owner/{owner_id}")]
118pub async fn assign_owner(
119    state: web::Data<AppState>,
120    user: AuthenticatedUser,
121    path: web::Path<(Uuid, Uuid)>,
122) -> impl Responder {
123    let (unit_id, owner_id) = path.into_inner();
124
125    match state.unit_use_cases.assign_owner(unit_id, owner_id).await {
126        Ok(unit) => {
127            // Audit log: successful unit assignment
128            AuditLogEntry::new(
129                AuditEventType::UnitAssignedToOwner,
130                Some(user.user_id),
131                user.organization_id,
132            )
133            .with_resource("Unit", unit_id)
134            .log();
135
136            HttpResponse::Ok().json(unit)
137        }
138        Err(err) => {
139            // Audit log: failed unit assignment
140            AuditLogEntry::new(
141                AuditEventType::UnitAssignedToOwner,
142                Some(user.user_id),
143                user.organization_id,
144            )
145            .with_resource("Unit", unit_id)
146            .with_error(err.clone())
147            .log();
148
149            HttpResponse::BadRequest().json(serde_json::json!({
150                "error": err
151            }))
152        }
153    }
154}