1use crate::application::dto::{
2 CreateAchievementDto, CreateChallengeDto, UpdateAchievementDto, UpdateChallengeDto,
3};
4use crate::domain::entities::{AchievementCategory, ChallengeStatus};
5use crate::infrastructure::web::app_state::AppState;
6use crate::infrastructure::web::middleware::AuthenticatedUser;
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
8use serde::Deserialize;
9use uuid::Uuid;
10
11#[post("/achievements")]
36pub async fn create_achievement(
37 data: web::Data<AppState>,
38 _auth: AuthenticatedUser, request: web::Json<CreateAchievementDto>,
40) -> impl Responder {
41 match data
42 .achievement_use_cases
43 .create_achievement(request.into_inner())
44 .await
45 {
46 Ok(achievement) => HttpResponse::Created().json(achievement),
47 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
48 }
49}
50
51#[get("/achievements/{id}")]
59pub async fn get_achievement(
60 data: web::Data<AppState>,
61 _auth: AuthenticatedUser,
62 id: web::Path<Uuid>,
63) -> impl Responder {
64 match data
65 .achievement_use_cases
66 .get_achievement(id.into_inner())
67 .await
68 {
69 Ok(achievement) => HttpResponse::Ok().json(achievement),
70 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
71 }
72}
73
74#[get("/organizations/{organization_id}/achievements")]
81pub async fn list_achievements(
82 data: web::Data<AppState>,
83 _auth: AuthenticatedUser,
84 organization_id: web::Path<Uuid>,
85) -> impl Responder {
86 match data
87 .achievement_use_cases
88 .list_achievements(organization_id.into_inner())
89 .await
90 {
91 Ok(achievements) => HttpResponse::Ok().json(achievements),
92 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
93 }
94}
95
96#[get("/organizations/{organization_id}/achievements/category/{category}")]
103pub async fn list_achievements_by_category(
104 data: web::Data<AppState>,
105 _auth: AuthenticatedUser,
106 path: web::Path<(Uuid, String)>,
107) -> impl Responder {
108 let (organization_id, category_str) = path.into_inner();
109
110 let category: AchievementCategory = match serde_json::from_str(&format!("\"{}\"", category_str))
112 {
113 Ok(cat) => cat,
114 Err(_) => {
115 return HttpResponse::BadRequest()
116 .json(serde_json::json!({"error": "Invalid category"}))
117 }
118 };
119
120 match data
121 .achievement_use_cases
122 .list_achievements_by_category(organization_id, category)
123 .await
124 {
125 Ok(achievements) => HttpResponse::Ok().json(achievements),
126 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
127 }
128}
129
130#[get("/organizations/{organization_id}/achievements/visible")]
137pub async fn list_visible_achievements(
138 data: web::Data<AppState>,
139 auth: AuthenticatedUser,
140 organization_id: web::Path<Uuid>,
141) -> impl Responder {
142 match data
143 .achievement_use_cases
144 .list_visible_achievements(organization_id.into_inner(), auth.user_id)
145 .await
146 {
147 Ok(achievements) => HttpResponse::Ok().json(achievements),
148 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
149 }
150}
151
152#[put("/achievements/{id}")]
161pub async fn update_achievement(
162 data: web::Data<AppState>,
163 _auth: AuthenticatedUser, id: web::Path<Uuid>,
165 request: web::Json<UpdateAchievementDto>,
166) -> impl Responder {
167 match data
168 .achievement_use_cases
169 .update_achievement(id.into_inner(), request.into_inner())
170 .await
171 {
172 Ok(achievement) => HttpResponse::Ok().json(achievement),
173 Err(e) if e.contains("not found") => {
174 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
175 }
176 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
177 }
178}
179
180#[delete("/achievements/{id}")]
188pub async fn delete_achievement(
189 data: web::Data<AppState>,
190 _auth: AuthenticatedUser, id: web::Path<Uuid>,
192) -> impl Responder {
193 match data
194 .achievement_use_cases
195 .delete_achievement(id.into_inner())
196 .await
197 {
198 Ok(_) => HttpResponse::NoContent().finish(),
199 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
200 }
201}
202
203#[derive(Debug, Deserialize)]
208pub struct AwardAchievementRequest {
209 pub achievement_id: Uuid,
210 pub progress_data: Option<String>,
211}
212
213#[post("/users/achievements")]
226pub async fn award_achievement(
227 data: web::Data<AppState>,
228 auth: AuthenticatedUser,
229 request: web::Json<AwardAchievementRequest>,
230) -> impl Responder {
231 let req = request.into_inner();
232 match data
233 .achievement_use_cases
234 .award_achievement(auth.user_id, req.achievement_id, req.progress_data)
235 .await
236 {
237 Ok(user_achievement) => HttpResponse::Created().json(user_achievement),
238 Err(e) if e.contains("not found") => {
239 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
240 }
241 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
242 }
243}
244
245#[get("/users/achievements")]
252pub async fn get_user_achievements(
253 data: web::Data<AppState>,
254 auth: AuthenticatedUser,
255) -> impl Responder {
256 match data
257 .achievement_use_cases
258 .get_user_achievements(auth.user_id)
259 .await
260 {
261 Ok(achievements) => HttpResponse::Ok().json(achievements),
262 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
263 }
264}
265
266#[get("/users/achievements/recent")]
276pub async fn get_recent_achievements(
277 data: web::Data<AppState>,
278 auth: AuthenticatedUser,
279 query: web::Query<serde_json::Value>,
280) -> impl Responder {
281 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(5);
282
283 match data
284 .achievement_use_cases
285 .get_recent_achievements(auth.user_id, limit)
286 .await
287 {
288 Ok(achievements) => HttpResponse::Ok().json(achievements),
289 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
290 }
291}
292
293#[post("/challenges")]
318pub async fn create_challenge(
319 data: web::Data<AppState>,
320 _auth: AuthenticatedUser, request: web::Json<CreateChallengeDto>,
322) -> impl Responder {
323 match data
324 .challenge_use_cases
325 .create_challenge(request.into_inner())
326 .await
327 {
328 Ok(challenge) => HttpResponse::Created().json(challenge),
329 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
330 }
331}
332
333#[get("/challenges/{id}")]
341pub async fn get_challenge(
342 data: web::Data<AppState>,
343 _auth: AuthenticatedUser,
344 id: web::Path<Uuid>,
345) -> impl Responder {
346 match data
347 .challenge_use_cases
348 .get_challenge(id.into_inner())
349 .await
350 {
351 Ok(challenge) => HttpResponse::Ok().json(challenge),
352 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
353 }
354}
355
356#[get("/organizations/{organization_id}/challenges")]
363pub async fn list_challenges(
364 data: web::Data<AppState>,
365 _auth: AuthenticatedUser,
366 organization_id: web::Path<Uuid>,
367) -> impl Responder {
368 match data
369 .challenge_use_cases
370 .list_challenges(organization_id.into_inner())
371 .await
372 {
373 Ok(challenges) => HttpResponse::Ok().json(challenges),
374 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
375 }
376}
377
378#[get("/organizations/{organization_id}/challenges/status/{status}")]
385pub async fn list_challenges_by_status(
386 data: web::Data<AppState>,
387 _auth: AuthenticatedUser,
388 path: web::Path<(Uuid, String)>,
389) -> impl Responder {
390 let (organization_id, status_str) = path.into_inner();
391
392 let status: ChallengeStatus = match serde_json::from_str(&format!("\"{}\"", status_str)) {
394 Ok(s) => s,
395 Err(_) => {
396 return HttpResponse::BadRequest().json(serde_json::json!({"error": "Invalid status"}))
397 }
398 };
399
400 match data
401 .challenge_use_cases
402 .list_challenges_by_status(organization_id, status)
403 .await
404 {
405 Ok(challenges) => HttpResponse::Ok().json(challenges),
406 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
407 }
408}
409
410#[get("/buildings/{building_id}/challenges")]
417pub async fn list_building_challenges(
418 data: web::Data<AppState>,
419 _auth: AuthenticatedUser,
420 building_id: web::Path<Uuid>,
421) -> impl Responder {
422 match data
423 .challenge_use_cases
424 .list_building_challenges(building_id.into_inner())
425 .await
426 {
427 Ok(challenges) => HttpResponse::Ok().json(challenges),
428 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
429 }
430}
431
432#[get("/organizations/{organization_id}/challenges/active")]
439pub async fn list_active_challenges(
440 data: web::Data<AppState>,
441 _auth: AuthenticatedUser,
442 organization_id: web::Path<Uuid>,
443) -> impl Responder {
444 match data
445 .challenge_use_cases
446 .list_active_challenges(organization_id.into_inner())
447 .await
448 {
449 Ok(challenges) => HttpResponse::Ok().json(challenges),
450 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
451 }
452}
453
454#[put("/challenges/{id}")]
463pub async fn update_challenge(
464 data: web::Data<AppState>,
465 _auth: AuthenticatedUser, id: web::Path<Uuid>,
467 request: web::Json<UpdateChallengeDto>,
468) -> impl Responder {
469 match data
470 .challenge_use_cases
471 .update_challenge(id.into_inner(), request.into_inner())
472 .await
473 {
474 Ok(challenge) => HttpResponse::Ok().json(challenge),
475 Err(e) if e.contains("not found") => {
476 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
477 }
478 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
479 }
480}
481
482#[put("/challenges/{id}/activate")]
491pub async fn activate_challenge(
492 data: web::Data<AppState>,
493 _auth: AuthenticatedUser, id: web::Path<Uuid>,
495) -> impl Responder {
496 match data
497 .challenge_use_cases
498 .activate_challenge(id.into_inner())
499 .await
500 {
501 Ok(challenge) => HttpResponse::Ok().json(challenge),
502 Err(e) if e.contains("not found") => {
503 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
504 }
505 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
506 }
507}
508
509#[put("/challenges/{id}/complete")]
518pub async fn complete_challenge(
519 data: web::Data<AppState>,
520 _auth: AuthenticatedUser, id: web::Path<Uuid>,
522) -> impl Responder {
523 match data
524 .challenge_use_cases
525 .complete_challenge(id.into_inner())
526 .await
527 {
528 Ok(challenge) => HttpResponse::Ok().json(challenge),
529 Err(e) if e.contains("not found") => {
530 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
531 }
532 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
533 }
534}
535
536#[put("/challenges/{id}/cancel")]
545pub async fn cancel_challenge(
546 data: web::Data<AppState>,
547 _auth: AuthenticatedUser, id: web::Path<Uuid>,
549) -> impl Responder {
550 match data
551 .challenge_use_cases
552 .cancel_challenge(id.into_inner())
553 .await
554 {
555 Ok(challenge) => HttpResponse::Ok().json(challenge),
556 Err(e) if e.contains("not found") => {
557 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
558 }
559 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
560 }
561}
562
563#[delete("/challenges/{id}")]
571pub async fn delete_challenge(
572 data: web::Data<AppState>,
573 _auth: AuthenticatedUser, id: web::Path<Uuid>,
575) -> impl Responder {
576 match data
577 .challenge_use_cases
578 .delete_challenge(id.into_inner())
579 .await
580 {
581 Ok(_) => HttpResponse::NoContent().finish(),
582 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
583 }
584}
585
586#[get("/challenges/{challenge_id}/progress")]
598pub async fn get_challenge_progress(
599 data: web::Data<AppState>,
600 auth: AuthenticatedUser,
601 challenge_id: web::Path<Uuid>,
602) -> impl Responder {
603 match data
604 .challenge_use_cases
605 .get_challenge_progress(auth.user_id, challenge_id.into_inner())
606 .await
607 {
608 Ok(progress) => HttpResponse::Ok().json(progress),
609 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
610 }
611}
612
613#[get("/challenges/{challenge_id}/all-progress")]
620pub async fn list_challenge_progress(
621 data: web::Data<AppState>,
622 _auth: AuthenticatedUser,
623 challenge_id: web::Path<Uuid>,
624) -> impl Responder {
625 match data
626 .challenge_use_cases
627 .list_challenge_progress(challenge_id.into_inner())
628 .await
629 {
630 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
631 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
632 }
633}
634
635#[get("/users/challenges/active")]
642pub async fn list_user_active_challenges(
643 data: web::Data<AppState>,
644 auth: AuthenticatedUser,
645) -> impl Responder {
646 match data
647 .challenge_use_cases
648 .list_user_active_progress(auth.user_id)
649 .await
650 {
651 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
652 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
653 }
654}
655
656#[derive(Debug, Deserialize)]
657pub struct IncrementProgressRequest {
658 pub increment: i32,
659}
660
661#[post("/challenges/{challenge_id}/progress/increment")]
673pub async fn increment_progress(
674 data: web::Data<AppState>,
675 auth: AuthenticatedUser,
676 challenge_id: web::Path<Uuid>,
677 request: web::Json<IncrementProgressRequest>,
678) -> impl Responder {
679 match data
680 .challenge_use_cases
681 .increment_progress(auth.user_id, challenge_id.into_inner(), request.increment)
682 .await
683 {
684 Ok(progress) => HttpResponse::Ok().json(progress),
685 Err(e) if e.contains("not found") => {
686 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
687 }
688 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
689 }
690}
691
692#[get("/organizations/{organization_id}/gamification/stats")]
703pub async fn get_gamification_user_stats(
704 data: web::Data<AppState>,
705 auth: AuthenticatedUser,
706 organization_id: web::Path<Uuid>,
707) -> impl Responder {
708 match data
709 .gamification_stats_use_cases
710 .get_user_stats(auth.user_id, organization_id.into_inner())
711 .await
712 {
713 Ok(stats) => HttpResponse::Ok().json(stats),
714 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
715 }
716}
717
718#[get("/organizations/{organization_id}/gamification/leaderboard")]
729pub async fn get_gamification_leaderboard(
730 data: web::Data<AppState>,
731 _auth: AuthenticatedUser,
732 organization_id: web::Path<Uuid>,
733 query: web::Query<serde_json::Value>,
734) -> impl Responder {
735 let building_id = query
736 .get("building_id")
737 .and_then(|v| v.as_str())
738 .and_then(|s| Uuid::parse_str(s).ok());
739
740 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10);
741
742 match data
743 .gamification_stats_use_cases
744 .get_leaderboard(organization_id.into_inner(), building_id, limit)
745 .await
746 {
747 Ok(leaderboard) => HttpResponse::Ok().json(leaderboard),
748 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
749 }
750}