koprogo_api/infrastructure/web/handlers/
building_handlers.rs

1use crate::application::dto::{CreateBuildingDto, PageRequest, PageResponse, UpdateBuildingDto};
2use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
3use crate::infrastructure::web::{AppState, AuthenticatedUser};
4use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
5use uuid::Uuid;
6use validator::Validate;
7
8#[post("/buildings")]
9pub async fn create_building(
10    state: web::Data<AppState>,
11    user: AuthenticatedUser, // JWT-extracted user info (SECURE!)
12    mut dto: web::Json<CreateBuildingDto>,
13) -> impl Responder {
14    // Override the organization_id from DTO with the one from JWT token
15    // This prevents users from creating buildings 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
34        .building_use_cases
35        .create_building(dto.into_inner())
36        .await
37    {
38        Ok(building) => {
39            // Audit log: successful building creation
40            AuditLogEntry::new(
41                AuditEventType::BuildingCreated,
42                Some(user.user_id),
43                Some(organization_id),
44            )
45            .with_resource("Building", Uuid::parse_str(&building.id).unwrap())
46            .log();
47
48            HttpResponse::Created().json(building)
49        }
50        Err(err) => {
51            // Audit log: failed building creation
52            AuditLogEntry::new(
53                AuditEventType::BuildingCreated,
54                Some(user.user_id),
55                Some(organization_id),
56            )
57            .with_error(err.clone())
58            .log();
59
60            HttpResponse::BadRequest().json(serde_json::json!({
61                "error": err
62            }))
63        }
64    }
65}
66
67#[get("/buildings")]
68pub async fn list_buildings(
69    state: web::Data<AppState>,
70    user: AuthenticatedUser,
71    page_request: web::Query<PageRequest>,
72) -> impl Responder {
73    // Extract organization_id from authenticated user (secure!)
74    let organization_id = user.organization_id;
75
76    match state
77        .building_use_cases
78        .list_buildings_paginated(&page_request, organization_id)
79        .await
80    {
81        Ok((buildings, total)) => {
82            let response =
83                PageResponse::new(buildings, page_request.page, page_request.per_page, total);
84            HttpResponse::Ok().json(response)
85        }
86        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
87            "error": err
88        })),
89    }
90}
91
92#[get("/buildings/{id}")]
93pub async fn get_building(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
94    match state.building_use_cases.get_building(*id).await {
95        Ok(Some(building)) => HttpResponse::Ok().json(building),
96        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
97            "error": "Building not found"
98        })),
99        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
100            "error": err
101        })),
102    }
103}
104
105#[put("/buildings/{id}")]
106pub async fn update_building(
107    state: web::Data<AppState>,
108    user: AuthenticatedUser,
109    id: web::Path<Uuid>,
110    dto: web::Json<UpdateBuildingDto>,
111) -> impl Responder {
112    if let Err(errors) = dto.validate() {
113        return HttpResponse::BadRequest().json(serde_json::json!({
114            "error": "Validation failed",
115            "details": errors.to_string()
116        }));
117    }
118
119    match state
120        .building_use_cases
121        .update_building(*id, dto.into_inner())
122        .await
123    {
124        Ok(building) => {
125            // Audit log: successful building update
126            AuditLogEntry::new(
127                AuditEventType::BuildingUpdated,
128                Some(user.user_id),
129                user.organization_id,
130            )
131            .with_resource("Building", *id)
132            .log();
133
134            HttpResponse::Ok().json(building)
135        }
136        Err(err) => {
137            // Audit log: failed building update
138            AuditLogEntry::new(
139                AuditEventType::BuildingUpdated,
140                Some(user.user_id),
141                user.organization_id,
142            )
143            .with_resource("Building", *id)
144            .with_error(err.clone())
145            .log();
146
147            HttpResponse::BadRequest().json(serde_json::json!({
148                "error": err
149            }))
150        }
151    }
152}
153
154#[delete("/buildings/{id}")]
155pub async fn delete_building(
156    state: web::Data<AppState>,
157    user: AuthenticatedUser,
158    id: web::Path<Uuid>,
159) -> impl Responder {
160    match state.building_use_cases.delete_building(*id).await {
161        Ok(true) => {
162            // Audit log: successful building deletion
163            AuditLogEntry::new(
164                AuditEventType::BuildingDeleted,
165                Some(user.user_id),
166                user.organization_id,
167            )
168            .with_resource("Building", *id)
169            .log();
170
171            HttpResponse::NoContent().finish()
172        }
173        Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
174            "error": "Building not found"
175        })),
176        Err(err) => {
177            // Audit log: failed building deletion
178            AuditLogEntry::new(
179                AuditEventType::BuildingDeleted,
180                Some(user.user_id),
181                user.organization_id,
182            )
183            .with_resource("Building", *id)
184            .with_error(err.clone())
185            .log();
186
187            HttpResponse::InternalServerError().json(serde_json::json!({
188                "error": err
189            }))
190        }
191    }
192}