koprogo_api/infrastructure/web/handlers/
gamification_handlers.rs

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// ============================================================================
12// ACHIEVEMENT HANDLERS
13// ============================================================================
14
15/// Create a new achievement (admin only)
16///
17/// POST /achievements
18///
19/// # Request Body
20/// - organization_id: UUID
21/// - category: AchievementCategory
22/// - tier: AchievementTier
23/// - name: String (3-100 chars)
24/// - description: String (10-500 chars)
25/// - icon: String (emoji or URL)
26/// - points_value: i32 (0-1000)
27/// - requirements: String (JSON criteria)
28/// - is_secret: bool
29/// - is_repeatable: bool
30/// - display_order: i32
31///
32/// # Responses
33/// - 201 Created: Achievement created successfully
34/// - 400 Bad Request: Validation error
35#[post("/achievements")]
36pub async fn create_achievement(
37    data: web::Data<AppState>,
38    _auth: AuthenticatedUser, // TODO: Check admin role
39    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 achievement by ID
52///
53/// GET /achievements/:id
54///
55/// # Responses
56/// - 200 OK: Achievement details
57/// - 404 Not Found: Achievement not found
58#[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/// List all achievements for an organization
75///
76/// GET /organizations/:organization_id/achievements
77///
78/// # Responses
79/// - 200 OK: List of achievements
80#[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/// List achievements by category
97///
98/// GET /organizations/:organization_id/achievements/category/:category
99///
100/// # Responses
101/// - 200 OK: List of achievements in category
102#[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    // Parse category
111    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/// List visible achievements for current user
131///
132/// GET /organizations/:organization_id/achievements/visible
133///
134/// # Responses
135/// - 200 OK: List of visible achievements (non-secret or already earned)
136#[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/// Update achievement (admin only)
153///
154/// PUT /achievements/:id
155///
156/// # Responses
157/// - 200 OK: Achievement updated successfully
158/// - 400 Bad Request: Validation error
159/// - 404 Not Found: Achievement not found
160#[put("/achievements/{id}")]
161pub async fn update_achievement(
162    data: web::Data<AppState>,
163    _auth: AuthenticatedUser, // TODO: Check admin role
164    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 achievement (admin only)
181///
182/// DELETE /achievements/:id
183///
184/// # Responses
185/// - 204 No Content: Achievement deleted successfully
186/// - 404 Not Found: Achievement not found
187#[delete("/achievements/{id}")]
188pub async fn delete_achievement(
189    data: web::Data<AppState>,
190    _auth: AuthenticatedUser, // TODO: Check admin role
191    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// ============================================================================
204// USER ACHIEVEMENT HANDLERS
205// ============================================================================
206
207#[derive(Debug, Deserialize)]
208pub struct AwardAchievementRequest {
209    pub achievement_id: Uuid,
210    pub progress_data: Option<String>,
211}
212
213/// Award achievement to user
214///
215/// POST /users/achievements
216///
217/// # Request Body
218/// - achievement_id: UUID
219/// - progress_data: Option<String> (JSON)
220///
221/// # Responses
222/// - 201 Created: Achievement awarded successfully
223/// - 400 Bad Request: Already earned (non-repeatable) or validation error
224/// - 404 Not Found: Achievement not found
225#[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 all achievements earned by current user
246///
247/// GET /users/achievements
248///
249/// # Responses
250/// - 200 OK: List of user achievements with enriched achievement data
251#[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 recent achievements for current user
267///
268/// GET /users/achievements/recent?limit=5
269///
270/// # Query Parameters
271/// - limit: i64 (default: 5)
272///
273/// # Responses
274/// - 200 OK: List of recent achievements
275#[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// ============================================================================
294// CHALLENGE HANDLERS
295// ============================================================================
296
297/// Create a new challenge (admin only)
298///
299/// POST /challenges
300///
301/// # Request Body
302/// - organization_id: UUID
303/// - building_id: Option<UUID> (null = organization-wide)
304/// - challenge_type: ChallengeType (Individual, Team, Building)
305/// - title: String (3-100 chars)
306/// - description: String (10-1000 chars)
307/// - icon: String
308/// - start_date: DateTime<Utc>
309/// - end_date: DateTime<Utc>
310/// - target_metric: String (e.g., "bookings_created")
311/// - target_value: i32
312/// - reward_points: i32 (0-10000)
313///
314/// # Responses
315/// - 201 Created: Challenge created successfully (Draft status)
316/// - 400 Bad Request: Validation error
317#[post("/challenges")]
318pub async fn create_challenge(
319    data: web::Data<AppState>,
320    _auth: AuthenticatedUser, // TODO: Check admin role
321    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 challenge by ID
334///
335/// GET /challenges/:id
336///
337/// # Responses
338/// - 200 OK: Challenge details
339/// - 404 Not Found: Challenge not found
340#[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/// List all challenges for an organization
357///
358/// GET /organizations/:organization_id/challenges
359///
360/// # Responses
361/// - 200 OK: List of challenges
362#[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/// List challenges by status
379///
380/// GET /organizations/:organization_id/challenges/status/:status
381///
382/// # Responses
383/// - 200 OK: List of challenges with specified status
384#[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    // Parse status
393    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/// List challenges for a building
411///
412/// GET /buildings/:building_id/challenges
413///
414/// # Responses
415/// - 200 OK: List of building challenges
416#[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/// List active challenges (Active status + date range)
433///
434/// GET /organizations/:organization_id/challenges/active
435///
436/// # Responses
437/// - 200 OK: List of currently active challenges
438#[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/// Update challenge (Draft only, admin only)
455///
456/// PUT /challenges/:id
457///
458/// # Responses
459/// - 200 OK: Challenge updated successfully
460/// - 400 Bad Request: Validation error or not Draft status
461/// - 404 Not Found: Challenge not found
462#[put("/challenges/{id}")]
463pub async fn update_challenge(
464    data: web::Data<AppState>,
465    _auth: AuthenticatedUser, // TODO: Check admin role
466    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/// Activate challenge (Draft → Active, admin only)
483///
484/// PUT /challenges/:id/activate
485///
486/// # Responses
487/// - 200 OK: Challenge activated successfully
488/// - 400 Bad Request: Invalid state transition
489/// - 404 Not Found: Challenge not found
490#[put("/challenges/{id}/activate")]
491pub async fn activate_challenge(
492    data: web::Data<AppState>,
493    _auth: AuthenticatedUser, // TODO: Check admin role
494    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/// Complete challenge (Active → Completed, admin only)
510///
511/// PUT /challenges/:id/complete
512///
513/// # Responses
514/// - 200 OK: Challenge completed successfully
515/// - 400 Bad Request: Invalid state transition
516/// - 404 Not Found: Challenge not found
517#[put("/challenges/{id}/complete")]
518pub async fn complete_challenge(
519    data: web::Data<AppState>,
520    _auth: AuthenticatedUser, // TODO: Check admin role
521    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/// Cancel challenge (Draft/Active → Cancelled, admin only)
537///
538/// PUT /challenges/:id/cancel
539///
540/// # Responses
541/// - 200 OK: Challenge cancelled successfully
542/// - 400 Bad Request: Invalid state transition
543/// - 404 Not Found: Challenge not found
544#[put("/challenges/{id}/cancel")]
545pub async fn cancel_challenge(
546    data: web::Data<AppState>,
547    _auth: AuthenticatedUser, // TODO: Check admin role
548    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 challenge (admin only)
564///
565/// DELETE /challenges/:id
566///
567/// # Responses
568/// - 204 No Content: Challenge deleted successfully
569/// - 404 Not Found: Challenge not found
570#[delete("/challenges/{id}")]
571pub async fn delete_challenge(
572    data: web::Data<AppState>,
573    _auth: AuthenticatedUser, // TODO: Check admin role
574    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// ============================================================================
587// CHALLENGE PROGRESS HANDLERS
588// ============================================================================
589
590/// Get user progress for a challenge
591///
592/// GET /challenges/:challenge_id/progress
593///
594/// # Responses
595/// - 200 OK: User progress details
596/// - 404 Not Found: Progress or challenge not found
597#[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/// List all progress for a challenge
614///
615/// GET /challenges/:challenge_id/all-progress
616///
617/// # Responses
618/// - 200 OK: List of all user progress for challenge
619#[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/// List active challenges for current user with progress
636///
637/// GET /users/challenges/active
638///
639/// # Responses
640/// - 200 OK: List of active challenges with user progress
641#[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/// Increment user progress for a challenge
662///
663/// POST /challenges/:challenge_id/progress/increment
664///
665/// # Request Body
666/// - increment: i32 (value to add to current progress)
667///
668/// # Responses
669/// - 200 OK: Progress incremented successfully (auto-completes if target reached)
670/// - 400 Bad Request: Validation error
671/// - 404 Not Found: Challenge not found
672#[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// ============================================================================
693// GAMIFICATION STATS HANDLERS
694// ============================================================================
695
696/// Get comprehensive gamification stats for current user
697///
698/// GET /organizations/:organization_id/gamification/stats
699///
700/// # Responses
701/// - 200 OK: User gamification statistics
702#[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 leaderboard for organization or building
719///
720/// GET /organizations/:organization_id/gamification/leaderboard?building_id=<uuid>&limit=10
721///
722/// # Query Parameters
723/// - building_id: Option<UUID> (filter by building)
724/// - limit: i64 (default: 10)
725///
726/// # Responses
727/// - 200 OK: Leaderboard with top users
728#[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}