fix(backend): add project_name and invited_by_username to InvitationResponse

InvitationResponse was missing project_name and invited_by_username fields,
causing /invitations accept to redirect to /project/undefined.
Now populated via async from_model() with batch DB lookups.
This commit is contained in:
ZhenYi 2026-04-18 19:24:43 +08:00
parent 5579e6c58e
commit c4fb943e07

View File

@ -1,8 +1,9 @@
use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use futures::future::join_all;
use models::projects::{
MemberRole, project_audit_log, project_member_invitations, project_members,
project, MemberRole, project_audit_log, project_member_invitations, project_members,
};
use models::users::{user, user_email};
use sea_orm::*;
@ -13,8 +14,10 @@ use uuid::Uuid;
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct InvitationResponse {
pub project_uid: Uuid,
pub project_name: String,
pub user_uid: Uuid,
pub invited_by: Uuid,
pub invited_by_username: Option<String>,
pub scope: String,
pub accepted: bool,
pub accepted_at: Option<DateTime<Utc>>,
@ -31,18 +34,36 @@ pub struct InvitationListResponse {
pub per_page: u64,
}
impl From<project_member_invitations::Model> for InvitationResponse {
fn from(invitation: project_member_invitations::Model) -> Self {
impl InvitationResponse {
pub async fn from_model(
inv: project_member_invitations::Model,
db: &DatabaseConnection,
) -> Self {
let project_name = project::Entity::find_by_id(inv.project)
.one(db)
.await
.ok()
.flatten()
.map(|p| p.name)
.unwrap_or_default();
let invited_by_username = user::Entity::find_by_id(inv.invited_by)
.one(db)
.await
.ok()
.flatten()
.map(|u| u.username);
InvitationResponse {
project_uid: invitation.project,
user_uid: invitation.user,
invited_by: invitation.invited_by,
scope: invitation.scope,
accepted: invitation.accepted,
accepted_at: invitation.accepted_at,
rejected: invitation.rejected,
rejected_at: invitation.rejected_at,
created_at: invitation.created_at,
project_uid: inv.project,
project_name,
user_uid: inv.user,
invited_by: inv.invited_by,
invited_by_username,
scope: inv.scope,
accepted: inv.accepted,
accepted_at: inv.accepted_at,
rejected: inv.rejected,
rejected_at: inv.rejected_at,
created_at: inv.created_at,
}
}
}
@ -82,9 +103,31 @@ impl AppService {
.count(&self.db)
.await?;
let project_name = project.name.clone();
let inviter_ids: Vec<Uuid> = invitations.iter().map(|i| i.invited_by).collect();
let inviters: std::collections::HashMap<Uuid, Option<String>> = user::Entity::find()
.filter(user::Column::Uid.is_in(inviter_ids))
.all(&self.db)
.await?
.into_iter()
.map(|u| (u.uid, Some(u.username)))
.collect();
let invitations = invitations
.into_iter()
.map(InvitationResponse::from)
.map(|inv| InvitationResponse {
project_uid: inv.project,
project_name: project_name.clone(),
user_uid: inv.user,
invited_by: inv.invited_by,
invited_by_username: inviters.get(&inv.invited_by).cloned().flatten(),
scope: inv.scope,
accepted: inv.accepted,
accepted_at: inv.accepted_at,
rejected: inv.rejected,
rejected_at: inv.rejected_at,
created_at: inv.created_at,
})
.collect();
Ok(InvitationListResponse {
@ -122,9 +165,12 @@ impl AppService {
.count(&self.db)
.await?;
let invitations = invitations
let invitations = join_all(
invitations
.into_iter()
.map(InvitationResponse::from)
.map(|inv| InvitationResponse::from_model(inv, self.db.writer())),
)
.await;
.collect();
Ok(InvitationListResponse {