feat: Add member team management functionality with detailed DTOs and service methods
This commit is contained in:
@@ -6,11 +6,13 @@ use crate::{
|
|||||||
TeamMemberDto, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto,
|
TeamMemberDto, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto,
|
||||||
AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum
|
AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum
|
||||||
};
|
};
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::response::Response;
|
||||||
|
use axum::extract::Query;
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::{Extension, Json};
|
use axum::{Extension, Json};
|
||||||
|
|
||||||
use super::teams_service::{TeamsServiceTrait, TeamsService};
|
use super::teams_service::{TeamsServiceTrait, TeamsService};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
@@ -29,55 +31,60 @@ use super::teams_service::{TeamsServiceTrait, TeamsService};
|
|||||||
("filter_by" = Option<String>, Query, description = "Field to filter by"),
|
("filter_by" = Option<String>, Query, description = "Field to filter by"),
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get team list", body = ResponseListSuccessDto<Vec<TeamsListItemDto>>)
|
(status = 200, description = "Get team list", body = ResponseListSuccessDto<Vec<TeamsListItemDto>>),
|
||||||
|
(status = 200, description = "Get public team list", body = ResponseListSuccessDto<Vec<PublicTeamsListItemDto>>)
|
||||||
),
|
),
|
||||||
tag = "Teams"
|
tag = "Teams"
|
||||||
)]
|
)]
|
||||||
pub async fn get_team_list(
|
pub async fn get_team_list(
|
||||||
headers: HeaderMap,
|
headers: Option<HeaderMap>,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
|
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match headers {
|
||||||
headers,
|
Some(headers) => {
|
||||||
Extension(state),
|
match permissions_guard(
|
||||||
vec![],
|
headers,
|
||||||
)
|
Extension(state.clone()),
|
||||||
.await
|
vec![],
|
||||||
{
|
).await {
|
||||||
Ok((_claims, state)) => TeamsService::get_team_list(&state, meta).await,
|
Ok((_claims, state)) => TeamsService::get_team_list(&state, meta).await,
|
||||||
Err(response) => response,
|
Err(_) => TeamsService::get_public_team_list(&state, meta).await,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => TeamsService::get_public_team_list(&state, meta).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
security(
|
path = "/v1/teams/{id}",
|
||||||
("Bearer" = [])
|
params(
|
||||||
),
|
("id" = String, Path, description = "Team ID")
|
||||||
path = "/v1/teams/detail/{id}",
|
),
|
||||||
params(
|
responses(
|
||||||
("id" = String, Path, description = "Team ID")
|
(status = 200, description = "Get team by ID", body = ResponseSuccessDto<TeamsDetailItemDto>),
|
||||||
),
|
(status = 200, description = "Get public team by ID", body = ResponseSuccessDto<PublicTeamsDetailItemDto>)
|
||||||
responses(
|
),
|
||||||
(status = 200, description = "Get team by ID", body = ResponseSuccessDto<TeamsDetailItemDto>)
|
tag = "Teams"
|
||||||
),
|
|
||||||
tag = "Teams"
|
|
||||||
)]
|
)]
|
||||||
pub async fn get_team_by_id(
|
pub async fn get_team_by_id(
|
||||||
headers: HeaderMap,
|
headers: Option<HeaderMap>,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match headers {
|
||||||
headers,
|
Some(headers) => {
|
||||||
Extension(state),
|
match permissions_guard(
|
||||||
vec![],
|
headers,
|
||||||
)
|
Extension(state.clone()),
|
||||||
.await
|
vec![],
|
||||||
{
|
).await {
|
||||||
Ok((_claims, state)) => TeamsService::get_team_by_id(&state, id).await,
|
Ok((_claims, state)) => TeamsService::get_team_by_id(&state, id).await,
|
||||||
Err(response) => response,
|
Err(_) => TeamsService::get_public_team_by_id(&state, id).await,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => TeamsService::get_public_team_by_id(&state, id).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +310,7 @@ pub async fn get_team_members(
|
|||||||
("id" = String, Path, description = "Team ID")
|
("id" = String, Path, description = "Team ID")
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Leave team", body = MessageResponseDto)
|
(status = 200, description = "Leave specific team", body = MessageResponseDto)
|
||||||
),
|
),
|
||||||
tag = "Teams"
|
tag = "Teams"
|
||||||
)]
|
)]
|
||||||
@@ -325,45 +332,32 @@ pub async fn post_leave_team(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
post,
|
||||||
path = "/v1/teams/public",
|
security(
|
||||||
params(
|
("Bearer" = [])
|
||||||
("page" = Option<i64>, Query, description = "Page number"),
|
),
|
||||||
("per_page" = Option<i64>, Query, description = "Items per page"),
|
path = "/v1/teams/leave-me",
|
||||||
("search" = Option<String>, Query, description = "Search keyword"),
|
|
||||||
("sort_by" = Option<String>, Query, description = "Sort by field"),
|
|
||||||
("order" = Option<String>, Query, description = "Order ASC or DESC"),
|
|
||||||
("filter" = Option<String>, Query, description = "Filter value"),
|
|
||||||
("filter_by" = Option<String>, Query, description = "Field to filter by"),
|
|
||||||
),
|
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get public team list", body = ResponseListSuccessDto<Vec<PublicTeamsListItemDto>>)
|
(status = 200, description = "Leave current team", body = MessageResponseDto)
|
||||||
),
|
),
|
||||||
tag = "Teams"
|
tag = "Teams"
|
||||||
)]
|
)]
|
||||||
pub async fn get_public_team_list(
|
pub async fn post_leave_current_team(
|
||||||
|
headers: HeaderMap,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
axum::extract::Query(meta): axum::extract::Query<MetaRequestDto>,
|
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
TeamsService::get_team_list(&state, meta).await
|
match permissions_guard(
|
||||||
|
headers,
|
||||||
|
Extension(state),
|
||||||
|
vec![],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok((claims, state)) => TeamsService::leave_current_team(&state, claims).await,
|
||||||
|
Err(response) => response,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
get,
|
|
||||||
path = "/v1/teams/public/{id}",
|
|
||||||
params(
|
|
||||||
("id" = String, Path, description = "Team ID")
|
|
||||||
),
|
|
||||||
responses(
|
|
||||||
(status = 200, description = "Get public team by ID", body = ResponseSuccessDto<PublicTeamsDetailItemDto>)
|
|
||||||
),
|
|
||||||
tag = "Teams"
|
|
||||||
)]
|
|
||||||
pub async fn get_public_team_by_id(
|
|
||||||
Extension(state): Extension<AppState>,
|
|
||||||
Path(id): Path<String>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
TeamsService::get_team_by_id(&state, id).await
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
security(
|
security(
|
||||||
|
|||||||
@@ -110,6 +110,26 @@ pub struct TeamsDetailItemDto {
|
|||||||
pub updated_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,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct TeamsListItemDto {
|
pub struct TeamsListItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::{
|
use super::{
|
||||||
TeamsCreateRequestDto, TeamsUpdateRequestDto, TeamInviteRequestDto,
|
TeamsCreateRequestDto, TeamsUpdateRequestDto, TeamInviteRequestDto,
|
||||||
TeamAcceptInvitationRequestDto, TeamsDetailItemDto,
|
TeamAcceptInvitationRequestDto, TeamsDetailItemDto, MemberTeamsDetailItemDto,
|
||||||
TeamMemberDto, TeamsRepository, TeamsSchema, TeamMembersSchema,
|
TeamMemberDto, TeamsRepository, TeamsSchema, TeamMembersSchema,
|
||||||
TeamInvitationsSchema, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto,
|
TeamInvitationsSchema, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto,
|
||||||
AdminTeamsListItemDto, AdminTeamsDetailItemDto
|
AdminTeamsListItemDto, AdminTeamsDetailItemDto
|
||||||
@@ -24,6 +24,8 @@ use chrono::Utc;
|
|||||||
pub trait TeamsServiceTrait: Send + Sync + 'static {
|
pub trait TeamsServiceTrait: Send + Sync + 'static {
|
||||||
fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
|
fn get_member_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
|
fn get_member_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_public_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_public_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_public_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_public_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn create_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, new_team: TeamsCreateRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn create_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, new_team: TeamsCreateRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
@@ -33,6 +35,7 @@ pub trait TeamsServiceTrait: Send + Sync + 'static {
|
|||||||
fn accept_invitation(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, accept: TeamAcceptInvitationRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn accept_invitation(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, accept: TeamAcceptInvitationRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_team_members(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_team_members(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn leave_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn leave_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
|
fn leave_current_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn search_teams(state: &AppState, search_params: TeamsSearchQueryDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn search_teams(state: &AppState, search_params: TeamsSearchQueryDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_admin_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_admin_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
fn get_admin_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
fn get_admin_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||||
@@ -107,69 +110,167 @@ impl TeamsService {
|
|||||||
|
|
||||||
impl TeamsServiceTrait for TeamsService {
|
impl TeamsServiceTrait for TeamsService {
|
||||||
fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
fn get_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||||
let state = state.to_owned();
|
let state = state.to_owned();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let repo = TeamsRepository::new(&state);
|
let repo = TeamsRepository::new(&state);
|
||||||
match repo.query_team_list(meta).await {
|
match repo.query_team_list(meta).await {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
let response = ResponseListSuccessDto {
|
let response = ResponseListSuccessDto {
|
||||||
data: data.data,
|
data: data.data,
|
||||||
meta: data.meta,
|
meta: data.meta,
|
||||||
};
|
};
|
||||||
success_list_response(response)
|
success_list_response(response)
|
||||||
|
}
|
||||||
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
}
|
}
|
||||||
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();
|
||||||
fn get_team_by_id(state: &AppState, id: String) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
Box::pin(async move {
|
||||||
let state = state.to_owned();
|
if Uuid::parse_str(&id).is_err() {
|
||||||
Box::pin(async move {
|
return common_response(StatusCode::BAD_REQUEST, "Invalid Team ID format");
|
||||||
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 })
|
|
||||||
}
|
}
|
||||||
Ok(_) => common_response(StatusCode::NOT_FOUND, "Team not found"),
|
let repo = TeamsRepository::new(&state);
|
||||||
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
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()),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
&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>> {
|
fn get_public_team_list(state: &AppState, meta: MetaRequestDto) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||||
let state = state.to_owned();
|
let state = state.to_owned();
|
||||||
@@ -761,8 +862,44 @@ impl TeamsServiceTrait for TeamsService {
|
|||||||
success_response(ResponseSuccessDto { data: member_dtos })
|
success_response(ResponseSuccessDto { data: member_dtos })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
|
||||||
|
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")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user