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 if let Err(e) = auth.verify_org_access(*organization_id) {
92 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
93 }
94 match data
95 .achievement_use_cases
96 .list_achievements(organization_id.into_inner())
97 .await
98 {
99 Ok(achievements) => HttpResponse::Ok().json(achievements),
100 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
101 }
102}
103
104#[get("/organizations/{organization_id}/achievements/category/{category}")]
111pub async fn list_achievements_by_category(
112 data: web::Data<AppState>,
113 auth: AuthenticatedUser,
114 path: web::Path<(Uuid, String)>,
115) -> impl Responder {
116 let (organization_id, category_str) = path.into_inner();
117 if let Err(e) = auth.verify_org_access(organization_id) {
118 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
119 }
120
121 let category: AchievementCategory = match serde_json::from_str(&format!("\"{}\"", category_str))
123 {
124 Ok(cat) => cat,
125 Err(_) => {
126 return HttpResponse::BadRequest()
127 .json(serde_json::json!({"error": "Invalid category"}))
128 }
129 };
130
131 match data
132 .achievement_use_cases
133 .list_achievements_by_category(organization_id, category)
134 .await
135 {
136 Ok(achievements) => HttpResponse::Ok().json(achievements),
137 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
138 }
139}
140
141#[get("/organizations/{organization_id}/achievements/visible")]
148pub async fn list_visible_achievements(
149 data: web::Data<AppState>,
150 auth: AuthenticatedUser,
151 organization_id: web::Path<Uuid>,
152) -> impl Responder {
153 if let Err(e) = auth.verify_org_access(*organization_id) {
154 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
155 }
156 match data
157 .achievement_use_cases
158 .list_visible_achievements(organization_id.into_inner(), auth.user_id)
159 .await
160 {
161 Ok(achievements) => HttpResponse::Ok().json(achievements),
162 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
163 }
164}
165
166#[put("/achievements/{id}")]
175pub async fn update_achievement(
176 data: web::Data<AppState>,
177 auth: AuthenticatedUser,
178 id: web::Path<Uuid>,
179 request: web::Json<UpdateAchievementDto>,
180) -> impl Responder {
181 if auth.role != "superadmin" && auth.role != "syndic" {
182 return HttpResponse::Forbidden().json(serde_json::json!({
183 "error": "Only superadmin or syndic can update achievements"
184 }));
185 }
186 match data
187 .achievement_use_cases
188 .update_achievement(id.into_inner(), request.into_inner())
189 .await
190 {
191 Ok(achievement) => HttpResponse::Ok().json(achievement),
192 Err(e) if e.contains("not found") => {
193 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
194 }
195 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
196 }
197}
198
199#[delete("/achievements/{id}")]
207pub async fn delete_achievement(
208 data: web::Data<AppState>,
209 auth: AuthenticatedUser,
210 id: web::Path<Uuid>,
211) -> impl Responder {
212 if auth.role != "superadmin" && auth.role != "syndic" {
213 return HttpResponse::Forbidden().json(serde_json::json!({
214 "error": "Only superadmin or syndic can delete achievements"
215 }));
216 }
217 match data
218 .achievement_use_cases
219 .delete_achievement(id.into_inner())
220 .await
221 {
222 Ok(_) => HttpResponse::NoContent().finish(),
223 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
224 }
225}
226
227#[derive(Debug, Deserialize)]
232pub struct AwardAchievementRequest {
233 pub achievement_id: Uuid,
234 pub progress_data: Option<String>,
235}
236
237#[post("/users/achievements")]
250pub async fn award_achievement(
251 data: web::Data<AppState>,
252 auth: AuthenticatedUser,
253 request: web::Json<AwardAchievementRequest>,
254) -> impl Responder {
255 let req = request.into_inner();
256 match data
257 .achievement_use_cases
258 .award_achievement(auth.user_id, req.achievement_id, req.progress_data)
259 .await
260 {
261 Ok(user_achievement) => HttpResponse::Created().json(user_achievement),
262 Err(e) if e.contains("not found") => {
263 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
264 }
265 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
266 }
267}
268
269#[get("/users/achievements")]
276pub async fn get_user_achievements(
277 data: web::Data<AppState>,
278 auth: AuthenticatedUser,
279) -> impl Responder {
280 match data
281 .achievement_use_cases
282 .get_user_achievements(auth.user_id)
283 .await
284 {
285 Ok(achievements) => HttpResponse::Ok().json(achievements),
286 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
287 }
288}
289
290#[get("/users/achievements/recent")]
300pub async fn get_recent_achievements(
301 data: web::Data<AppState>,
302 auth: AuthenticatedUser,
303 query: web::Query<serde_json::Value>,
304) -> impl Responder {
305 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(5);
306
307 match data
308 .achievement_use_cases
309 .get_recent_achievements(auth.user_id, limit)
310 .await
311 {
312 Ok(achievements) => HttpResponse::Ok().json(achievements),
313 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
314 }
315}
316
317#[post("/challenges")]
342pub async fn create_challenge(
343 data: web::Data<AppState>,
344 auth: AuthenticatedUser,
345 request: web::Json<CreateChallengeDto>,
346) -> impl Responder {
347 if auth.role != "superadmin" && auth.role != "syndic" {
348 return HttpResponse::Forbidden().json(serde_json::json!({
349 "error": "Only superadmin or syndic can create challenges"
350 }));
351 }
352 match data
353 .challenge_use_cases
354 .create_challenge(request.into_inner())
355 .await
356 {
357 Ok(challenge) => HttpResponse::Created().json(challenge),
358 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
359 }
360}
361
362#[get("/challenges/{id}")]
370pub async fn get_challenge(
371 data: web::Data<AppState>,
372 _auth: AuthenticatedUser,
373 id: web::Path<Uuid>,
374) -> impl Responder {
375 match data
376 .challenge_use_cases
377 .get_challenge(id.into_inner())
378 .await
379 {
380 Ok(challenge) => HttpResponse::Ok().json(challenge),
381 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
382 }
383}
384
385#[get("/organizations/{organization_id}/challenges")]
392pub async fn list_challenges(
393 data: web::Data<AppState>,
394 auth: AuthenticatedUser,
395 organization_id: web::Path<Uuid>,
396) -> impl Responder {
397 if let Err(e) = auth.verify_org_access(*organization_id) {
398 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
399 }
400 match data
401 .challenge_use_cases
402 .list_challenges(organization_id.into_inner())
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("/organizations/{organization_id}/challenges/status/{status}")]
417pub async fn list_challenges_by_status(
418 data: web::Data<AppState>,
419 auth: AuthenticatedUser,
420 path: web::Path<(Uuid, String)>,
421) -> impl Responder {
422 let (organization_id, status_str) = path.into_inner();
423 if let Err(e) = auth.verify_org_access(organization_id) {
424 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
425 }
426
427 let status: ChallengeStatus = match serde_json::from_str(&format!("\"{}\"", status_str)) {
429 Ok(s) => s,
430 Err(_) => {
431 return HttpResponse::BadRequest().json(serde_json::json!({"error": "Invalid status"}))
432 }
433 };
434
435 match data
436 .challenge_use_cases
437 .list_challenges_by_status(organization_id, status)
438 .await
439 {
440 Ok(challenges) => HttpResponse::Ok().json(challenges),
441 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
442 }
443}
444
445#[get("/buildings/{building_id}/challenges")]
452pub async fn list_building_challenges(
453 data: web::Data<AppState>,
454 _auth: AuthenticatedUser,
455 building_id: web::Path<Uuid>,
456) -> impl Responder {
457 match data
458 .challenge_use_cases
459 .list_building_challenges(building_id.into_inner())
460 .await
461 {
462 Ok(challenges) => HttpResponse::Ok().json(challenges),
463 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
464 }
465}
466
467#[get("/organizations/{organization_id}/challenges/active")]
474pub async fn list_active_challenges(
475 data: web::Data<AppState>,
476 auth: AuthenticatedUser,
477 organization_id: web::Path<Uuid>,
478) -> impl Responder {
479 if let Err(e) = auth.verify_org_access(*organization_id) {
480 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
481 }
482 match data
483 .challenge_use_cases
484 .list_active_challenges(organization_id.into_inner())
485 .await
486 {
487 Ok(challenges) => HttpResponse::Ok().json(challenges),
488 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
489 }
490}
491
492#[put("/challenges/{id}")]
501pub async fn update_challenge(
502 data: web::Data<AppState>,
503 auth: AuthenticatedUser,
504 id: web::Path<Uuid>,
505 request: web::Json<UpdateChallengeDto>,
506) -> impl Responder {
507 if auth.role != "superadmin" && auth.role != "syndic" {
508 return HttpResponse::Forbidden().json(serde_json::json!({
509 "error": "Only superadmin or syndic can update challenges"
510 }));
511 }
512 match data
513 .challenge_use_cases
514 .update_challenge(id.into_inner(), request.into_inner())
515 .await
516 {
517 Ok(challenge) => HttpResponse::Ok().json(challenge),
518 Err(e) if e.contains("not found") => {
519 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
520 }
521 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
522 }
523}
524
525#[put("/challenges/{id}/activate")]
534pub async fn activate_challenge(
535 data: web::Data<AppState>,
536 auth: AuthenticatedUser,
537 id: web::Path<Uuid>,
538) -> impl Responder {
539 if auth.role != "superadmin" && auth.role != "syndic" {
540 return HttpResponse::Forbidden().json(serde_json::json!({
541 "error": "Only superadmin or syndic can activate challenges"
542 }));
543 }
544 match data
545 .challenge_use_cases
546 .activate_challenge(id.into_inner())
547 .await
548 {
549 Ok(challenge) => HttpResponse::Ok().json(challenge),
550 Err(e) if e.contains("not found") => {
551 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
552 }
553 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
554 }
555}
556
557#[put("/challenges/{id}/complete")]
566pub async fn complete_challenge(
567 data: web::Data<AppState>,
568 auth: AuthenticatedUser,
569 id: web::Path<Uuid>,
570) -> impl Responder {
571 if auth.role != "superadmin" && auth.role != "syndic" {
572 return HttpResponse::Forbidden().json(serde_json::json!({
573 "error": "Only superadmin or syndic can complete challenges"
574 }));
575 }
576 match data
577 .challenge_use_cases
578 .complete_challenge(id.into_inner())
579 .await
580 {
581 Ok(challenge) => HttpResponse::Ok().json(challenge),
582 Err(e) if e.contains("not found") => {
583 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
584 }
585 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
586 }
587}
588
589#[put("/challenges/{id}/cancel")]
598pub async fn cancel_challenge(
599 data: web::Data<AppState>,
600 auth: AuthenticatedUser,
601 id: web::Path<Uuid>,
602) -> impl Responder {
603 if auth.role != "superadmin" && auth.role != "syndic" {
604 return HttpResponse::Forbidden().json(serde_json::json!({
605 "error": "Only superadmin or syndic can cancel challenges"
606 }));
607 }
608 match data
609 .challenge_use_cases
610 .cancel_challenge(id.into_inner())
611 .await
612 {
613 Ok(challenge) => HttpResponse::Ok().json(challenge),
614 Err(e) if e.contains("not found") => {
615 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
616 }
617 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
618 }
619}
620
621#[delete("/challenges/{id}")]
629pub async fn delete_challenge(
630 data: web::Data<AppState>,
631 auth: AuthenticatedUser,
632 id: web::Path<Uuid>,
633) -> impl Responder {
634 if auth.role != "superadmin" && auth.role != "syndic" {
635 return HttpResponse::Forbidden().json(serde_json::json!({
636 "error": "Only superadmin or syndic can delete challenges"
637 }));
638 }
639 match data
640 .challenge_use_cases
641 .delete_challenge(id.into_inner())
642 .await
643 {
644 Ok(_) => HttpResponse::NoContent().finish(),
645 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
646 }
647}
648
649#[get("/challenges/{challenge_id}/progress")]
661pub async fn get_challenge_progress(
662 data: web::Data<AppState>,
663 auth: AuthenticatedUser,
664 challenge_id: web::Path<Uuid>,
665) -> impl Responder {
666 match data
667 .challenge_use_cases
668 .get_challenge_progress(auth.user_id, challenge_id.into_inner())
669 .await
670 {
671 Ok(progress) => HttpResponse::Ok().json(progress),
672 Err(e) => HttpResponse::NotFound().json(serde_json::json!({"error": e})),
673 }
674}
675
676#[get("/challenges/{challenge_id}/all-progress")]
683pub async fn list_challenge_progress(
684 data: web::Data<AppState>,
685 _auth: AuthenticatedUser,
686 challenge_id: web::Path<Uuid>,
687) -> impl Responder {
688 match data
689 .challenge_use_cases
690 .list_challenge_progress(challenge_id.into_inner())
691 .await
692 {
693 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
694 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
695 }
696}
697
698#[get("/users/challenges/active")]
705pub async fn list_user_active_challenges(
706 data: web::Data<AppState>,
707 auth: AuthenticatedUser,
708) -> impl Responder {
709 match data
710 .challenge_use_cases
711 .list_user_active_progress(auth.user_id)
712 .await
713 {
714 Ok(progress_list) => HttpResponse::Ok().json(progress_list),
715 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
716 }
717}
718
719#[derive(Debug, Deserialize)]
720pub struct IncrementProgressRequest {
721 pub increment: i32,
722}
723
724#[post("/challenges/{challenge_id}/progress/increment")]
736pub async fn increment_progress(
737 data: web::Data<AppState>,
738 auth: AuthenticatedUser,
739 challenge_id: web::Path<Uuid>,
740 request: web::Json<IncrementProgressRequest>,
741) -> impl Responder {
742 match data
743 .challenge_use_cases
744 .increment_progress(auth.user_id, challenge_id.into_inner(), request.increment)
745 .await
746 {
747 Ok(progress) => HttpResponse::Ok().json(progress),
748 Err(e) if e.contains("not found") => {
749 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
750 }
751 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
752 }
753}
754
755#[get("/organizations/{organization_id}/gamification/stats")]
766pub async fn get_gamification_user_stats(
767 data: web::Data<AppState>,
768 auth: AuthenticatedUser,
769 organization_id: web::Path<Uuid>,
770) -> impl Responder {
771 if let Err(e) = auth.verify_org_access(*organization_id) {
772 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
773 }
774 match data
775 .gamification_stats_use_cases
776 .get_user_stats(auth.user_id, organization_id.into_inner())
777 .await
778 {
779 Ok(stats) => HttpResponse::Ok().json(stats),
780 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
781 }
782}
783
784#[get("/organizations/{organization_id}/gamification/leaderboard")]
795pub async fn get_gamification_leaderboard(
796 data: web::Data<AppState>,
797 auth: AuthenticatedUser,
798 organization_id: web::Path<Uuid>,
799 query: web::Query<serde_json::Value>,
800) -> impl Responder {
801 if let Err(e) = auth.verify_org_access(*organization_id) {
802 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
803 }
804 let building_id = query
805 .get("building_id")
806 .and_then(|v| v.as_str())
807 .and_then(|s| Uuid::parse_str(s).ok());
808
809 let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10);
810
811 match data
812 .gamification_stats_use_cases
813 .get_leaderboard(organization_id.into_inner(), building_id, limit)
814 .await
815 {
816 Ok(leaderboard) => HttpResponse::Ok().json(leaderboard),
817 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
818 }
819}