diff --git a/imphnen-iam/src/v1/teams/teams_dto.rs b/imphnen-iam/src/v1/teams/teams_dto.rs index 84366bc..b2fc926 100644 --- a/imphnen-iam/src/v1/teams/teams_dto.rs +++ b/imphnen-iam/src/v1/teams/teams_dto.rs @@ -5,129 +5,129 @@ use validator::Validate; #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct TeamsCreateRequestDto { - #[validate(length(min = 3, max = 100, message = "Team name must be between 3 and 100 characters"))] - pub name: String, - - #[validate(length(max = 500, message = "Description cannot exceed 500 characters"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_open: Option, - - #[validate(range(min = 2, max = 50, message = "Max members must be between 2 and 50"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub max_members: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub skills_required: Option>, - - #[validate(length(max = 100, message = "Location cannot exceed 100 characters"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub location: Option, - - #[validate(url(message = "Invalid avatar URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, - - #[validate(url(message = "Invalid website URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub website_url: Option, - - #[validate(url(message = "Invalid GitHub URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub github_url: Option, - - #[validate(length(min = 1, message = "Member emails cannot be empty"))] - pub member_emails: Vec, + #[validate(length(min = 3, max = 100, message = "Team name must be between 3 and 100 characters"))] + pub name: String, + + #[validate(length(max = 500, message = "Description cannot exceed 500 characters"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub is_open: Option, + + #[validate(range(min = 2, max = 50, message = "Max members must be between 2 and 50"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub max_members: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub skills_required: Option>, + + #[validate(length(max = 100, message = "Location cannot exceed 100 characters"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option, + + #[validate(url(message = "Invalid avatar URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar: Option, + + #[validate(url(message = "Invalid website URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub website_url: Option, + + #[validate(url(message = "Invalid GitHub URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub github_url: Option, + + #[validate(length(min = 1, message = "Member emails cannot be empty"))] + pub member_emails: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct TeamsUpdateRequestDto { - #[validate(length(min = 3, max = 100, message = "Team name must be between 3 and 100 characters"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, - - #[validate(length(max = 500, message = "Description cannot exceed 500 characters"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub is_open: Option, - - #[validate(range(min = 2, max = 50, message = "Max members must be between 2 and 50"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub max_members: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub skills_required: Option>, - - #[validate(length(max = 100, message = "Location cannot exceed 100 characters"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub location: Option, - - #[validate(url(message = "Invalid avatar URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub avatar: Option, - - #[validate(url(message = "Invalid website URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub website_url: Option, - - #[validate(url(message = "Invalid GitHub URL"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub github_url: Option, + #[validate(length(min = 3, max = 100, message = "Team name must be between 3 and 100 characters"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + #[validate(length(max = 500, message = "Description cannot exceed 500 characters"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub is_open: Option, + + #[validate(range(min = 2, max = 50, message = "Max members must be between 2 and 50"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub max_members: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub skills_required: Option>, + + #[validate(length(max = 100, message = "Location cannot exceed 100 characters"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option, + + #[validate(url(message = "Invalid avatar URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar: Option, + + #[validate(url(message = "Invalid website URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub website_url: Option, + + #[validate(url(message = "Invalid GitHub URL"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub github_url: Option, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct TeamInviteRequestDto { - #[validate(length(min = 1, message = "Member emails cannot be empty"))] - pub member_emails: Vec, + #[validate(length(min = 1, message = "Member emails cannot be empty"))] + pub member_emails: Vec, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct TeamAcceptInvitationRequestDto { - pub token: String, + pub token: String, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct TeamsDetailItemDto { - pub id: String, - pub name: String, - pub description: Option, - pub leader: TeamMemberDto, - pub is_open: bool, - pub max_members: Option, - pub current_member_count: i32, - pub skills_required: Option>, - pub location: Option, - pub avatar: Option, - pub website_url: Option, - pub github_url: Option, - pub members: Option>, - pub is_active: bool, - pub created_at: String, - pub updated_at: String, + pub id: String, + pub name: String, + pub description: Option, + pub leader: TeamMemberDto, + pub is_open: bool, + pub max_members: Option, + pub current_member_count: i32, + pub skills_required: Option>, + pub location: Option, + pub avatar: Option, + pub website_url: Option, + pub github_url: Option, + pub members: Option>, + pub is_active: bool, + pub created_at: String, + pub updated_at: String, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct MemberTeamsDetailItemDto { - pub id: String, - pub name: String, - pub description: Option, - pub leader: TeamMemberDto, - pub is_open: bool, - pub max_members: Option, - pub current_member_count: i32, - pub skills_required: Option>, - pub location: Option, - pub avatar: Option, - pub website_url: Option, - pub github_url: Option, - pub members: Vec, // Always include members for authenticated users - pub is_active: bool, - pub created_at: String, - pub updated_at: String, + pub id: String, + pub name: String, + pub description: Option, + pub leader: TeamMemberDto, + pub is_open: bool, + pub max_members: Option, + pub current_member_count: i32, + pub skills_required: Option>, + pub location: Option, + pub avatar: Option, + pub website_url: Option, + pub github_url: Option, + pub members: Vec, // Always include members for authenticated users + pub is_active: bool, + pub created_at: String, + pub updated_at: String, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] @@ -179,168 +179,106 @@ pub struct PublicTeamsDetailItemDto { #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct TeamMemberDto { - pub id: String, - pub user_id: String, - pub fullname: String, - pub email: Option, - pub avatar: Option, - pub role: String, - pub skills: Option>, - pub joined_at: String, + pub id: String, + pub user_id: String, + pub fullname: String, + pub email: Option, + pub avatar: Option, + pub role: String, + pub skills: Option>, + pub joined_at: String, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct TeamInvitationDto { - pub id: String, - pub team_id: String, - pub team_name: String, - pub email: String, - pub inviter_name: String, - pub status: String, - pub expires_at: String, - pub invited_at: String, + pub id: String, + pub team_id: String, + pub team_name: String, + pub email: String, + pub inviter_name: String, + pub status: String, + pub expires_at: String, + pub invited_at: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TeamsDetailQueryDto { - pub id: Thing, - pub name: String, - pub description: Option, - pub leader_id: Thing, - pub is_open: bool, - pub max_members: Option, - pub skills_required: Option>, - pub location: Option, - pub avatar: Option, - pub website_url: Option, - pub github_url: Option, - pub is_active: bool, - pub is_deleted: bool, - pub created_at: String, - pub updated_at: String, + pub id: Thing, + pub name: String, + pub description: Option, + pub leader_id: Thing, + pub is_open: bool, + pub max_members: Option, + pub skills_required: Option>, + pub location: Option, + pub avatar: Option, + pub website_url: Option, + pub github_url: Option, + pub is_active: bool, + pub is_deleted: bool, + pub created_at: String, + pub updated_at: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TeamsListQueryDto { - pub id: Thing, - pub name: String, - pub description: Option, - pub leader_id: Thing, - pub is_open: bool, - pub max_members: Option, - pub skills_required: Option>, - pub location: Option, - pub avatar: Option, - pub created_at: String, - pub updated_at: String, + pub id: Thing, + pub name: String, + pub description: Option, + pub leader_id: Thing, + pub is_open: bool, + pub max_members: Option, + pub skills_required: Option>, + pub location: Option, + pub avatar: Option, + pub created_at: String, + pub updated_at: String, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TeamMembersQueryDto { - pub id: Thing, - pub team_id: Thing, - pub user_id: Thing, - pub role: String, - pub joined_at: String, - pub is_active: bool, + pub id: Thing, + pub team_id: Thing, + pub user_id: Thing, + pub role: String, + pub joined_at: String, + pub is_active: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TeamInvitationsQueryDto { - pub id: Thing, - pub team_id: Thing, - pub email: String, - pub inviter_id: Thing, - pub invite_code: String, - pub expires_at: String, - pub status: String, - pub invited_at: String, - pub accepted_at: Option, + pub id: Thing, + pub team_id: Thing, + pub email: String, + pub inviter_id: Thing, + pub invite_code: String, + pub expires_at: String, + pub status: String, + pub invited_at: String, + pub accepted_at: Option, } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct TeamsSearchQueryDto { - #[serde(skip_serializing_if = "Option::is_none")] - pub query: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub open: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub skills: Option>, - - #[serde(skip_serializing_if = "Option::is_none")] - pub location: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub page: Option, - - #[serde(skip_serializing_if = "Option::is_none")] - pub per_page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub open: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub skills: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub per_page: Option, } -impl TeamsDetailQueryDto { - pub fn from(self) -> Self { - self - } -} - -impl TeamsListQueryDto { - pub fn from(self) -> TeamsListItemDto { - TeamsListItemDto { - id: self.id.id.to_raw(), - name: self.name, - description: self.description, - leader: TeamMemberDto { - id: String::new(), - user_id: self.leader_id.id.to_raw(), - fullname: String::new(), - email: None, - avatar: None, - role: "leader".to_string(), - skills: None, - joined_at: self.created_at.clone(), - }, - is_open: self.is_open, - current_member_count: 1, - max_members: self.max_members, - skills_required: self.skills_required, - location: self.location, - avatar: self.avatar, - created_at: self.created_at, - } - } - - pub fn to_admin_list_dto(self) -> AdminTeamsListItemDto { - let members = vec![]; - AdminTeamsListItemDto { - id: self.id.id.to_raw(), - name: self.name, - description: self.description, - leader: TeamMemberDto { - id: String::new(), - user_id: self.leader_id.id.to_raw(), - fullname: String::new(), - email: None, - avatar: None, - role: "leader".to_string(), - skills: None, - joined_at: self.created_at.clone(), - }, - is_open: self.is_open, - current_member_count: members.len() as i32 + 1, - max_members: self.max_members, - skills_required: self.skills_required, - location: self.location, - avatar: self.avatar, - website_url: None, - github_url: None, - is_active: true, - is_deleted: false, - created_at: self.created_at, - } - } -} #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct AdminTeamsListItemDto { pub id: String, @@ -381,9 +319,39 @@ pub struct AdminTeamsDetailItemDto { pub updated_at: String, } +impl TeamsDetailQueryDto { + pub fn into_detail_dto(self) -> Self { + self + } +} + impl TeamsListQueryDto { - pub fn to_admin_list_dto(self) -> AdminTeamsListItemDto { - let members = vec![]; + pub fn into_list_item_dto(self) -> TeamsListItemDto { + TeamsListItemDto { + id: self.id.id.to_raw(), + name: self.name, + description: self.description, + leader: TeamMemberDto { + id: String::new(), + user_id: self.leader_id.id.to_raw(), + fullname: String::new(), + email: None, + avatar: None, + role: "leader".to_string(), + skills: None, + joined_at: self.created_at.clone(), + }, + is_open: self.is_open, + current_member_count: 1, + max_members: self.max_members, + skills_required: self.skills_required, + location: self.location, + avatar: self.avatar, + created_at: self.created_at, + } + } + + pub fn into_admin_list_dto(self) -> AdminTeamsListItemDto { AdminTeamsListItemDto { id: self.id.id.to_raw(), name: self.name, @@ -399,7 +367,7 @@ impl TeamsListQueryDto { joined_at: self.created_at.clone(), }, is_open: self.is_open, - current_member_count: members.len() as i32 + 1, + current_member_count: 1, // Placeholder; adjust based on actual member count max_members: self.max_members, skills_required: self.skills_required, location: self.location, @@ -414,7 +382,7 @@ impl TeamsListQueryDto { } impl TeamsDetailQueryDto { - pub fn to_admin_detail_dto(self, members: Vec) -> AdminTeamsDetailItemDto { + pub fn into_admin_detail_dto(self, members: Vec) -> AdminTeamsDetailItemDto { AdminTeamsDetailItemDto { id: self.id.id.to_raw(), name: self.name, @@ -445,4 +413,3 @@ impl TeamsDetailQueryDto { } } } -} \ No newline at end of file diff --git a/imphnen-iam/src/v1/teams/teams_service.rs b/imphnen-iam/src/v1/teams/teams_service.rs index 414879e..7ab4cc8 100644 --- a/imphnen-iam/src/v1/teams/teams_service.rs +++ b/imphnen-iam/src/v1/teams/teams_service.rs @@ -110,167 +110,167 @@ impl TeamsService { impl TeamsServiceTrait for TeamsService { fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - let repo = TeamsRepository::new(&state); - match repo.query_team_list(meta).await { - Ok(data) => { - let response = ResponseListSuccessDto { - data: data.data, - meta: data.meta, - }; - success_list_response(response) - } - Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + let state = state.to_owned(); + Box::pin(async move { + let repo = TeamsRepository::new(&state); + match repo.query_team_list(meta).await { + Ok(data) => { + let response = ResponseListSuccessDto { + data: data.data, + meta: data.meta, + }; + success_list_response(response) } - }) - } + Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + } + }) + } - fn get_team_by_id(state: &AppState, id: String) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - if Uuid::parse_str(&id).is_err() { - return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); + fn get_team_by_id(state: &AppState, id: String) -> Pin + Send>> { + let state = state.to_owned(); + Box::pin(async move { + if Uuid::parse_str(&id).is_err() { + return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); + } + let repo = TeamsRepository::new(&state); + let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); + match repo.query_team_by_id(&thing_id).await { + Ok(team) if !team.is_deleted => { + let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + + // For public team details, only show sensitive info if user is authenticated and part of the team + let team_dto = TeamsDetailItemDto { + id: team.id.id.to_raw(), + name: team.name, + description: team.description, + leader: TeamMemberDto { + id: String::new(), + user_id: team.leader_id.id.to_raw(), + fullname: String::new(), + email: None, + avatar: None, + role: "leader".to_string(), + skills: None, + joined_at: team.created_at.clone(), + }, + is_open: team.is_open, + max_members: team.max_members, + current_member_count: members.len() as i32 + 1, + skills_required: team.skills_required, + location: team.location, + avatar: team.avatar, + website_url: team.website_url, + github_url: team.github_url, + members: None, + is_active: team.is_active, + created_at: team.created_at, + updated_at: team.updated_at, + }; + success_response(ResponseSuccessDto { data: team_dto }) } - let repo = TeamsRepository::new(&state); - let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); - match repo.query_team_by_id(&thing_id).await { - Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); - - // For public team details, only show sensitive info if user is authenticated and part of the team - let team_dto = TeamsDetailItemDto { - id: team.id.id.to_raw(), - name: team.name, - description: team.description, - leader: TeamMemberDto { - id: String::new(), - user_id: team.leader_id.id.to_raw(), - fullname: String::new(), - email: None, - avatar: None, - role: "leader".to_string(), - skills: None, - joined_at: team.created_at.clone(), - }, - is_open: team.is_open, - max_members: team.max_members, - current_member_count: members.len() as i32 + 1, - skills_required: team.skills_required, - location: team.location, - avatar: team.avatar, - website_url: team.website_url, - github_url: team.github_url, - members: None, - is_active: team.is_active, - created_at: team.created_at, - updated_at: team.updated_at, - }; - success_response(ResponseSuccessDto { data: team_dto }) - } - Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), - Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), - } - }) - } + Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), + Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), + } + }) + } - fn get_member_team_list(state: &AppState, meta: MetaRequestDto) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - let repo = TeamsRepository::new(&state); - match repo.query_team_list(meta).await { - Ok(data) => { - let response = ResponseListSuccessDto { - data: data.data, - meta: data.meta, - }; - success_list_response(response) - } - Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + fn get_member_team_list(state: &AppState, meta: MetaRequestDto) -> Pin + Send>> { + let state = state.to_owned(); + Box::pin(async move { + let repo = TeamsRepository::new(&state); + match repo.query_team_list(meta).await { + Ok(data) => { + let response = ResponseListSuccessDto { + data: data.data, + meta: data.meta, + }; + success_list_response(response) } - }) - } + Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + } + }) + } - fn get_member_team_by_id(state: &AppState, id: String) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - if Uuid::parse_str(&id).is_err() { - return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); - } - let repo = TeamsRepository::new(&state); - let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); - match repo.query_team_by_id(&thing_id).await { - Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); - - // For member team details, include all information including members list - let mut member_dtos = Vec::new(); - for member in members { - match Self::get_user_info_with_privacy( - &member.user_id.id.to_raw(), - "system", // In member context, we show all user info - true, // In member context, we show all user info - &state, - ).await { - Ok(mut member_dto) => { - member_dto.role = member.role; - member_dto.joined_at = member.joined_at; - member_dtos.push(member_dto); - } - Err(_) => continue, - } - } - - // Add leader with full info - let leader_dto = match Self::get_user_info_with_privacy( - &team.leader_id.id.to_raw(), - "system", - true, + fn get_member_team_by_id(state: &AppState, id: String) -> Pin + Send>> { + let state = state.to_owned(); + Box::pin(async move { + if Uuid::parse_str(&id).is_err() { + return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); + } + let repo = TeamsRepository::new(&state); + let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); + match repo.query_team_by_id(&thing_id).await { + Ok(team) if !team.is_deleted => { + let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + + // For member team details, include all information including members list + let mut member_dtos = Vec::new(); + for member in members { + match Self::get_user_info_with_privacy( + &member.user_id.id.to_raw(), + "system", // In member context, we show all user info + true, // In member context, we show all user info &state, ).await { - Ok(mut leader_dto) => { - leader_dto.role = "leader".to_string(); - leader_dto + Ok(mut member_dto) => { + member_dto.role = member.role; + member_dto.joined_at = member.joined_at; + member_dtos.push(member_dto); } - Err(_) => TeamMemberDto { - id: String::new(), - user_id: team.leader_id.id.to_raw(), - fullname: String::new(), - email: None, - avatar: None, - role: "leader".to_string(), - skills: None, - joined_at: team.created_at.clone(), - } - }; - - member_dtos.insert(0, leader_dto); - - let team_dto = MemberTeamsDetailItemDto { - id: team.id.id.to_raw(), - name: team.name, - description: team.description, - leader: leader_dto, - is_open: team.is_open, - max_members: team.max_members, - current_member_count: members.len() as i32 + 1, - skills_required: team.skills_required, - location: team.location, - avatar: team.avatar, - website_url: team.website_url, - github_url: team.github_url, - members: member_dtos, - is_active: team.is_active, - created_at: team.created_at, - updated_at: team.updated_at, - }; - success_response(ResponseSuccessDto { data: team_dto }) + Err(_) => continue, + } } - Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), - Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), + + // Add leader with full info + let leader_dto = match Self::get_user_info_with_privacy( + &team.leader_id.id.to_raw(), + "system", + true, + &state, + ).await { + Ok(mut leader_dto) => { + leader_dto.role = "leader".to_string(); + leader_dto + } + Err(_) => TeamMemberDto { + id: String::new(), + user_id: team.leader_id.id.to_raw(), + fullname: String::new(), + email: None, + avatar: None, + role: "leader".to_string(), + skills: None, + joined_at: team.created_at.clone(), + } + }; + + member_dtos.insert(0, leader_dto); + + let team_dto = MemberTeamsDetailItemDto { + id: team.id.id.to_raw(), + name: team.name, + description: team.description, + leader: leader_dto, + is_open: team.is_open, + max_members: team.max_members, + current_member_count: members.len() as i32 + 1, + skills_required: team.skills_required, + location: team.location, + avatar: team.avatar, + website_url: team.website_url, + github_url: team.github_url, + members: member_dtos, + is_active: team.is_active, + created_at: team.created_at, + updated_at: team.updated_at, + }; + success_response(ResponseSuccessDto { data: team_dto }) } - }) - } + Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), + Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), + } + }) + } fn get_public_team_list(state: &AppState, meta: MetaRequestDto) -> Pin + Send>> { let state = state.to_owned(); @@ -372,8 +372,8 @@ impl TeamsServiceTrait for TeamsService { match repo.query_create_invitation(invitation).await { Ok(_) => { let inviter_user = match users_repo.query_user_by_id(&make_thing_from_enum(ResourceEnum::Users, &claims.user_id)).await { - Ok(user) => user, - Err(_) => continue, + Ok(user) => user, + Err(_) => continue, }; if let Err(e) = Self::send_invitation_email( &team_schema.name, @@ -538,8 +538,8 @@ impl TeamsServiceTrait for TeamsService { match repo.query_create_invitation(invitation).await { Ok(_) => { let inviter_user = match users_repo.query_user_by_id(&make_thing_from_enum(ResourceEnum::Users, &claims.user_id)).await { - Ok(user) => user, - Err(_) => return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to get inviter user information"), + Ok(user) => user, + Err(_) => return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to get inviter user information"), }; if let Err(e) = Self::send_invitation_email( &team.name, @@ -759,6 +759,11 @@ impl TeamsServiceTrait for TeamsService { }; success_list_response(response) } + Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()), + } + }) + } + fn get_admin_team_list(state: &AppState, meta: MetaRequestDto) -> Pin + Send>> { let state = state.to_owned(); Box::pin(async move { @@ -777,129 +782,129 @@ impl TeamsServiceTrait for TeamsService { } fn get_admin_team_by_id(state: &AppState, id: String) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - if Uuid::parse_str(&id).is_err() { - return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); - } - let repo = TeamsRepository::new(&state); - let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); - match repo.query_team_by_id(&thing_id).await { - Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); - let mut member_dtos = Vec::new(); - - for member in members { - match Self::get_user_info_with_privacy( - &member.user_id.id.to_raw(), - "system", // Admin context - show all sensitive data - true, // Admin context - always show sensitive data - &state, - ).await { - Ok(mut member_dto) => { - member_dto.role = member.role; - member_dto.joined_at = member.joined_at; - member_dtos.push(member_dto); - } - Err(_) => continue, - } - } - - // Add leader with full sensitive info + let state = state.to_owned(); + Box::pin(async move { + if Uuid::parse_str(&id).is_err() { + return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); + } + let repo = TeamsRepository::new(&state); + let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); + match repo.query_team_by_id(&thing_id).await { + Ok(team) if !team.is_deleted => { + let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + let mut member_dtos = Vec::new(); + + for member in members { match Self::get_user_info_with_privacy( - &team.leader_id.id.to_raw(), - "system", - true, + &member.user_id.id.to_raw(), + "system", // Admin context - show all sensitive data + true, // Admin context - always show sensitive data &state, ).await { - Ok(mut leader_dto) => { - leader_dto.role = "leader".to_string(); - member_dtos.insert(0, leader_dto); + Ok(mut member_dto) => { + member_dto.role = member.role; + member_dto.joined_at = member.joined_at; + member_dtos.push(member_dto); } - Err(_) => {} + Err(_) => continue, } - - let team_dto = team.to_admin_detail_dto(member_dtos); - success_response(ResponseSuccessDto { data: team_dto }) } - Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), - Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), - } - }) - } - fn get_admin_team_members(state: &AppState, team_id: String) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - if Uuid::parse_str(&team_id).is_err() { - return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); - } - let repo = TeamsRepository::new(&state); - let thing_id = make_thing_from_enum(ResourceEnum::Teams, &team_id); - - let members = match repo.query_team_members(&thing_id).await { - Ok(members) => members, - Err(e) => return common_response(StatusCode::BAD_REQUEST, &e.to_string()), - }; - - let mut member_dtos = Vec::new(); - for member in members { + // Add leader with full sensitive info match Self::get_user_info_with_privacy( - &member.user_id.id.to_raw(), - "system", // Admin context - show all sensitive data - true, // Admin context - always show sensitive data + &team.leader_id.id.to_raw(), + "system", + true, &state, ).await { - Ok(mut member_dto) => { - member_dto.role = member.role; - member_dto.joined_at = member.joined_at; - member_dtos.push(member_dto); + Ok(mut leader_dto) => { + leader_dto.role = "leader".to_string(); + member_dtos.insert(0, leader_dto); } - Err(_) => continue, + Err(_) => {} } - } - - success_response(ResponseSuccessDto { data: member_dtos }) - }) - } - - fn leave_current_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims) -> Pin + Send>> { - let state = state.to_owned(); - Box::pin(async move { - let repo = TeamsRepository::new(&state); - let user_thing = make_thing_from_enum(ResourceEnum::Users, &claims.user_id); - // Find the teams that the user is a member of - let teams = match repo.query_teams_by_user(&user_thing).await { - Ok(teams) => teams, - Err(e) => { - error!("Failed to query user teams: {}", e); - return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to retrieve user teams") - }, - }; - - if teams.is_empty() { - return common_response(StatusCode::BAD_REQUEST, "User is not a member of any team"); - } - - // For now, assume user is in only one team (common case) - // In a future enhancement, we could ask the user to specify which team to leave - let team = &teams[0]; - let team_id = team.id.id.to_raw(); - let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team_id); - - // Check if user is the leader - if team.leader_id.id.to_raw() == claims.user_id { - return common_response(StatusCode::FORBIDDEN, "Team leader cannot leave the team"); - } - - match repo.query_remove_team_member(&team_thing, &user_thing).await { - Ok(msg) => common_response(StatusCode::OK, &format!("Successfully left team: {}", team.name)), - Err(e) => { - error!("Failed to remove team member: {}", e); - return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to leave team") - }, - } - }) + let team_dto = team.to_admin_detail_dto(member_dtos); + success_response(ResponseSuccessDto { data: team_dto }) + } + Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"), + Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()), } + }) + } + + fn get_admin_team_members(state: &AppState, team_id: String) -> Pin + Send>> { + let state = state.to_owned(); + Box::pin(async move { + if Uuid::parse_str(&team_id).is_err() { + return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format"); + } + let repo = TeamsRepository::new(&state); + let thing_id = make_thing_from_enum(ResourceEnum::Teams, &team_id); + + let members = match repo.query_team_members(&thing_id).await { + Ok(members) => members, + Err(e) => return common_response(StatusCode::BAD_REQUEST, &e.to_string()), + }; + + let mut member_dtos = Vec::new(); + for member in members { + match Self::get_user_info_with_privacy( + &member.user_id.id.to_raw(), + "system", // Admin context - show all sensitive data + true, // Admin context - always show sensitive data + &state, + ).await { + Ok(mut member_dto) => { + member_dto.role = member.role; + member_dto.joined_at = member.joined_at; + member_dtos.push(member_dto); + } + Err(_) => continue, + } + } + + success_response(ResponseSuccessDto { data: member_dtos }) + }) + } + + fn leave_current_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims) -> Pin + Send>> { + let state = state.to_owned(); + Box::pin(async move { + let repo = TeamsRepository::new(&state); + let user_thing = make_thing_from_enum(ResourceEnum::Users, &claims.user_id); + + // Find the teams that the user is a member of + let teams = match repo.query_teams_by_user(&user_thing).await { + Ok(teams) => teams, + Err(e) => { + error!("Failed to query user teams: {}", e); + return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to retrieve user teams") + }, + }; + + if teams.is_empty() { + return common_response(StatusCode::BAD_REQUEST, "User is not a member of any team"); + } + + // For now, assume user is in only one team (common case) + // In a future enhancement, we could ask the user to specify which team to leave + let team = &teams[0]; + let team_id = team.id.id.to_raw(); + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team_id); + + // Check if user is the leader + if team.leader_id.id.to_raw() == claims.user_id { + return common_response(StatusCode::FORBIDDEN, "Team leader cannot leave the team"); + } + + match repo.query_remove_team_member(&team_thing, &user_thing).await { + Ok(msg) => common_response(StatusCode::OK, &format!("Successfully left team: {}", team.name)), + Err(e) => { + error!("Failed to remove team member: {}", e); + return common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to leave team") + }, + } + }) + } } \ No newline at end of file