fix: resolve E2E audit findings and add Phase C frontend pages

- Fix audit_log handler multi-tenant bug: use Extension<TenantContext>
  instead of hardcoded default_tenant_id
- Fix sendMessage route mismatch: frontend /messages/send → /messages
- Add POST /users/{id}/roles backend route for role assignment
- Add task.completed event payload: started_by + instance_id for
  notification delivery
- Add audit log viewer frontend page (AuditLogViewer.tsx)
- Add language management frontend page (LanguageManager.tsx)
- Add api/auditLogs.ts and api/languages.ts modules
This commit is contained in:
iven
2026-04-12 15:57:33 +08:00
parent 14f431efff
commit 3b41e73f82
11 changed files with 567 additions and 12 deletions

View File

@@ -277,6 +277,70 @@ impl UserService {
Ok(())
}
/// Replace all role assignments for a user within a tenant.
pub async fn assign_roles(
user_id: Uuid,
tenant_id: Uuid,
operator_id: Uuid,
role_ids: &[Uuid],
db: &sea_orm::DatabaseConnection,
) -> AuthResult<Vec<RoleResp>> {
// 验证用户存在
let _user = user::Entity::find_by_id(user_id)
.one(db)
.await
.map_err(|e| AuthError::Validation(e.to_string()))?
.filter(|u| u.tenant_id == tenant_id && u.deleted_at.is_none())
.ok_or_else(|| AuthError::Validation("用户不存在".to_string()))?;
// 验证所有角色存在且属于当前租户
if !role_ids.is_empty() {
let found = role::Entity::find()
.filter(role::Column::Id.is_in(role_ids.iter().copied()))
.filter(role::Column::TenantId.eq(tenant_id))
.all(db)
.await
.map_err(|e| AuthError::Validation(e.to_string()))?;
if found.len() != role_ids.len() {
return Err(AuthError::Validation("部分角色不存在或不属于当前租户".to_string()));
}
}
// 删除旧的角色分配
user_role::Entity::delete_many()
.filter(user_role::Column::UserId.eq(user_id))
.filter(user_role::Column::TenantId.eq(tenant_id))
.exec(db)
.await
.map_err(|e| AuthError::Validation(e.to_string()))?;
// 创建新的角色分配
let now = chrono::Utc::now();
for &role_id in role_ids {
let assignment = user_role::ActiveModel {
user_id: Set(user_id),
role_id: Set(role_id),
tenant_id: Set(tenant_id),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(operator_id),
updated_by: Set(operator_id),
deleted_at: Set(None),
version: Set(1),
};
assignment.insert(db).await.map_err(|e| AuthError::Validation(e.to_string()))?;
}
audit_service::record(
AuditLog::new(tenant_id, Some(operator_id), "user.assign_roles", "user")
.with_resource_id(user_id),
db,
)
.await;
Self::fetch_user_role_resps(user_id, tenant_id, db).await
}
/// Fetch RoleResp DTOs for a given user within a tenant.
async fn fetch_user_role_resps(
user_id: Uuid,