koprogo_api/application/dto/
skill_dto.rs

1use crate::domain::entities::{ExpertiseLevel, Skill, SkillCategory};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// DTO for creating a new skill
6#[derive(Debug, Serialize, Deserialize)]
7pub struct CreateSkillDto {
8    pub building_id: Uuid,
9    pub skill_category: SkillCategory,
10    pub skill_name: String,
11    pub expertise_level: ExpertiseLevel,
12    pub description: String,
13    pub is_available_for_help: bool,
14    pub hourly_rate_credits: Option<i32>, // 0-100 (SEL integration)
15    pub years_of_experience: Option<i32>,
16    pub certifications: Option<String>,
17}
18
19/// DTO for updating a skill
20#[derive(Debug, Serialize, Deserialize)]
21pub struct UpdateSkillDto {
22    pub skill_name: Option<String>,
23    pub expertise_level: Option<ExpertiseLevel>,
24    pub description: Option<String>,
25    pub is_available_for_help: Option<bool>,
26    pub hourly_rate_credits: Option<Option<i32>>,
27    pub years_of_experience: Option<Option<i32>>,
28    pub certifications: Option<Option<String>>,
29}
30
31/// Complete skill response with owner information
32#[derive(Debug, Serialize, Clone)]
33pub struct SkillResponseDto {
34    pub id: Uuid,
35    pub owner_id: Uuid,
36    pub owner_name: String, // Enriched from Owner
37    pub building_id: Uuid,
38    pub skill_category: SkillCategory,
39    pub skill_name: String,
40    pub expertise_level: ExpertiseLevel,
41    pub description: String,
42    pub is_available_for_help: bool,
43    pub hourly_rate_credits: Option<i32>,
44    pub years_of_experience: Option<i32>,
45    pub certifications: Option<String>,
46    pub created_at: chrono::DateTime<chrono::Utc>,
47    pub updated_at: chrono::DateTime<chrono::Utc>,
48    // Computed fields
49    pub is_free: bool,
50    pub is_professional: bool,
51}
52
53impl SkillResponseDto {
54    /// Create from Skill with owner name enrichment
55    pub fn from_skill(skill: Skill, owner_name: String) -> Self {
56        let is_free = skill.is_free();
57        let is_professional = skill.is_professional();
58
59        Self {
60            id: skill.id,
61            owner_id: skill.owner_id,
62            owner_name,
63            building_id: skill.building_id,
64            skill_category: skill.skill_category,
65            skill_name: skill.skill_name,
66            expertise_level: skill.expertise_level,
67            description: skill.description,
68            is_available_for_help: skill.is_available_for_help,
69            hourly_rate_credits: skill.hourly_rate_credits,
70            years_of_experience: skill.years_of_experience,
71            certifications: skill.certifications,
72            created_at: skill.created_at,
73            updated_at: skill.updated_at,
74            is_free,
75            is_professional,
76        }
77    }
78}
79
80/// Summary skill view for lists
81#[derive(Debug, Serialize, Clone)]
82pub struct SkillSummaryDto {
83    pub id: Uuid,
84    pub owner_id: Uuid,
85    pub owner_name: String, // Enriched from Owner
86    pub building_id: Uuid,
87    pub skill_category: SkillCategory,
88    pub skill_name: String,
89    pub expertise_level: ExpertiseLevel,
90    pub is_available_for_help: bool,
91    pub hourly_rate_credits: Option<i32>,
92    pub is_free: bool,
93    pub is_professional: bool,
94}
95
96impl SkillSummaryDto {
97    /// Create from Skill with owner name enrichment
98    pub fn from_skill(skill: Skill, owner_name: String) -> Self {
99        let is_free = skill.is_free();
100        let is_professional = skill.is_professional();
101
102        Self {
103            id: skill.id,
104            owner_id: skill.owner_id,
105            owner_name,
106            building_id: skill.building_id,
107            skill_category: skill.skill_category,
108            skill_name: skill.skill_name,
109            expertise_level: skill.expertise_level,
110            is_available_for_help: skill.is_available_for_help,
111            hourly_rate_credits: skill.hourly_rate_credits,
112            is_free,
113            is_professional,
114        }
115    }
116}
117
118/// Statistics for building skills
119#[derive(Debug, Serialize)]
120pub struct SkillStatisticsDto {
121    pub total_skills: i64,
122    pub available_skills: i64,
123    pub free_skills: i64,
124    pub paid_skills: i64,
125    pub professional_skills: i64,
126    pub skills_by_category: Vec<CategoryCount>,
127    pub skills_by_expertise: Vec<ExpertiseCount>,
128}
129
130/// Category count for statistics
131#[derive(Debug, Serialize)]
132pub struct CategoryCount {
133    pub category: SkillCategory,
134    pub count: i64,
135}
136
137/// Expertise level count for statistics
138#[derive(Debug, Serialize)]
139pub struct ExpertiseCount {
140    pub level: ExpertiseLevel,
141    pub count: i64,
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_skill_response_dto_from_skill() {
150        let skill = Skill::new(
151            Uuid::new_v4(),
152            Uuid::new_v4(),
153            SkillCategory::Technology,
154            "Web Development".to_string(),
155            ExpertiseLevel::Advanced,
156            "Full-stack web development".to_string(),
157            true,
158            Some(10),
159            Some(5),
160            Some("Certified Developer".to_string()),
161        )
162        .unwrap();
163
164        let dto = SkillResponseDto::from_skill(skill.clone(), "John Doe".to_string());
165
166        assert_eq!(dto.owner_name, "John Doe");
167        assert_eq!(dto.skill_name, "Web Development");
168        assert_eq!(dto.expertise_level, ExpertiseLevel::Advanced);
169        assert!(!dto.is_free);
170        assert!(dto.is_professional);
171    }
172
173    #[test]
174    fn test_skill_summary_dto_from_skill() {
175        let skill = Skill::new(
176            Uuid::new_v4(),
177            Uuid::new_v4(),
178            SkillCategory::Gardening,
179            "Gardening".to_string(),
180            ExpertiseLevel::Beginner,
181            "Basic gardening".to_string(),
182            true,
183            None,
184            Some(1),
185            None,
186        )
187        .unwrap();
188
189        let dto = SkillSummaryDto::from_skill(skill.clone(), "Jane Smith".to_string());
190
191        assert_eq!(dto.owner_name, "Jane Smith");
192        assert_eq!(dto.skill_name, "Gardening");
193        assert!(dto.is_free);
194        assert!(!dto.is_professional);
195    }
196}