koprogo_api/infrastructure/web/handlers/
technical_inspection_handlers.rs

1use crate::application::dto::{
2    AddCertificateDto, AddInspectionPhotoDto, AddReportDto, CreateTechnicalInspectionDto,
3    PageRequest, TechnicalInspectionFilters, UpdateTechnicalInspectionDto,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use uuid::Uuid;
9
10// ==================== Technical Inspection CRUD Endpoints ====================
11
12/// Create a new technical inspection
13#[post("/technical-inspections")]
14pub async fn create_technical_inspection(
15    state: web::Data<AppState>,
16    user: AuthenticatedUser,
17    request: web::Json<CreateTechnicalInspectionDto>,
18) -> impl Responder {
19    let organization_id = match user.require_organization() {
20        Ok(org_id) => org_id,
21        Err(e) => {
22            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
23        }
24    };
25
26    match state
27        .technical_inspection_use_cases
28        .create_technical_inspection(request.into_inner())
29        .await
30    {
31        Ok(inspection) => {
32            AuditLogEntry::new(
33                AuditEventType::TechnicalInspectionCreated,
34                Some(user.user_id),
35                Some(organization_id),
36            )
37            .with_resource("TechnicalInspection", inspection.id.parse().unwrap())
38            .log();
39
40            HttpResponse::Created().json(inspection)
41        }
42        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
43    }
44}
45
46/// Get technical inspection by ID
47#[get("/technical-inspections/{id}")]
48pub async fn get_technical_inspection(
49    state: web::Data<AppState>,
50    id: web::Path<Uuid>,
51) -> impl Responder {
52    match state
53        .technical_inspection_use_cases
54        .get_technical_inspection(*id)
55        .await
56    {
57        Ok(Some(inspection)) => HttpResponse::Ok().json(inspection),
58        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
59            "error": "Technical inspection not found"
60        })),
61        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
62    }
63}
64
65/// List technical inspections by building
66#[get("/buildings/{building_id}/technical-inspections")]
67pub async fn list_building_technical_inspections(
68    state: web::Data<AppState>,
69    building_id: web::Path<Uuid>,
70) -> impl Responder {
71    match state
72        .technical_inspection_use_cases
73        .list_technical_inspections_by_building(*building_id)
74        .await
75    {
76        Ok(inspections) => HttpResponse::Ok().json(inspections),
77        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
78    }
79}
80
81/// List technical inspections by organization
82#[get("/organizations/{organization_id}/technical-inspections")]
83pub async fn list_organization_technical_inspections(
84    state: web::Data<AppState>,
85    user: AuthenticatedUser,
86    organization_id: web::Path<Uuid>,
87) -> impl Responder {
88    if let Err(e) = user.verify_org_access(*organization_id) {
89        return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
90    }
91    match state
92        .technical_inspection_use_cases
93        .list_technical_inspections_by_organization(*organization_id)
94        .await
95    {
96        Ok(inspections) => HttpResponse::Ok().json(inspections),
97        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
98    }
99}
100
101/// List technical inspections with pagination and filters
102#[get("/technical-inspections")]
103pub async fn list_technical_inspections_paginated(
104    state: web::Data<AppState>,
105    page_request: web::Query<PageRequest>,
106    filters: web::Query<TechnicalInspectionFilters>,
107) -> impl Responder {
108    match state
109        .technical_inspection_use_cases
110        .list_technical_inspections_paginated(&page_request.into_inner(), &filters.into_inner())
111        .await
112    {
113        Ok(response) => HttpResponse::Ok().json(response),
114        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
115    }
116}
117
118/// Update technical inspection
119#[put("/technical-inspections/{id}")]
120pub async fn update_technical_inspection(
121    state: web::Data<AppState>,
122    user: AuthenticatedUser,
123    id: web::Path<Uuid>,
124    request: web::Json<UpdateTechnicalInspectionDto>,
125) -> impl Responder {
126    let organization_id = match user.require_organization() {
127        Ok(org_id) => org_id,
128        Err(e) => {
129            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
130        }
131    };
132
133    match state
134        .technical_inspection_use_cases
135        .update_technical_inspection(*id, request.into_inner())
136        .await
137    {
138        Ok(inspection) => {
139            AuditLogEntry::new(
140                AuditEventType::TechnicalInspectionUpdated,
141                Some(user.user_id),
142                Some(organization_id),
143            )
144            .with_resource("TechnicalInspection", *id)
145            .log();
146
147            HttpResponse::Ok().json(inspection)
148        }
149        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
150    }
151}
152
153/// Delete technical inspection
154#[delete("/technical-inspections/{id}")]
155pub async fn delete_technical_inspection(
156    state: web::Data<AppState>,
157    user: AuthenticatedUser,
158    id: web::Path<Uuid>,
159) -> impl Responder {
160    let organization_id = match user.require_organization() {
161        Ok(org_id) => org_id,
162        Err(e) => {
163            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
164        }
165    };
166
167    match state
168        .technical_inspection_use_cases
169        .delete_technical_inspection(*id)
170        .await
171    {
172        Ok(deleted) => {
173            if deleted {
174                AuditLogEntry::new(
175                    AuditEventType::TechnicalInspectionDeleted,
176                    Some(user.user_id),
177                    Some(organization_id),
178                )
179                .with_resource("TechnicalInspection", *id)
180                .log();
181
182                HttpResponse::NoContent().finish()
183            } else {
184                HttpResponse::NotFound().json(serde_json::json!({
185                    "error": "Technical inspection not found"
186                }))
187            }
188        }
189        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
190    }
191}
192
193// ==================== Inspection Tracking Endpoints ====================
194
195/// Get overdue inspections for a building
196#[get("/buildings/{building_id}/technical-inspections/overdue")]
197pub async fn get_overdue_inspections(
198    state: web::Data<AppState>,
199    building_id: web::Path<Uuid>,
200) -> impl Responder {
201    match state
202        .technical_inspection_use_cases
203        .get_overdue_inspections(*building_id)
204        .await
205    {
206        Ok(inspections) => HttpResponse::Ok().json(inspections),
207        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
208    }
209}
210
211/// Get upcoming inspections for a building (within X days)
212#[get("/buildings/{building_id}/technical-inspections/upcoming")]
213pub async fn get_upcoming_inspections(
214    state: web::Data<AppState>,
215    path: web::Path<Uuid>,
216    query: web::Query<serde_json::Value>,
217) -> impl Responder {
218    let building_id = path.into_inner();
219    let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
220
221    match state
222        .technical_inspection_use_cases
223        .get_upcoming_inspections(building_id, days)
224        .await
225    {
226        Ok(inspections) => HttpResponse::Ok().json(inspections),
227        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
228    }
229}
230
231/// Get inspections by type for a building
232#[get("/buildings/{building_id}/technical-inspections/type/{inspection_type}")]
233pub async fn get_inspections_by_type(
234    state: web::Data<AppState>,
235    path: web::Path<(Uuid, String)>,
236) -> impl Responder {
237    let (building_id, inspection_type) = path.into_inner();
238
239    match state
240        .technical_inspection_use_cases
241        .get_inspections_by_type(building_id, &inspection_type)
242        .await
243    {
244        Ok(inspections) => HttpResponse::Ok().json(inspections),
245        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
246    }
247}
248
249// ==================== Document Management ====================
250
251/// Add report to technical inspection
252#[post("/technical-inspections/{id}/reports")]
253pub async fn add_report(
254    state: web::Data<AppState>,
255    user: AuthenticatedUser,
256    id: web::Path<Uuid>,
257    request: web::Json<AddReportDto>,
258) -> impl Responder {
259    let organization_id = match user.require_organization() {
260        Ok(org_id) => org_id,
261        Err(e) => {
262            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
263        }
264    };
265
266    match state
267        .technical_inspection_use_cases
268        .add_report(*id, request.into_inner())
269        .await
270    {
271        Ok(inspection) => {
272            AuditLogEntry::new(
273                AuditEventType::TechnicalInspectionReportAdded,
274                Some(user.user_id),
275                Some(organization_id),
276            )
277            .with_resource("TechnicalInspection", *id)
278            .log();
279
280            HttpResponse::Ok().json(inspection)
281        }
282        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
283    }
284}
285
286/// Add photo to technical inspection
287#[post("/technical-inspections/{id}/photos")]
288pub async fn add_inspection_photo(
289    state: web::Data<AppState>,
290    user: AuthenticatedUser,
291    id: web::Path<Uuid>,
292    request: web::Json<AddInspectionPhotoDto>,
293) -> impl Responder {
294    let organization_id = match user.require_organization() {
295        Ok(org_id) => org_id,
296        Err(e) => {
297            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
298        }
299    };
300
301    match state
302        .technical_inspection_use_cases
303        .add_photo(*id, request.into_inner())
304        .await
305    {
306        Ok(inspection) => {
307            AuditLogEntry::new(
308                AuditEventType::TechnicalInspectionPhotoAdded,
309                Some(user.user_id),
310                Some(organization_id),
311            )
312            .with_resource("TechnicalInspection", *id)
313            .log();
314
315            HttpResponse::Ok().json(inspection)
316        }
317        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
318    }
319}
320
321/// Add certificate to technical inspection
322#[post("/technical-inspections/{id}/certificates")]
323pub async fn add_certificate(
324    state: web::Data<AppState>,
325    user: AuthenticatedUser,
326    id: web::Path<Uuid>,
327    request: web::Json<AddCertificateDto>,
328) -> impl Responder {
329    let organization_id = match user.require_organization() {
330        Ok(org_id) => org_id,
331        Err(e) => {
332            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
333        }
334    };
335
336    match state
337        .technical_inspection_use_cases
338        .add_certificate(*id, request.into_inner())
339        .await
340    {
341        Ok(inspection) => {
342            AuditLogEntry::new(
343                AuditEventType::TechnicalInspectionCertificateAdded,
344                Some(user.user_id),
345                Some(organization_id),
346            )
347            .with_resource("TechnicalInspection", *id)
348            .log();
349
350            HttpResponse::Ok().json(inspection)
351        }
352        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
353    }
354}