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,
39 request: web::Json<CreateAchievementDto>,
40) -> impl Responder {
41 if auth.role != "superadmin" && auth.role != "syndic" {
42 return HttpResponse::Forbidden().json(serde_json::json!({
43 "error": "Only superadmin or syndic can create achievements"
44 }));
45 }
46 match data
47 .achievement_use_cases
48 .create_achievement(request.into_inner())
49 .await
50 {
51 Ok(achievement) => HttpResponse::Created().json(achievement),
52 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
53 }
54}
55
56#[get("/achievements/{id}")]
64pub async fn get_achievement(
65 data: web::Data<AppState>,
66 _auth: AuthenticatedUser,
67 id: web::Path<Uuid>,
68) -> impl Responder {
69 match data
70 .achievement_use_cases
71 .get_achievement(id.into_inner())
72 .await
73 {
74 Ok(achievement) => HttpResponse::Ok().json(achievement),
75 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
76 }
77}
78
79#[get("/organizations/{organization_id}/achievements")]
86pub async fn list_achievements(
87 data: web::Data<AppState>,
88 _auth: AuthenticatedUser,
89 organization_id: web::Path<Uuid>,
90) -> impl Responder {
91 match data
92 .achievement_use_cases
93 .list_achievements(organization_id.into_inner())
94 .await
95 {
96 Ok(achievements) => HttpResponse::Ok().json(achievements),
97 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
98 }
99}
100
101#[get("/organizations/{organization_id}/achievements/category/{category}")]
108pub async fn list_achievements_by_category(
109 data: web::Data<AppState>,
110 _auth: AuthenticatedUser,
111 path: web::Path<(Uuid, String)>,
112) -> impl Responder {
113 let (organization_id, category_str) = path.into_inner();
114
115 let category: AchievementCategory = match serde_json::from_str(&format!("\"{}\"", category_str))
117 {
118 Ok(cat) => cat,
119 Err(_) => {
120 return HttpResponse::BadRequest()
121 .json(serde_json::json!({"error": "Invalid category"}))
122 }
123 };
124
125 match data
126 .achievement_use_cases
127 .list_achievements_by_category(organization_id, category)
128 .await
129 {
130 Ok(achievements) => HttpResponse::Ok().json(achievements),
131 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
132 }
133}
134
135#[get("/organizations/{organization_id}/achievements/visible")]
142pub async fn list_visible_achievements(
143 data: web::Data<AppState>,
144 auth: AuthenticatedUser,
145 organization_id: web::Path<Uuid>,
146) -> impl Responder {
147 match data
148 .achievement_use_cases
149 .list_visible_achievements(organization_id.into_inner(), auth.user_id)
150 .await
151 {
152 Ok(achievements) => HttpResponse::Ok().json(achievements),
153 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
154 }
155}
156
157#[put("/achievements/{id}")]
166pub async fn update_achievement(
167 data: web::Data<AppState>,
168 auth: AuthenticatedUser,
169 id: web::Path<Uuid>,
170 request: web::Json<UpdateAchievementDto>,
171) -> impl Responder {
172 if auth.role != "superadmin" && auth.role != "syndic" {
173 return HttpResponse::Forbidden().json(serde_json::json!({
174 "error": "Only superadmin or syndic can update achievements"
175 }));
176 }
177 match data
178 .achievement_use_cases
179 .update_achievement(id.into_inner(), request.into_inner())
180 .await
181 {
182 Ok(achievement) => HttpResponse::Ok().json(achievement),
183 Err(e) if e.contains("not found") => {
184 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
185 }
186 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
187 }
188}
189
190#[delete("/achievements/{id}")]
198pub async fn delete_achievement(
199 data: web::Data<AppState>,
200 auth: AuthenticatedUser,
201 id: web::Path<Uuid>,
202) -> impl Responder {
203 if auth.role != "superadmin" && auth.role != "syndic" {
204 return HttpResponse::Forbidden().json(serde_json::json!({
205 "error": "Only superadmin or syndic can delete achievements"
206 }));
207 }
208 match data
209 .achievement_use_cases
210 .delete_achievement(id.into_inner())
211 .await
212 {
213 Ok(_) => HttpResponse::NoContent().finish(),
214 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
215 }
216}
217
218#[derive(Debug, Deserialize)]
223pub struct AwardAchievementRequest {
224 pub achievement_id: Uuid,
225 pub progress_data: Option<String>,
226}
227
228#[post("/users/achievements")]
241pub async fn award_achievement(
242 data: web::Data<AppState>,
243 auth: AuthenticatedUser,
244 request: web::Json<AwardAchievementRequest>,
245) -> impl Responder {
246 let req = request.into_inner();
247 match data
248 .achievement_use_cases
249 .award_achievement(auth.user_id, req.achievement_id, req.progress_data)
250 .await
251 {
252 Ok(user_achievement) => HttpResponse::Created().json(user_achievement),
253 Err(e) if e.contains("not found") => {
254 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
255 }
256 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
257 }
258}
259
260#[get("/users/achievements")]
267pub async fn get_user_achievements(
268 data: web::Data<AppState>,
269 auth: AuthenticatedUser,
270) -> impl Responder {
271 match data
272 .achievement_use_cases
273 .get_user_achievements(auth.user_id)
274 .await
275 {
276 Ok(achievements) => HttpResponse::Ok().json(achievements),
277 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
278 }
279}
280
281#[get("/users/achievements/recent")]
291pub async fn get_recent_achievements(
292 data: web::Data<AppState>,
293 auth: AuthenticatedUser,
294 query: web::Query<serde_json::Value>,
295) -> impl Responder {
296 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(5);
297
298 match data
299 .achievement_use_cases
300 .get_recent_achievements(auth.user_id, limit)
301 .await
302 {
303 Ok(achievements) => HttpResponse::Ok().json(achievements),
304 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
305 }
306}
307
308#[post("/challenges")]
333pub async fn create_challenge(
334 data: web::Data<AppState>,
335 auth: AuthenticatedUser,
336 request: web::Json<CreateChallengeDto>,
337) -> impl Responder {
338 if auth.role != "superadmin" && auth.role != "syndic" {
339 return HttpResponse::Forbidden().json(serde_json::json!({
340 "error": "Only superadmin or syndic can create challenges"
341 }));
342 }
343 match data
344 .challenge_use_cases
345 .create_challenge(request.into_inner())
346 .await
347 {
348 Ok(challenge) => HttpResponse::Created().json(challenge),
349 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
350 }
351}
352
353#[get("/challenges/{id}")]
361pub async fn get_challenge(
362 data: web::Data<AppState>,
363 _auth: AuthenticatedUser,
364 id: web::Path<Uuid>,
365) -> impl Responder {
366 match data
367 .challenge_use_cases
368 .get_challenge(id.into_inner())
369 .await
370 {
371 Ok(challenge) => HttpResponse::Ok().json(challenge),
372 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
373 }
374}
375
376#[get("/organizations/{organization_id}/challenges")]
383pub async fn list_challenges(
384 data: web::Data<AppState>,
385 _auth: AuthenticatedUser,
386 organization_id: web::Path<Uuid>,
387) -> impl Responder {
388 match data
389 .challenge_use_cases
390 .list_challenges(organization_id.into_inner())
391 .await
392 {
393 Ok(challenges) => HttpResponse::Ok().json(challenges),
394 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
395 }
396}
397
398#[get("/organizations/{organization_id}/challenges/status/{status}")]
405pub async fn list_challenges_by_status(
406 data: web::Data<AppState>,
407 _auth: AuthenticatedUser,
408 path: web::Path<(Uuid, String)>,
409) -> impl Responder {
410 let (organization_id, status_str) = path.into_inner();
411
412 let status: ChallengeStatus = match serde_json::from_str(&format!("\"{}\"", status_str)) {
414 Ok(s) => s,
415 Err(_) => {
416 return HttpResponse::BadRequest().json(serde_json::json!({"error": "Invalid status"}))
417 }
418 };
419
420 match data
421 .challenge_use_cases
422 .list_challenges_by_status(organization_id, status)
423 .await
424 {
425 Ok(challenges) => HttpResponse::Ok().json(challenges),
426 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
427 }
428}
429
430#[get("/buildings/{building_id}/challenges")]
437pub async fn list_building_challenges(
438 data: web::Data<AppState>,
439 _auth: AuthenticatedUser,
440 building_id: web::Path<Uuid>,
441) -> impl Responder {
442 match data
443 .challenge_use_cases
444 .list_building_challenges(building_id.into_inner())
445 .await
446 {
447 Ok(challenges) => HttpResponse::Ok().json(challenges),
448 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
449 }
450}
451
452#[get("/organizations/{organization_id}/challenges/active")]
459pub async fn list_active_challenges(
460 data: web::Data<AppState>,
461 _auth: AuthenticatedUser,
462 organization_id: web::Path<Uuid>,
463) -> impl Responder {
464 match data
465 .challenge_use_cases
466 .list_active_challenges(organization_id.into_inner())
467 .await
468 {
469 Ok(challenges) => HttpResponse::Ok().json(challenges),
470 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
471 }
472}
473
474#[put("/challenges/{id}")]
483pub async fn update_challenge(
484 data: web::Data<AppState>,
485 auth: AuthenticatedUser,
486 id: web::Path<Uuid>,
487 request: web::Json<UpdateChallengeDto>,
488) -> impl Responder {
489 if auth.role != "superadmin" && auth.role != "syndic" {
490 return HttpResponse::Forbidden().json(serde_json::json!({
491 "error": "Only superadmin or syndic can update challenges"
492 }));
493 }
494 match data
495 .challenge_use_cases
496 .update_challenge(id.into_inner(), request.into_inner())
497 .await
498 {
499 Ok(challenge) => HttpResponse::Ok().json(challenge),
500 Err(e) if e.contains("not found") => {
501 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
502 }
503 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
504 }
505}
506
507#[put("/challenges/{id}/activate")]
516pub async fn activate_challenge(
517 data: web::Data<AppState>,
518 auth: AuthenticatedUser,
519 id: web::Path<Uuid>,
520) -> impl Responder {
521 if auth.role != "superadmin" && auth.role != "syndic" {
522 return HttpResponse::Forbidden().json(serde_json::json!({
523 "error": "Only superadmin or syndic can activate challenges"
524 }));
525 }
526 match data
527 .challenge_use_cases
528 .activate_challenge(id.into_inner())
529 .await
530 {
531 Ok(challenge) => HttpResponse::Ok().json(challenge),
532 Err(e) if e.contains("not found") => {
533 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
534 }
535 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
536 }
537}
538
539#[put("/challenges/{id}/complete")]
548pub async fn complete_challenge(
549 data: web::Data<AppState>,
550 auth: AuthenticatedUser,
551 id: web::Path<Uuid>,
552) -> impl Responder {
553 if auth.role != "superadmin" && auth.role != "syndic" {
554 return HttpResponse::Forbidden().json(serde_json::json!({
555 "error": "Only superadmin or syndic can complete challenges"
556 }));
557 }
558 match data
559 .challenge_use_cases
560 .complete_challenge(id.into_inner())
561 .await
562 {
563 Ok(challenge) => HttpResponse::Ok().json(challenge),
564 Err(e) if e.contains("not found") => {
565 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
566 }
567 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
568 }
569}
570
571#[put("/challenges/{id}/cancel")]
580pub async fn cancel_challenge(
581 data: web::Data<AppState>,
582 auth: AuthenticatedUser,
583 id: web::Path<Uuid>,
584) -> impl Responder {
585 if auth.role != "superadmin" && auth.role != "syndic" {
586 return HttpResponse::Forbidden().json(serde_json::json!({
587 "error": "Only superadmin or syndic can cancel challenges"
588 }));
589 }
590 match data
591 .challenge_use_cases
592 .cancel_challenge(id.into_inner())
593 .await
594 {
595 Ok(challenge) => HttpResponse::Ok().json(challenge),
596 Err(e) if e.contains("not found") => {
597 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
598 }
599 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
600 }
601}
602
603#[delete("/challenges/{id}")]
611pub async fn delete_challenge(
612 data: web::Data<AppState>,
613 auth: AuthenticatedUser,
614 id: web::Path<Uuid>,
615) -> impl Responder {
616 if auth.role != "superadmin" && auth.role != "syndic" {
617 return HttpResponse::Forbidden().json(serde_json::json!({
618 "error": "Only superadmin or syndic can delete challenges"
619 }));
620 }
621 match data
622 .challenge_use_cases
623 .delete_challenge(id.into_inner())
624 .await
625 {
626 Ok(_) => HttpResponse::NoContent().finish(),
627 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
628 }
629}
630
631#[get("/challenges/{challenge_id}/progress")]
643pub async fn get_challenge_progress(
644 data: web::Data<AppState>,
645 auth: AuthenticatedUser,
646 challenge_id: web::Path<Uuid>,
647) -> impl Responder {
648 match data
649 .challenge_use_cases
650 .get_challenge_progress(auth.user_id, challenge_id.into_inner())
651 .await
652 {
653 Ok(progress) => HttpResponse::Ok().json(progress),
654 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
655 }
656}
657
658#[get("/challenges/{challenge_id}/all-progress")]
665pub async fn list_challenge_progress(
666 data: web::Data<AppState>,
667 _auth: AuthenticatedUser,
668 challenge_id: web::Path<Uuid>,
669) -> impl Responder {
670 match data
671 .challenge_use_cases
672 .list_challenge_progress(challenge_id.into_inner())
673 .await
674 {
675 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
676 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
677 }
678}
679
680#[get("/users/challenges/active")]
687pub async fn list_user_active_challenges(
688 data: web::Data<AppState>,
689 auth: AuthenticatedUser,
690) -> impl Responder {
691 match data
692 .challenge_use_cases
693 .list_user_active_progress(auth.user_id)
694 .await
695 {
696 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
697 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
698 }
699}
700
701#[derive(Debug, Deserialize)]
702pub struct IncrementProgressRequest {
703 pub increment: i32,
704}
705
706#[post("/challenges/{challenge_id}/progress/increment")]
718pub async fn increment_progress(
719 data: web::Data<AppState>,
720 auth: AuthenticatedUser,
721 challenge_id: web::Path<Uuid>,
722 request: web::Json<IncrementProgressRequest>,
723) -> impl Responder {
724 match data
725 .challenge_use_cases
726 .increment_progress(auth.user_id, challenge_id.into_inner(), request.increment)
727 .await
728 {
729 Ok(progress) => HttpResponse::Ok().json(progress),
730 Err(e) if e.contains("not found") => {
731 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
732 }
733 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
734 }
735}
736
737#[get("/organizations/{organization_id}/gamification/stats")]
748pub async fn get_gamification_user_stats(
749 data: web::Data<AppState>,
750 auth: AuthenticatedUser,
751 organization_id: web::Path<Uuid>,
752) -> impl Responder {
753 match data
754 .gamification_stats_use_cases
755 .get_user_stats(auth.user_id, organization_id.into_inner())
756 .await
757 {
758 Ok(stats) => HttpResponse::Ok().json(stats),
759 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
760 }
761}
762
763#[get("/organizations/{organization_id}/gamification/leaderboard")]
774pub async fn get_gamification_leaderboard(
775 data: web::Data<AppState>,
776 _auth: AuthenticatedUser,
777 organization_id: web::Path<Uuid>,
778 query: web::Query<serde_json::Value>,
779) -> impl Responder {
780 let building_id = query
781 .get("building_id")
782 .and_then(|v| v.as_str())
783 .and_then(|s| Uuid::parse_str(s).ok());
784
785 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10);
786
787 match data
788 .gamification_stats_use_cases
789 .get_leaderboard(organization_id.into_inner(), building_id, limit)
790 .await
791 {
792 Ok(leaderboard) => HttpResponse::Ok().json(leaderboard),
793 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
794 }
795}