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::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use uuid::Uuid;
9
10#[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-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#[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#[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#[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#[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-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#[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("/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("/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#[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#[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#[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}