a
This commit is contained in:
@@ -89,5 +89,7 @@ pub use v1::teams::{
|
||||
PublicTeamsDetailItemDto, TeamsDetailQueryDto, TeamsListQueryDto,
|
||||
TeamMembersSchema, TeamInvitationsSchema, TeamMembersQueryDto,
|
||||
TeamInvitationsQueryDto, MemberTeamsDetailItemDto,
|
||||
AddTeamMemberRequestDto, UpdateMemberRoleRequestDto,
|
||||
TeamInvitationListDto, MyInvitationDto
|
||||
};
|
||||
pub use v1::users::{UsersRepository, UsersSchema, UsersDetailItemDto, UsersCreateRequestDto};
|
||||
|
||||
@@ -43,7 +43,11 @@ pub use teams_dto::{
|
||||
TeamsListQueryDto,
|
||||
TeamMembersQueryDto,
|
||||
TeamInvitationsQueryDto,
|
||||
MemberTeamsDetailItemDto
|
||||
MemberTeamsDetailItemDto,
|
||||
AddTeamMemberRequestDto,
|
||||
UpdateMemberRoleRequestDto,
|
||||
TeamInvitationListDto,
|
||||
MyInvitationDto
|
||||
};
|
||||
|
||||
pub use teams_repository::TeamsRepository;
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::{
|
||||
TeamsCreateRequestDto, TeamsDetailItemDto, TeamsListItemDto, permissions_guard,
|
||||
TeamsUpdateRequestDto, TeamInviteRequestDto, TeamAcceptInvitationRequestDto,
|
||||
TeamMemberDto, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto,
|
||||
AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum
|
||||
AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum,
|
||||
AddTeamMemberRequestDto, UpdateMemberRoleRequestDto,
|
||||
TeamInvitationListDto, MyInvitationDto
|
||||
};
|
||||
use super::super::teams::{TeamsRepository, TeamMembersSchema};
|
||||
use axum::response::Response;
|
||||
@@ -153,12 +155,24 @@ pub async fn put_update_team(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct AddTeamMemberRequestDto {
|
||||
pub user_id: String,
|
||||
pub role: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/{id}/members",
|
||||
params(
|
||||
("id" = String, Path, description = "Team ID")
|
||||
),
|
||||
request_body = AddTeamMemberRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Add member to team successfully", body = ResponseSuccessDto<String>),
|
||||
(status = 401, description = "[AUTH] Unauthorized"),
|
||||
(status = 403, description = "[AUTH] Only team leader or members can add"),
|
||||
(status = 404, description = "[AUTH] Team not found")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn post_add_team_member(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
@@ -201,6 +215,24 @@ pub async fn post_add_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/{id}/members/{user_id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Team ID"),
|
||||
("user_id" = String, Path, description = "User ID to remove")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Member removed successfully", body = ResponseSuccessDto<String>),
|
||||
(status = 401, description = "[AUTH] Unauthorized"),
|
||||
(status = 403, description = "[AUTH] Only team leader can remove members"),
|
||||
(status = 404, description = "[AUTH] Team not found")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn delete_remove_team_member(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
@@ -236,6 +268,63 @@ pub async fn delete_remove_team_member(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/{id}/members/{user_id}/role",
|
||||
params(
|
||||
("id" = String, Path, description = "Team ID"),
|
||||
("user_id" = String, Path, description = "User ID")
|
||||
),
|
||||
request_body = UpdateMemberRoleRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Member role updated successfully", body = ResponseSuccessDto<String>),
|
||||
(status = 401, description = "[AUTH] Unauthorized"),
|
||||
(status = 403, description = "[AUTH] Only team leader can update roles"),
|
||||
(status = 404, description = "[AUTH] Team or member not found")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn put_update_member_role(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path((team_id, user_id)): Path<(String, String)>,
|
||||
Json(payload): Json<UpdateMemberRoleRequestDto>,
|
||||
) -> impl IntoResponse {
|
||||
let state_clone = state.clone();
|
||||
let is_admin = crate::permissions_guard(headers.clone(), axum::Extension(state_clone.clone()), vec![PermissionsEnum::ManageAllTeams]).await.is_ok();
|
||||
|
||||
let auth = permissions_guard(headers, axum::Extension(state.clone()), vec![]).await;
|
||||
let (claims, state) = match auth {
|
||||
Ok((c, s)) => (c, s),
|
||||
Err(response) => return response,
|
||||
};
|
||||
|
||||
let repo = TeamsRepository::new(&state);
|
||||
let thing_id = imphnen_utils::make_thing_from_enum(imphnen_libs::ResourceEnum::Teams, &team_id);
|
||||
let team = match repo.query_team_by_id(&thing_id).await {
|
||||
Ok(t) => t,
|
||||
Err(_) => return crate::common_response(axum::http::StatusCode::NOT_FOUND, "Team not found"),
|
||||
};
|
||||
|
||||
if !is_admin {
|
||||
// Only leader can update roles
|
||||
if team.leader_id.id.to_raw() != claims.user_id {
|
||||
return crate::common_response(axum::http::StatusCode::FORBIDDEN, "Only team leader can update member roles");
|
||||
}
|
||||
}
|
||||
|
||||
let user_thing = imphnen_utils::make_thing_from_enum(imphnen_libs::ResourceEnum::Users, &user_id);
|
||||
match repo.query_update_team_member_role(&thing_id, &user_thing, &payload.role).await {
|
||||
Ok(_) => crate::success_response(crate::ResponseSuccessDto {
|
||||
data: format!("Member role updated to: {}", payload.role)
|
||||
}),
|
||||
Err(e) => crate::common_response(axum::http::StatusCode::BAD_REQUEST, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
security(
|
||||
@@ -410,6 +499,75 @@ pub async fn get_my_team(
|
||||
authenticated(headers, Extension(state), |claims, state| TeamsService::get_my_team(&state, claims)).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/{id}/invitations",
|
||||
params(
|
||||
("id" = String, Path, description = "Team ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Get team invitations", body = ResponseSuccessDto<Vec<TeamInvitationListDto>>),
|
||||
(status = 401, description = "[AUTH] Unauthorized"),
|
||||
(status = 403, description = "[AUTH] Only team leader can view invitations"),
|
||||
(status = 404, description = "[AUTH] Team not found")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn get_team_invitations(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(team_id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
authenticated(headers, Extension(state), move |claims, state| TeamsService::get_team_invitations(&state, claims, team_id)).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/invitations/{token}",
|
||||
params(
|
||||
("token" = String, Path, description = "Invitation token")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Invitation cancelled", body = ResponseSuccessDto<String>),
|
||||
(status = 401, description = "[AUTH] Unauthorized"),
|
||||
(status = 403, description = "[AUTH] Only team leader can cancel invitations"),
|
||||
(status = 404, description = "[AUTH] Invitation not found")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn delete_invitation(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
Path(token): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
authenticated(headers, Extension(state), move |claims, state| TeamsService::cancel_invitation(&state, claims, token)).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
security(
|
||||
("Bearer" = [])
|
||||
),
|
||||
path = "/v1/teams/me/invitations",
|
||||
responses(
|
||||
(status = 200, description = "[AUTH] Get my pending invitations", body = ResponseSuccessDto<Vec<MyInvitationDto>>),
|
||||
(status = 401, description = "[AUTH] Unauthorized")
|
||||
),
|
||||
tag = "Teams"
|
||||
)]
|
||||
pub async fn get_my_invitations(
|
||||
headers: HeaderMap,
|
||||
Extension(state): Extension<AppState>,
|
||||
) -> impl IntoResponse {
|
||||
authenticated(headers, Extension(state), |claims, state| TeamsService::get_my_invitations(&state, claims)).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
security(
|
||||
@@ -504,7 +662,11 @@ pub fn teams_router() -> Router {
|
||||
.route("/{id}/members", axum::routing::get(get_team_members))
|
||||
.route("/{id}/members", axum::routing::post(post_add_team_member))
|
||||
.route("/{id}/members/{user_id}", axum::routing::delete(delete_remove_team_member))
|
||||
.route("/{id}/members/{user_id}/role", axum::routing::put(put_update_member_role))
|
||||
.route("/{id}/invitations", axum::routing::get(get_team_invitations))
|
||||
.route("/invitations/{token}", axum::routing::delete(delete_invitation))
|
||||
.route("/{id}/leave", axum::routing::post(post_leave_team))
|
||||
.route("/leave-me", axum::routing::post(post_leave_current_team))
|
||||
.route("/me", axum::routing::get(get_my_team))
|
||||
.route("/me/invitations", axum::routing::get(get_my_invitations))
|
||||
}
|
||||
|
||||
@@ -479,4 +479,51 @@ impl TeamsDetailQueryDto {
|
||||
}
|
||||
}
|
||||
|
||||
// Additional DTOs for Team Member Management
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct AddTeamMemberRequestDto {
|
||||
#[validate(length(min = 1, message = "User ID is required"))]
|
||||
pub user_id: String,
|
||||
|
||||
#[validate(length(max = 50, message = "Role cannot exceed 50 characters"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub role: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct UpdateMemberRoleRequestDto {
|
||||
#[validate(length(min = 1, max = 50, message = "Role must be between 1 and 50 characters"))]
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct TeamInvitationListDto {
|
||||
pub id: String,
|
||||
pub team_id: String,
|
||||
pub team_name: String,
|
||||
pub email: String,
|
||||
pub inviter_id: String,
|
||||
pub inviter_name: String,
|
||||
pub status: String,
|
||||
pub invite_code: String,
|
||||
pub expires_at: String,
|
||||
pub invited_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct MyInvitationDto {
|
||||
pub id: String,
|
||||
pub team_id: String,
|
||||
pub team_name: String,
|
||||
pub team_description: Option<String>,
|
||||
pub team_avatar: Option<String>,
|
||||
pub inviter_name: String,
|
||||
pub invite_code: String,
|
||||
pub status: String,
|
||||
pub expires_at: String,
|
||||
pub invited_at: String,
|
||||
}
|
||||
|
||||
// (previous custom validator removed; using validator::email(each = true) attribute)
|
||||
|
||||
|
||||
@@ -431,6 +431,102 @@ impl<'a> TeamsRepository<'a> {
|
||||
println!("Query 'query_remove_team_member' took: {elapsed:.2?}");
|
||||
}
|
||||
|
||||
Ok("Success remove team member".into())
|
||||
Ok("Success remove team member".into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn query_update_team_member_role(&self, team_id: &Thing, user_id: &Thing, role: &str) -> Result<String> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let conditions = build_multi_thing_condition(&[("team_id", team_id), ("user_id", user_id)]);
|
||||
let sql = format!(
|
||||
"UPDATE {} SET role = '{}' WHERE {} AND is_active = true",
|
||||
ResourceEnum::TeamMembers,
|
||||
role,
|
||||
conditions
|
||||
);
|
||||
|
||||
execute_safe_update_query(db, sql).await?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_update_team_member_role' took: {elapsed:.2?}");
|
||||
}
|
||||
|
||||
Ok("Success update team member role".into())
|
||||
}
|
||||
|
||||
pub async fn query_team_invitations(&self, team_id: &Thing) -> Result<Vec<TeamInvitationsQueryDto>> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let team_id_clone = team_id.clone();
|
||||
|
||||
let sql = format!(
|
||||
"SELECT * FROM {} WHERE team_id = $team_id AND status = 'pending' ORDER BY invited_at DESC",
|
||||
ResourceEnum::TeamInvitations
|
||||
);
|
||||
|
||||
let mut result = db.query(&sql).bind(("team_id", team_id_clone)).await?;
|
||||
let invitations: Vec<TeamInvitationsQueryDto> = result.take(0)?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_team_invitations' took: {elapsed:.2?}");
|
||||
}
|
||||
|
||||
Ok(invitations)
|
||||
}
|
||||
|
||||
pub async fn query_user_invitations(&self, email: &str) -> Result<Vec<TeamInvitationsQueryDto>> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let sql = format!(
|
||||
"SELECT * FROM {} WHERE email = '{}' AND status = 'pending' ORDER BY invited_at DESC",
|
||||
ResourceEnum::TeamInvitations,
|
||||
email
|
||||
);
|
||||
|
||||
let mut result = db.query(&sql).await?;
|
||||
let invitations: Vec<TeamInvitationsQueryDto> = result.take(0)?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_user_invitations' took: {elapsed:.2?}");
|
||||
}
|
||||
|
||||
Ok(invitations)
|
||||
}
|
||||
|
||||
pub async fn query_delete_invitation(&self, token: &str) -> Result<String> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let sql = format!(
|
||||
"UPDATE {} SET status = 'cancelled' WHERE invite_code = '{}'",
|
||||
ResourceEnum::TeamInvitations,
|
||||
token
|
||||
);
|
||||
|
||||
execute_safe_update_query(db, sql).await?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_delete_invitation' took: {elapsed:.2?}");
|
||||
}
|
||||
|
||||
Ok("Invitation cancelled successfully".into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ pub trait TeamsServiceTrait: Send + Sync + 'static {
|
||||
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 get_my_team(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
fn get_team_invitations(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
fn cancel_invitation(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, token: String) -> Pin<Box<dyn Future<Output = Response> + Send>>;
|
||||
fn get_my_invitations(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 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>>;
|
||||
@@ -1087,4 +1090,144 @@ impl TeamsServiceTrait for TeamsService {
|
||||
Self::get_public_team_by_id(&state, team_id).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_team_invitations(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, team_id: String) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||
let state = state.to_owned();
|
||||
Box::pin(async move {
|
||||
let repo = TeamsRepository::new(&state);
|
||||
let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team_id);
|
||||
|
||||
// Check if team exists and user is leader
|
||||
let team = match repo.query_team_by_id(&team_thing).await {
|
||||
Ok(t) => t,
|
||||
Err(_) => return common_response(StatusCode::NOT_FOUND, "Team not found"),
|
||||
};
|
||||
|
||||
if team.leader_id.id.to_raw() != claims.user_id {
|
||||
return common_response(StatusCode::FORBIDDEN, "Only team leader can view invitations");
|
||||
}
|
||||
|
||||
// Get invitations
|
||||
match repo.query_team_invitations(&team_thing).await {
|
||||
Ok(invitations) => {
|
||||
use crate::{v1::teams::TeamInvitationListDto, UsersRepository};
|
||||
let users_repo = UsersRepository::new(&state);
|
||||
|
||||
let mut invitation_list = Vec::new();
|
||||
for inv in invitations {
|
||||
// Get inviter name
|
||||
let inviter_name = match users_repo.query_user_by_id(&inv.inviter_id).await {
|
||||
Ok(user) => user.fullname,
|
||||
Err(_) => "Unknown".to_string(),
|
||||
};
|
||||
|
||||
invitation_list.push(TeamInvitationListDto {
|
||||
id: inv.id.id.to_raw(),
|
||||
team_id: inv.team_id.id.to_raw(),
|
||||
team_name: team.name.clone(),
|
||||
email: inv.email,
|
||||
inviter_id: inv.inviter_id.id.to_raw(),
|
||||
inviter_name,
|
||||
status: inv.status,
|
||||
invite_code: inv.invite_code,
|
||||
expires_at: inv.expires_at,
|
||||
invited_at: inv.invited_at,
|
||||
});
|
||||
}
|
||||
|
||||
success_response(ResponseSuccessDto { data: invitation_list })
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get team invitations: {}", e);
|
||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to retrieve invitations")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn cancel_invitation(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims, token: String) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||
let state = state.to_owned();
|
||||
Box::pin(async move {
|
||||
let repo = TeamsRepository::new(&state);
|
||||
|
||||
// Get invitation to check ownership
|
||||
let invitation = match repo.query_invitation_by_token(&token).await {
|
||||
Ok(inv) => inv,
|
||||
Err(_) => return common_response(StatusCode::NOT_FOUND, "Invitation not found"),
|
||||
};
|
||||
|
||||
// Check if user is the team leader
|
||||
let team_thing = invitation.team_id.clone();
|
||||
let team = match repo.query_team_by_id(&team_thing).await {
|
||||
Ok(t) => t,
|
||||
Err(_) => return common_response(StatusCode::NOT_FOUND, "Team not found"),
|
||||
};
|
||||
|
||||
if team.leader_id.id.to_raw() != claims.user_id {
|
||||
return common_response(StatusCode::FORBIDDEN, "Only team leader can cancel invitations");
|
||||
}
|
||||
|
||||
match repo.query_delete_invitation(&token).await {
|
||||
Ok(msg) => success_response(ResponseSuccessDto { data: msg }),
|
||||
Err(e) => {
|
||||
error!("Failed to cancel invitation: {}", e);
|
||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to cancel invitation")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_my_invitations(state: &AppState, claims: imphnen_libs::jsonwebtoken::Claims) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||
let state = state.to_owned();
|
||||
Box::pin(async move {
|
||||
use crate::{v1::teams::MyInvitationDto, UsersRepository};
|
||||
let users_repo = UsersRepository::new(&state);
|
||||
|
||||
// Get user email
|
||||
let user_thing = make_thing_from_enum(ResourceEnum::Users, &claims.user_id);
|
||||
let user = match users_repo.query_user_by_id(&user_thing).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||
};
|
||||
|
||||
let repo = TeamsRepository::new(&state);
|
||||
match repo.query_user_invitations(&user.email).await {
|
||||
Ok(invitations) => {
|
||||
let mut my_invitations = Vec::new();
|
||||
for inv in invitations {
|
||||
// Get team details
|
||||
let team = match repo.query_team_by_id(&inv.team_id).await {
|
||||
Ok(t) => t,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
// Get inviter name
|
||||
let inviter_name = match users_repo.query_user_by_id(&inv.inviter_id).await {
|
||||
Ok(u) => u.fullname,
|
||||
Err(_) => "Unknown".to_string(),
|
||||
};
|
||||
|
||||
my_invitations.push(MyInvitationDto {
|
||||
id: inv.id.id.to_raw(),
|
||||
team_id: inv.team_id.id.to_raw(),
|
||||
team_name: team.name,
|
||||
team_description: team.description,
|
||||
team_avatar: team.avatar,
|
||||
inviter_name,
|
||||
invite_code: inv.invite_code,
|
||||
status: inv.status,
|
||||
expires_at: inv.expires_at,
|
||||
invited_at: inv.invited_at,
|
||||
});
|
||||
}
|
||||
|
||||
success_response(ResponseSuccessDto { data: my_invitations })
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get user invitations: {}", e);
|
||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, "Failed to retrieve invitations")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user