koprogo_api/infrastructure/web/handlers/
skill_handlers.rs

1use crate::application::dto::{CreateSkillDto, UpdateSkillDto};
2use crate::domain::entities::{ExpertiseLevel, SkillCategory};
3use crate::infrastructure::web::app_state::AppState;
4use crate::infrastructure::web::middleware::AuthenticatedUser;
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use uuid::Uuid;
7
8/// Create a new skill
9///
10/// POST /skills
11#[post("/skills")]
12pub async fn create_skill(
13    data: web::Data<AppState>,
14    auth: AuthenticatedUser,
15    request: web::Json<CreateSkillDto>,
16) -> impl Responder {
17    match data
18        .skill_use_cases
19        .create_skill(auth.user_id, request.into_inner())
20        .await
21    {
22        Ok(skill) => HttpResponse::Created().json(skill),
23        Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
24    }
25}
26
27/// Get skill by ID with owner name enrichment
28///
29/// GET /skills/:id
30#[get("/skills/{id}")]
31pub async fn get_skill(data: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
32    match data.skill_use_cases.get_skill(id.into_inner()).await {
33        Ok(skill) => HttpResponse::Ok().json(skill),
34        Err(e) => {
35            if e.contains("not found") {
36                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
37            } else {
38                HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
39            }
40        }
41    }
42}
43
44/// List all skills for a building
45///
46/// GET /buildings/:building_id/skills
47#[get("/buildings/{building_id}/skills")]
48pub async fn list_building_skills(
49    data: web::Data<AppState>,
50    building_id: web::Path<Uuid>,
51) -> impl Responder {
52    match data
53        .skill_use_cases
54        .list_building_skills(building_id.into_inner())
55        .await
56    {
57        Ok(skills) => HttpResponse::Ok().json(skills),
58        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
59    }
60}
61
62/// List available skills for a building (marketplace view)
63///
64/// GET /buildings/:building_id/skills/available
65#[get("/buildings/{building_id}/skills/available")]
66pub async fn list_available_skills(
67    data: web::Data<AppState>,
68    building_id: web::Path<Uuid>,
69) -> impl Responder {
70    match data
71        .skill_use_cases
72        .list_available_skills(building_id.into_inner())
73        .await
74    {
75        Ok(skills) => HttpResponse::Ok().json(skills),
76        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
77    }
78}
79
80/// List free/volunteer skills for a building
81///
82/// GET /buildings/:building_id/skills/free
83#[get("/buildings/{building_id}/skills/free")]
84pub async fn list_free_skills(
85    data: web::Data<AppState>,
86    building_id: web::Path<Uuid>,
87) -> impl Responder {
88    match data
89        .skill_use_cases
90        .list_free_skills(building_id.into_inner())
91        .await
92    {
93        Ok(skills) => HttpResponse::Ok().json(skills),
94        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
95    }
96}
97
98/// List professional skills for a building
99///
100/// GET /buildings/:building_id/skills/professional
101#[get("/buildings/{building_id}/skills/professional")]
102pub async fn list_professional_skills(
103    data: web::Data<AppState>,
104    building_id: web::Path<Uuid>,
105) -> impl Responder {
106    match data
107        .skill_use_cases
108        .list_professional_skills(building_id.into_inner())
109        .await
110    {
111        Ok(skills) => HttpResponse::Ok().json(skills),
112        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
113    }
114}
115
116/// List skills by category (HomeRepair, Languages, Technology, etc.)
117///
118/// GET /buildings/:building_id/skills/category/:category
119#[get("/buildings/{building_id}/skills/category/{category}")]
120pub async fn list_skills_by_category(
121    data: web::Data<AppState>,
122    path: web::Path<(Uuid, String)>,
123) -> impl Responder {
124    let (building_id, category_str) = path.into_inner();
125
126    // Parse skill category
127    let category = match serde_json::from_str::<SkillCategory>(&format!("\"{}\"", category_str)) {
128        Ok(c) => c,
129        Err(_) => {
130            return HttpResponse::BadRequest().json(serde_json::json!({
131                "error": format!("Invalid skill category: {}. Valid categories: HomeRepair, Languages, Technology, Education, Arts, Sports, Cooking, Gardening, Health, Legal, Financial, PetCare, Other", category_str)
132            }))
133        }
134    };
135
136    match data
137        .skill_use_cases
138        .list_skills_by_category(building_id, category)
139        .await
140    {
141        Ok(skills) => HttpResponse::Ok().json(skills),
142        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
143    }
144}
145
146/// List skills by expertise level (Beginner, Intermediate, Advanced, Expert)
147///
148/// GET /buildings/:building_id/skills/expertise/:level
149#[get("/buildings/{building_id}/skills/expertise/{level}")]
150pub async fn list_skills_by_expertise(
151    data: web::Data<AppState>,
152    path: web::Path<(Uuid, String)>,
153) -> impl Responder {
154    let (building_id, level_str) = path.into_inner();
155
156    // Parse expertise level
157    let level = match serde_json::from_str::<ExpertiseLevel>(&format!("\"{}\"", level_str)) {
158        Ok(l) => l,
159        Err(_) => {
160            return HttpResponse::BadRequest().json(serde_json::json!({
161                "error": format!("Invalid expertise level: {}. Valid levels: Beginner, Intermediate, Advanced, Expert", level_str)
162            }))
163        }
164    };
165
166    match data
167        .skill_use_cases
168        .list_skills_by_expertise(building_id, level)
169        .await
170    {
171        Ok(skills) => HttpResponse::Ok().json(skills),
172        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
173    }
174}
175
176/// List all skills created by an owner
177///
178/// GET /owners/:owner_id/skills
179#[get("/owners/{owner_id}/skills")]
180pub async fn list_owner_skills(
181    data: web::Data<AppState>,
182    owner_id: web::Path<Uuid>,
183) -> impl Responder {
184    match data
185        .skill_use_cases
186        .list_owner_skills(owner_id.into_inner())
187        .await
188    {
189        Ok(skills) => HttpResponse::Ok().json(skills),
190        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
191    }
192}
193
194/// Update a skill
195///
196/// PUT /skills/:id
197#[put("/skills/{id}")]
198pub async fn update_skill(
199    data: web::Data<AppState>,
200    auth: AuthenticatedUser,
201    id: web::Path<Uuid>,
202    request: web::Json<UpdateSkillDto>,
203) -> impl Responder {
204    match data
205        .skill_use_cases
206        .update_skill(id.into_inner(), auth.user_id, request.into_inner())
207        .await
208    {
209        Ok(skill) => HttpResponse::Ok().json(skill),
210        Err(e) => {
211            if e.contains("Unauthorized") {
212                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
213            } else if e.contains("not found") {
214                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
215            } else {
216                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
217            }
218        }
219    }
220}
221
222/// Mark skill as available for help
223///
224/// POST /skills/:id/mark-available
225#[post("/skills/{id}/mark-available")]
226pub async fn mark_skill_available(
227    data: web::Data<AppState>,
228    auth: AuthenticatedUser,
229    id: web::Path<Uuid>,
230) -> impl Responder {
231    match data
232        .skill_use_cases
233        .mark_skill_available(id.into_inner(), auth.user_id)
234        .await
235    {
236        Ok(skill) => HttpResponse::Ok().json(skill),
237        Err(e) => {
238            if e.contains("Unauthorized") {
239                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
240            } else if e.contains("not found") {
241                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
242            } else {
243                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
244            }
245        }
246    }
247}
248
249/// Mark skill as unavailable for help
250///
251/// POST /skills/:id/mark-unavailable
252#[post("/skills/{id}/mark-unavailable")]
253pub async fn mark_skill_unavailable(
254    data: web::Data<AppState>,
255    auth: AuthenticatedUser,
256    id: web::Path<Uuid>,
257) -> impl Responder {
258    match data
259        .skill_use_cases
260        .mark_skill_unavailable(id.into_inner(), auth.user_id)
261        .await
262    {
263        Ok(skill) => HttpResponse::Ok().json(skill),
264        Err(e) => {
265            if e.contains("Unauthorized") {
266                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
267            } else if e.contains("not found") {
268                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
269            } else {
270                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
271            }
272        }
273    }
274}
275
276/// Delete a skill
277///
278/// DELETE /skills/:id
279#[delete("/skills/{id}")]
280pub async fn delete_skill(
281    data: web::Data<AppState>,
282    auth: AuthenticatedUser,
283    id: web::Path<Uuid>,
284) -> impl Responder {
285    match data
286        .skill_use_cases
287        .delete_skill(id.into_inner(), auth.user_id)
288        .await
289    {
290        Ok(_) => HttpResponse::NoContent().finish(),
291        Err(e) => {
292            if e.contains("Unauthorized") {
293                HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
294            } else if e.contains("not found") {
295                HttpResponse::NotFound().json(serde_json::json!({"error": e}))
296            } else {
297                HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
298            }
299        }
300    }
301}
302
303/// Get skill statistics for a building
304///
305/// GET /buildings/:building_id/skills/statistics
306#[get("/buildings/{building_id}/skills/statistics")]
307pub async fn get_skill_statistics(
308    data: web::Data<AppState>,
309    building_id: web::Path<Uuid>,
310) -> impl Responder {
311    match data
312        .skill_use_cases
313        .get_skill_statistics(building_id.into_inner())
314        .await
315    {
316        Ok(stats) => HttpResponse::Ok().json(stats),
317        Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
318    }
319}