koprogo_api/infrastructure/web/handlers/
technical_inspection_handlers.rs1use crate::application::dto::{
2 AddCertificateDto, AddInspectionPhotoDto, AddReportDto, CreateTechnicalInspectionDto,
3 PageRequest, TechnicalInspectionFilters, UpdateTechnicalInspectionDto,
4};
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::middleware::scope_guard::verify_building_org_access;
7use crate::infrastructure::web::{AppState, AuthenticatedUser};
8use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
9use uuid::Uuid;
10
11#[post("/technical-inspections")]
15pub async fn create_technical_inspection(
16 state: web::Data<AppState>,
17 user: AuthenticatedUser,
18 request: web::Json<CreateTechnicalInspectionDto>,
19) -> impl Responder {
20 let organization_id = match user.require_organization() {
21 Ok(org_id) => org_id,
22 Err(e) => {
23 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
24 }
25 };
26
27 match state
28 .technical_inspection_use_cases
29 .create_technical_inspection({
43 let mut dto = request.into_inner();
44 dto.organization_id = organization_id.to_string();
45 dto
46 })
47 .await
48 {
49 Ok(inspection) => {
50 AuditLogEntry::new(
51 AuditEventType::TechnicalInspectionCreated,
52 Some(user.user_id),
53 Some(organization_id),
54 )
55 .with_resource("TechnicalInspection", inspection.id.parse().unwrap())
56 .log();
57
58 HttpResponse::Created().json(inspection)
59 }
60 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
61 }
62}
63
64#[get("/technical-inspections/{id}")]
66pub async fn get_technical_inspection(
67 state: web::Data<AppState>,
68 user: AuthenticatedUser,
69 id: web::Path<Uuid>,
70) -> impl Responder {
71 match state
77 .technical_inspection_use_cases
78 .get_technical_inspection(*id)
79 .await
80 {
81 Ok(Some(inspection)) => {
82 match Uuid::parse_str(&inspection.organization_id) {
85 Ok(org) => match user.verify_org_access(org) {
86 Ok(()) => HttpResponse::Ok().json(inspection),
87 Err(e) => HttpResponse::Forbidden().json(serde_json::json!({ "error": e })),
88 },
89 Err(_) => HttpResponse::InternalServerError()
90 .json(serde_json::json!({"error": "Invalid organization_id"})),
91 }
92 }
93 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
94 "error": "Technical inspection not found"
95 })),
96 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
97 }
98}
99
100#[get("/buildings/{building_id}/technical-inspections")]
102pub async fn list_building_technical_inspections(
103 state: web::Data<AppState>,
104 building_id: web::Path<Uuid>,
105 user: AuthenticatedUser,
106) -> impl Responder {
107 if let Err(err) =
111 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
112 &user,
113 *building_id,
114 &state.building_use_cases,
115 &state.acp_use_cases,
116 )
117 .await
118 {
119 return err.error_response();
120 }
121
122 match state
123 .technical_inspection_use_cases
124 .list_technical_inspections_by_building(*building_id)
125 .await
126 {
127 Ok(inspections) => HttpResponse::Ok().json(inspections),
128 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
129 }
130}
131
132#[get("/organizations/{organization_id}/technical-inspections")]
134pub async fn list_organization_technical_inspections(
135 state: web::Data<AppState>,
136 user: AuthenticatedUser,
137 organization_id: web::Path<Uuid>,
138) -> impl Responder {
139 if let Err(e) = user.verify_org_access(*organization_id) {
140 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
141 }
142 match state
143 .technical_inspection_use_cases
144 .list_technical_inspections_by_organization(*organization_id)
145 .await
146 {
147 Ok(inspections) => HttpResponse::Ok().json(inspections),
148 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
149 }
150}
151
152#[get("/technical-inspections")]
154pub async fn list_technical_inspections_paginated(
155 state: web::Data<AppState>,
156 user: AuthenticatedUser,
157 page_request: web::Query<PageRequest>,
158 filters: web::Query<TechnicalInspectionFilters>,
159) -> impl Responder {
160 let mut filters = filters.into_inner();
167
168 match filters.organization_id {
169 Some(org_id) => {
170 if let Err(e) = user.verify_org_access(org_id) {
171 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
172 }
173 }
174 None => {
175 filters.organization_id = user.effective_org_filter();
179 }
180 }
181
182 if let Some(building_id) = filters.building_id {
183 if let Err(err) = verify_building_org_access(
184 &user,
185 building_id,
186 &state.building_use_cases,
187 &state.acp_use_cases,
188 )
189 .await
190 {
191 return err.error_response();
192 }
193 }
194
195 match state
196 .technical_inspection_use_cases
197 .list_technical_inspections_paginated(&page_request.into_inner(), &filters)
198 .await
199 {
200 Ok(response) => HttpResponse::Ok().json(response),
201 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
202 }
203}
204
205#[put("/technical-inspections/{id}")]
207pub async fn update_technical_inspection(
208 state: web::Data<AppState>,
209 user: AuthenticatedUser,
210 id: web::Path<Uuid>,
211 request: web::Json<UpdateTechnicalInspectionDto>,
212) -> impl Responder {
213 let organization_id = match user.require_organization() {
214 Ok(org_id) => org_id,
215 Err(e) => {
216 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
217 }
218 };
219
220 match state
221 .technical_inspection_use_cases
222 .update_technical_inspection(*id, request.into_inner())
223 .await
224 {
225 Ok(inspection) => {
226 AuditLogEntry::new(
227 AuditEventType::TechnicalInspectionUpdated,
228 Some(user.user_id),
229 Some(organization_id),
230 )
231 .with_resource("TechnicalInspection", *id)
232 .log();
233
234 HttpResponse::Ok().json(inspection)
235 }
236 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
237 }
238}
239
240#[delete("/technical-inspections/{id}")]
242pub async fn delete_technical_inspection(
243 state: web::Data<AppState>,
244 user: AuthenticatedUser,
245 id: web::Path<Uuid>,
246) -> impl Responder {
247 let organization_id = match user.require_organization() {
248 Ok(org_id) => org_id,
249 Err(e) => {
250 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
251 }
252 };
253
254 match state
255 .technical_inspection_use_cases
256 .delete_technical_inspection(*id)
257 .await
258 {
259 Ok(deleted) => {
260 if deleted {
261 AuditLogEntry::new(
262 AuditEventType::TechnicalInspectionDeleted,
263 Some(user.user_id),
264 Some(organization_id),
265 )
266 .with_resource("TechnicalInspection", *id)
267 .log();
268
269 HttpResponse::NoContent().finish()
270 } else {
271 HttpResponse::NotFound().json(serde_json::json!({
272 "error": "Technical inspection not found"
273 }))
274 }
275 }
276 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
277 }
278}
279
280#[get("/buildings/{building_id}/technical-inspections/overdue")]
284pub async fn get_overdue_inspections(
285 state: web::Data<AppState>,
286 building_id: web::Path<Uuid>,
287 user: AuthenticatedUser,
288) -> impl Responder {
289 if let Err(err) =
293 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
294 &user,
295 *building_id,
296 &state.building_use_cases,
297 &state.acp_use_cases,
298 )
299 .await
300 {
301 return err.error_response();
302 }
303
304 match state
305 .technical_inspection_use_cases
306 .get_overdue_inspections(*building_id)
307 .await
308 {
309 Ok(inspections) => HttpResponse::Ok().json(inspections),
310 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
311 }
312}
313
314#[get("/buildings/{building_id}/technical-inspections/upcoming")]
316pub async fn get_upcoming_inspections(
317 state: web::Data<AppState>,
318 path: web::Path<Uuid>,
319 query: web::Query<serde_json::Value>,
320 user: AuthenticatedUser,
321) -> impl Responder {
322 let building_id = path.into_inner();
323 let days = query.get("days").and_then(|v| v.as_i64()).unwrap_or(90) as i32;
324
325 if let Err(err) =
332 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
333 &user,
334 building_id,
335 &state.building_use_cases,
336 &state.acp_use_cases,
337 )
338 .await
339 {
340 return err.error_response();
341 }
342
343 match state
344 .technical_inspection_use_cases
345 .get_upcoming_inspections(building_id, days)
346 .await
347 {
348 Ok(inspections) => HttpResponse::Ok().json(inspections),
349 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
350 }
351}
352
353#[get("/buildings/{building_id}/technical-inspections/type/{inspection_type}")]
355pub async fn get_inspections_by_type(
356 state: web::Data<AppState>,
357 path: web::Path<(Uuid, String)>,
358 user: AuthenticatedUser,
359) -> impl Responder {
360 let (building_id, inspection_type) = path.into_inner();
361 if let Err(err) =
365 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
366 &user,
367 building_id,
368 &state.building_use_cases,
369 &state.acp_use_cases,
370 )
371 .await
372 {
373 return err.error_response();
374 }
375
376 if let Err(err) =
380 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
381 &user,
382 building_id,
383 &state.building_use_cases,
384 &state.acp_use_cases,
385 )
386 .await
387 {
388 return err.error_response();
389 }
390
391 match state
392 .technical_inspection_use_cases
393 .get_inspections_by_type(building_id, &inspection_type)
394 .await
395 {
396 Ok(inspections) => HttpResponse::Ok().json(inspections),
397 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
398 }
399}
400
401#[post("/technical-inspections/{id}/reports")]
405pub async fn add_report(
406 state: web::Data<AppState>,
407 user: AuthenticatedUser,
408 id: web::Path<Uuid>,
409 request: web::Json<AddReportDto>,
410) -> impl Responder {
411 let organization_id = match user.require_organization() {
412 Ok(org_id) => org_id,
413 Err(e) => {
414 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
415 }
416 };
417
418 match state
419 .technical_inspection_use_cases
420 .add_report(*id, request.into_inner())
421 .await
422 {
423 Ok(inspection) => {
424 AuditLogEntry::new(
425 AuditEventType::TechnicalInspectionReportAdded,
426 Some(user.user_id),
427 Some(organization_id),
428 )
429 .with_resource("TechnicalInspection", *id)
430 .log();
431
432 HttpResponse::Ok().json(inspection)
433 }
434 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
435 }
436}
437
438#[post("/technical-inspections/{id}/photos")]
440pub async fn add_inspection_photo(
441 state: web::Data<AppState>,
442 user: AuthenticatedUser,
443 id: web::Path<Uuid>,
444 request: web::Json<AddInspectionPhotoDto>,
445) -> impl Responder {
446 let organization_id = match user.require_organization() {
447 Ok(org_id) => org_id,
448 Err(e) => {
449 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
450 }
451 };
452
453 match state
454 .technical_inspection_use_cases
455 .add_photo(*id, request.into_inner())
456 .await
457 {
458 Ok(inspection) => {
459 AuditLogEntry::new(
460 AuditEventType::TechnicalInspectionPhotoAdded,
461 Some(user.user_id),
462 Some(organization_id),
463 )
464 .with_resource("TechnicalInspection", *id)
465 .log();
466
467 HttpResponse::Ok().json(inspection)
468 }
469 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
470 }
471}
472
473#[post("/technical-inspections/{id}/certificates")]
475pub async fn add_certificate(
476 state: web::Data<AppState>,
477 user: AuthenticatedUser,
478 id: web::Path<Uuid>,
479 request: web::Json<AddCertificateDto>,
480) -> impl Responder {
481 let organization_id = match user.require_organization() {
482 Ok(org_id) => org_id,
483 Err(e) => {
484 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
485 }
486 };
487
488 match state
489 .technical_inspection_use_cases
490 .add_certificate(*id, request.into_inner())
491 .await
492 {
493 Ok(inspection) => {
494 AuditLogEntry::new(
495 AuditEventType::TechnicalInspectionCertificateAdded,
496 Some(user.user_id),
497 Some(organization_id),
498 )
499 .with_resource("TechnicalInspection", *id)
500 .log();
501
502 HttpResponse::Ok().json(inspection)
503 }
504 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
505 }
506}