koprogo_api/infrastructure/web/handlers/
skill_handlers.rs1use 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#[post("/skills")]
12pub async fn create_skill(
13 data: web::Data<AppState>,
14 auth: AuthenticatedUser,
15 request: web::Json<CreateSkillDto>,
16) -> impl Responder {
17 let org_id = match auth.require_organization() {
18 Ok(id) => id,
19 Err(e) => {
20 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
21 }
22 };
23 match data
24 .skill_use_cases
25 .create_skill(auth.user_id, org_id, request.into_inner())
26 .await
27 {
28 Ok(skill) => HttpResponse::Created().json(skill),
29 Err(e) => HttpResponse::BadRequest().json(serde_json::json!({"error": e})),
30 }
31}
32
33#[get("/skills/{id}")]
37pub async fn get_skill(data: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
38 match data.skill_use_cases.get_skill(id.into_inner()).await {
39 Ok(skill) => HttpResponse::Ok().json(skill),
40 Err(e) => {
41 if e.contains("not found") {
42 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
43 } else {
44 HttpResponse::InternalServerError().json(serde_json::json!({"error": e}))
45 }
46 }
47 }
48}
49
50#[get("/buildings/{building_id}/skills")]
54pub async fn list_building_skills(
55 data: web::Data<AppState>,
56 building_id: web::Path<Uuid>,
57) -> impl Responder {
58 match data
59 .skill_use_cases
60 .list_building_skills(building_id.into_inner())
61 .await
62 {
63 Ok(skills) => HttpResponse::Ok().json(skills),
64 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
65 }
66}
67
68#[get("/buildings/{building_id}/skills/available")]
72pub async fn list_available_skills(
73 data: web::Data<AppState>,
74 building_id: web::Path<Uuid>,
75) -> impl Responder {
76 match data
77 .skill_use_cases
78 .list_available_skills(building_id.into_inner())
79 .await
80 {
81 Ok(skills) => HttpResponse::Ok().json(skills),
82 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
83 }
84}
85
86#[get("/buildings/{building_id}/skills/free")]
90pub async fn list_free_skills(
91 data: web::Data<AppState>,
92 building_id: web::Path<Uuid>,
93) -> impl Responder {
94 match data
95 .skill_use_cases
96 .list_free_skills(building_id.into_inner())
97 .await
98 {
99 Ok(skills) => HttpResponse::Ok().json(skills),
100 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
101 }
102}
103
104#[get("/buildings/{building_id}/skills/professional")]
108pub async fn list_professional_skills(
109 data: web::Data<AppState>,
110 building_id: web::Path<Uuid>,
111) -> impl Responder {
112 match data
113 .skill_use_cases
114 .list_professional_skills(building_id.into_inner())
115 .await
116 {
117 Ok(skills) => HttpResponse::Ok().json(skills),
118 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
119 }
120}
121
122#[get("/buildings/{building_id}/skills/category/{category}")]
126pub async fn list_skills_by_category(
127 data: web::Data<AppState>,
128 path: web::Path<(Uuid, String)>,
129) -> impl Responder {
130 let (building_id, category_str) = path.into_inner();
131
132 let category = match serde_json::from_str::<SkillCategory>(&format!("\"{}\"", category_str)) {
134 Ok(c) => c,
135 Err(_) => {
136 return HttpResponse::BadRequest().json(serde_json::json!({
137 "error": format!("Invalid skill category: {}. Valid categories: HomeRepair, Languages, Technology, Education, Arts, Sports, Cooking, Gardening, Health, Legal, Financial, PetCare, Other", category_str)
138 }))
139 }
140 };
141
142 match data
143 .skill_use_cases
144 .list_skills_by_category(building_id, category)
145 .await
146 {
147 Ok(skills) => HttpResponse::Ok().json(skills),
148 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
149 }
150}
151
152#[get("/buildings/{building_id}/skills/expertise/{level}")]
156pub async fn list_skills_by_expertise(
157 data: web::Data<AppState>,
158 path: web::Path<(Uuid, String)>,
159) -> impl Responder {
160 let (building_id, level_str) = path.into_inner();
161
162 let level = match serde_json::from_str::<ExpertiseLevel>(&format!("\"{}\"", level_str)) {
164 Ok(l) => l,
165 Err(_) => {
166 return HttpResponse::BadRequest().json(serde_json::json!({
167 "error": format!("Invalid expertise level: {}. Valid levels: Beginner, Intermediate, Advanced, Expert", level_str)
168 }))
169 }
170 };
171
172 match data
173 .skill_use_cases
174 .list_skills_by_expertise(building_id, level)
175 .await
176 {
177 Ok(skills) => HttpResponse::Ok().json(skills),
178 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
179 }
180}
181
182#[get("/owners/{owner_id}/skills")]
186pub async fn list_owner_skills(
187 data: web::Data<AppState>,
188 owner_id: web::Path<Uuid>,
189) -> impl Responder {
190 match data
191 .skill_use_cases
192 .list_owner_skills(owner_id.into_inner())
193 .await
194 {
195 Ok(skills) => HttpResponse::Ok().json(skills),
196 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
197 }
198}
199
200#[put("/skills/{id}")]
204pub async fn update_skill(
205 data: web::Data<AppState>,
206 auth: AuthenticatedUser,
207 id: web::Path<Uuid>,
208 request: web::Json<UpdateSkillDto>,
209) -> impl Responder {
210 let org_id = match auth.require_organization() {
211 Ok(id) => id,
212 Err(e) => {
213 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
214 }
215 };
216 match data
217 .skill_use_cases
218 .update_skill(id.into_inner(), auth.user_id, org_id, request.into_inner())
219 .await
220 {
221 Ok(skill) => HttpResponse::Ok().json(skill),
222 Err(e) => {
223 if e.contains("Unauthorized") {
224 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
225 } else if e.contains("not found") {
226 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
227 } else {
228 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
229 }
230 }
231 }
232}
233
234#[post("/skills/{id}/mark-available")]
238pub async fn mark_skill_available(
239 data: web::Data<AppState>,
240 auth: AuthenticatedUser,
241 id: web::Path<Uuid>,
242) -> impl Responder {
243 let org_id = match auth.require_organization() {
244 Ok(id) => id,
245 Err(e) => {
246 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
247 }
248 };
249 match data
250 .skill_use_cases
251 .mark_skill_available(id.into_inner(), auth.user_id, org_id)
252 .await
253 {
254 Ok(skill) => HttpResponse::Ok().json(skill),
255 Err(e) => {
256 if e.contains("Unauthorized") {
257 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
258 } else if e.contains("not found") {
259 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
260 } else {
261 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
262 }
263 }
264 }
265}
266
267#[post("/skills/{id}/mark-unavailable")]
271pub async fn mark_skill_unavailable(
272 data: web::Data<AppState>,
273 auth: AuthenticatedUser,
274 id: web::Path<Uuid>,
275) -> impl Responder {
276 let org_id = match auth.require_organization() {
277 Ok(id) => id,
278 Err(e) => {
279 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
280 }
281 };
282 match data
283 .skill_use_cases
284 .mark_skill_unavailable(id.into_inner(), auth.user_id, org_id)
285 .await
286 {
287 Ok(skill) => HttpResponse::Ok().json(skill),
288 Err(e) => {
289 if e.contains("Unauthorized") {
290 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
291 } else if e.contains("not found") {
292 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
293 } else {
294 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
295 }
296 }
297 }
298}
299
300#[delete("/skills/{id}")]
304pub async fn delete_skill(
305 data: web::Data<AppState>,
306 auth: AuthenticatedUser,
307 id: web::Path<Uuid>,
308) -> impl Responder {
309 let org_id = match auth.require_organization() {
310 Ok(id) => id,
311 Err(e) => {
312 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
313 }
314 };
315 match data
316 .skill_use_cases
317 .delete_skill(id.into_inner(), auth.user_id, org_id)
318 .await
319 {
320 Ok(_) => HttpResponse::NoContent().finish(),
321 Err(e) => {
322 if e.contains("Unauthorized") {
323 HttpResponse::Forbidden().json(serde_json::json!({"error": e}))
324 } else if e.contains("not found") {
325 HttpResponse::NotFound().json(serde_json::json!({"error": e}))
326 } else {
327 HttpResponse::BadRequest().json(serde_json::json!({"error": e}))
328 }
329 }
330 }
331}
332
333#[get("/buildings/{building_id}/skills/statistics")]
337pub async fn get_skill_statistics(
338 data: web::Data<AppState>,
339 building_id: web::Path<Uuid>,
340) -> impl Responder {
341 match data
342 .skill_use_cases
343 .get_skill_statistics(building_id.into_inner())
344 .await
345 {
346 Ok(stats) => HttpResponse::Ok().json(stats),
347 Err(e) => HttpResponse::InternalServerError().json(serde_json::json!({"error": e})),
348 }
349}