This commit is contained in:
MythEclipse
2025-09-25 17:29:17 +07:00
parent df11aa5eb8
commit 82b85abe8f
2 changed files with 481 additions and 509 deletions
+211 -244
View File
@@ -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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_open: Option<bool>,
#[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<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills_required: Option<Vec<String>>,
#[validate(length(max = 100, message = "Location cannot exceed 100 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[validate(url(message = "Invalid avatar URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[validate(url(message = "Invalid website URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[validate(url(message = "Invalid GitHub URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub github_url: Option<String>,
#[validate(length(min = 1, message = "Member emails cannot be empty"))]
pub member_emails: Vec<String>,
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_open: Option<bool>,
#[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<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills_required: Option<Vec<String>>,
#[validate(length(max = 100, message = "Location cannot exceed 100 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[validate(url(message = "Invalid avatar URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[validate(url(message = "Invalid website URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[validate(url(message = "Invalid GitHub URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub github_url: Option<String>,
#[validate(length(min = 1, message = "Member emails cannot be empty"))]
pub member_emails: Vec<String>,
}
#[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<String>,
#[validate(length(max = 500, message = "Description cannot exceed 500 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_open: Option<bool>,
#[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<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills_required: Option<Vec<String>>,
#[validate(length(max = 100, message = "Location cannot exceed 100 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[validate(url(message = "Invalid avatar URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[validate(url(message = "Invalid website URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[validate(url(message = "Invalid GitHub URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub github_url: Option<String>,
#[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<String>,
#[validate(length(max = 500, message = "Description cannot exceed 500 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_open: Option<bool>,
#[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<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills_required: Option<Vec<String>>,
#[validate(length(max = 100, message = "Location cannot exceed 100 characters"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[validate(url(message = "Invalid avatar URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar: Option<String>,
#[validate(url(message = "Invalid website URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
#[validate(url(message = "Invalid GitHub URL"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub github_url: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct TeamInviteRequestDto {
#[validate(length(min = 1, message = "Member emails cannot be empty"))]
pub member_emails: Vec<String>,
#[validate(length(min = 1, message = "Member emails cannot be empty"))]
pub member_emails: Vec<String>,
}
#[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<String>,
pub leader: TeamMemberDto,
pub is_open: bool,
pub max_members: Option<i32>,
pub current_member_count: i32,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
pub members: Option<Vec<TeamMemberDto>>,
pub is_active: bool,
pub created_at: String,
pub updated_at: String,
pub id: String,
pub name: String,
pub description: Option<String>,
pub leader: TeamMemberDto,
pub is_open: bool,
pub max_members: Option<i32>,
pub current_member_count: i32,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
pub members: Option<Vec<TeamMemberDto>>,
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<String>,
pub leader: TeamMemberDto,
pub is_open: bool,
pub max_members: Option<i32>,
pub current_member_count: i32,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
pub members: Vec<TeamMemberDto>, // 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<String>,
pub leader: TeamMemberDto,
pub is_open: bool,
pub max_members: Option<i32>,
pub current_member_count: i32,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
pub members: Vec<TeamMemberDto>, // 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<String>,
pub avatar: Option<String>,
pub role: String,
pub skills: Option<Vec<String>>,
pub joined_at: String,
pub id: String,
pub user_id: String,
pub fullname: String,
pub email: Option<String>,
pub avatar: Option<String>,
pub role: String,
pub skills: Option<Vec<String>>,
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<String>,
pub leader_id: Thing,
pub is_open: bool,
pub max_members: Option<i32>,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
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<String>,
pub leader_id: Thing,
pub is_open: bool,
pub max_members: Option<i32>,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub website_url: Option<String>,
pub github_url: Option<String>,
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<String>,
pub leader_id: Thing,
pub is_open: bool,
pub max_members: Option<i32>,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
pub created_at: String,
pub updated_at: String,
pub id: Thing,
pub name: String,
pub description: Option<String>,
pub leader_id: Thing,
pub is_open: bool,
pub max_members: Option<i32>,
pub skills_required: Option<Vec<String>>,
pub location: Option<String>,
pub avatar: Option<String>,
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<String>,
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<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct TeamsSearchQueryDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub open: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub per_page: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub open: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub skills: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub per_page: Option<i64>,
}
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<TeamMemberDto>) -> AdminTeamsDetailItemDto {
pub fn into_admin_detail_dto(self, members: Vec<TeamMemberDto>) -> AdminTeamsDetailItemDto {
AdminTeamsDetailItemDto {
id: self.id.id.to_raw(),
name: self.name,
@@ -445,4 +413,3 @@ impl TeamsDetailQueryDto {
}
}
}
}
+270 -265
View File
@@ -110,167 +110,167 @@ impl TeamsService {
impl TeamsServiceTrait for TeamsService {
fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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<Box<dyn Future<Output = Response> + 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")
},
}
})
}
}