koprogo_api/infrastructure/web/handlers/
board_decision_handlers.rs1use crate::application::dto::{
2 AddDecisionNotesDto, CreateBoardDecisionDto, UpdateBoardDecisionDto,
3};
4use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
5use crate::infrastructure::web::{AppState, AuthenticatedUser};
6use actix_web::{get, post, put, web, HttpResponse, Responder};
7use uuid::Uuid;
8
9#[post("/board-decisions")]
11pub async fn create_decision(
12 state: web::Data<AppState>,
13 user: AuthenticatedUser,
14 request: web::Json<CreateBoardDecisionDto>,
15) -> impl Responder {
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
25 match state
26 .board_decision_use_cases
27 .create_decision(request.into_inner())
28 .await
29 {
30 Ok(decision) => {
31 if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
33 AuditLogEntry::new(
34 AuditEventType::BoardDecisionCreated,
35 Some(user.user_id),
36 Some(organization_id),
37 )
38 .with_resource("BoardDecision", decision_uuid)
39 .log();
40 }
41
42 HttpResponse::Created().json(decision)
43 }
44 Err(err) => {
45 AuditLogEntry::new(
47 AuditEventType::BoardDecisionCreated,
48 Some(user.user_id),
49 Some(organization_id),
50 )
51 .with_error(err.clone())
52 .log();
53
54 HttpResponse::BadRequest().json(serde_json::json!({
55 "error": err
56 }))
57 }
58 }
59}
60
61#[get("/board-decisions/{id}")]
63pub async fn get_decision(
64 state: web::Data<AppState>,
65 user: AuthenticatedUser,
66 id: web::Path<Uuid>,
67) -> impl Responder {
68 let _organization_id = match user.require_organization() {
69 Ok(org_id) => org_id,
70 Err(e) => {
71 return HttpResponse::Unauthorized().json(serde_json::json!({
72 "error": e.to_string()
73 }))
74 }
75 };
76
77 match state.board_decision_use_cases.get_decision(*id).await {
78 Ok(decision) => HttpResponse::Ok().json(decision),
79 Err(err) => HttpResponse::NotFound().json(serde_json::json!({
80 "error": err
81 })),
82 }
83}
84
85#[get("/buildings/{building_id}/board-decisions")]
87pub async fn list_decisions_by_building(
88 state: web::Data<AppState>,
89 user: AuthenticatedUser,
90 building_id: web::Path<Uuid>,
91) -> impl Responder {
92 let _organization_id = match user.require_organization() {
93 Ok(org_id) => org_id,
94 Err(e) => {
95 return HttpResponse::Unauthorized().json(serde_json::json!({
96 "error": e.to_string()
97 }))
98 }
99 };
100
101 match state
102 .board_decision_use_cases
103 .list_decisions_by_building(*building_id)
104 .await
105 {
106 Ok(decisions) => HttpResponse::Ok().json(decisions),
107 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
108 "error": err
109 })),
110 }
111}
112
113#[get("/buildings/{building_id}/board-decisions/status/{status}")]
115pub async fn list_decisions_by_status(
116 state: web::Data<AppState>,
117 user: AuthenticatedUser,
118 path: web::Path<(Uuid, String)>,
119) -> impl Responder {
120 let (building_id, status) = path.into_inner();
121
122 let _organization_id = match user.require_organization() {
123 Ok(org_id) => org_id,
124 Err(e) => {
125 return HttpResponse::Unauthorized().json(serde_json::json!({
126 "error": e.to_string()
127 }))
128 }
129 };
130
131 match state
132 .board_decision_use_cases
133 .list_decisions_by_status(building_id, &status)
134 .await
135 {
136 Ok(decisions) => HttpResponse::Ok().json(decisions),
137 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({
138 "error": err
139 })),
140 }
141}
142
143#[get("/buildings/{building_id}/board-decisions/overdue")]
145pub async fn list_overdue_decisions(
146 state: web::Data<AppState>,
147 user: AuthenticatedUser,
148 building_id: web::Path<Uuid>,
149) -> impl Responder {
150 let _organization_id = match user.require_organization() {
151 Ok(org_id) => org_id,
152 Err(e) => {
153 return HttpResponse::Unauthorized().json(serde_json::json!({
154 "error": e.to_string()
155 }))
156 }
157 };
158
159 match state
160 .board_decision_use_cases
161 .list_overdue_decisions(*building_id)
162 .await
163 {
164 Ok(decisions) => HttpResponse::Ok().json(decisions),
165 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
166 "error": err
167 })),
168 }
169}
170
171#[put("/board-decisions/{id}")]
173pub async fn update_decision_status(
174 state: web::Data<AppState>,
175 user: AuthenticatedUser,
176 id: web::Path<Uuid>,
177 request: web::Json<UpdateBoardDecisionDto>,
178) -> impl Responder {
179 let organization_id = match user.require_organization() {
180 Ok(org_id) => org_id,
181 Err(e) => {
182 return HttpResponse::Unauthorized().json(serde_json::json!({
183 "error": e.to_string()
184 }))
185 }
186 };
187
188 match state
189 .board_decision_use_cases
190 .update_decision_status(*id, request.into_inner())
191 .await
192 {
193 Ok(decision) => {
194 if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
196 AuditLogEntry::new(
197 AuditEventType::BoardDecisionUpdated,
198 Some(user.user_id),
199 Some(organization_id),
200 )
201 .with_resource("BoardDecision", decision_uuid)
202 .log();
203 }
204
205 HttpResponse::Ok().json(decision)
206 }
207 Err(err) => {
208 AuditLogEntry::new(
210 AuditEventType::BoardDecisionUpdated,
211 Some(user.user_id),
212 Some(organization_id),
213 )
214 .with_error(err.clone())
215 .log();
216
217 HttpResponse::BadRequest().json(serde_json::json!({
218 "error": err
219 }))
220 }
221 }
222}
223
224#[post("/board-decisions/{id}/notes")]
226pub async fn add_notes(
227 state: web::Data<AppState>,
228 user: AuthenticatedUser,
229 id: web::Path<Uuid>,
230 request: web::Json<AddDecisionNotesDto>,
231) -> impl Responder {
232 let organization_id = match user.require_organization() {
233 Ok(org_id) => org_id,
234 Err(e) => {
235 return HttpResponse::Unauthorized().json(serde_json::json!({
236 "error": e.to_string()
237 }))
238 }
239 };
240
241 match state
242 .board_decision_use_cases
243 .add_notes(*id, request.into_inner())
244 .await
245 {
246 Ok(decision) => {
247 if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
249 AuditLogEntry::new(
250 AuditEventType::BoardDecisionNotesAdded,
251 Some(user.user_id),
252 Some(organization_id),
253 )
254 .with_resource("BoardDecision", decision_uuid)
255 .log();
256 }
257
258 HttpResponse::Ok().json(decision)
259 }
260 Err(err) => {
261 AuditLogEntry::new(
263 AuditEventType::BoardDecisionNotesAdded,
264 Some(user.user_id),
265 Some(organization_id),
266 )
267 .with_error(err.clone())
268 .log();
269
270 HttpResponse::BadRequest().json(serde_json::json!({
271 "error": err
272 }))
273 }
274 }
275}
276
277#[put("/board-decisions/{id}/complete")]
279pub async fn complete_decision(
280 state: web::Data<AppState>,
281 user: AuthenticatedUser,
282 id: web::Path<Uuid>,
283) -> impl Responder {
284 let organization_id = match user.require_organization() {
285 Ok(org_id) => org_id,
286 Err(e) => {
287 return HttpResponse::Unauthorized().json(serde_json::json!({
288 "error": e.to_string()
289 }))
290 }
291 };
292
293 match state.board_decision_use_cases.complete_decision(*id).await {
294 Ok(decision) => {
295 if let Ok(decision_uuid) = Uuid::parse_str(&decision.id) {
297 AuditLogEntry::new(
298 AuditEventType::BoardDecisionCompleted,
299 Some(user.user_id),
300 Some(organization_id),
301 )
302 .with_resource("BoardDecision", decision_uuid)
303 .log();
304 }
305
306 HttpResponse::Ok().json(decision)
307 }
308 Err(err) => {
309 AuditLogEntry::new(
311 AuditEventType::BoardDecisionCompleted,
312 Some(user.user_id),
313 Some(organization_id),
314 )
315 .with_error(err.clone())
316 .log();
317
318 HttpResponse::BadRequest().json(serde_json::json!({
319 "error": err
320 }))
321 }
322 }
323}
324
325#[get("/buildings/{building_id}/board-decisions/stats")]
327pub async fn get_decision_stats(
328 state: web::Data<AppState>,
329 user: AuthenticatedUser,
330 building_id: web::Path<Uuid>,
331) -> impl Responder {
332 let _organization_id = match user.require_organization() {
333 Ok(org_id) => org_id,
334 Err(e) => {
335 return HttpResponse::Unauthorized().json(serde_json::json!({
336 "error": e.to_string()
337 }))
338 }
339 };
340
341 match state
342 .board_decision_use_cases
343 .get_decision_stats(*building_id)
344 .await
345 {
346 Ok(stats) => HttpResponse::Ok().json(stats),
347 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({
348 "error": err
349 })),
350 }
351}