gitdataai/libs/service/project/audit.rs
2026-04-15 09:08:09 +08:00

184 lines
5.6 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use models::DateTimeUtc;
use models::projects::{MemberRole, project_audit_log};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use session::Session;
use uuid::Uuid;
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct AuditLogParams {
pub action: String,
pub details: Option<Value>,
}
use utoipa::__dev::ComposeSchema;
use utoipa::ToSchema;
use utoipa::openapi::schema::{ObjectBuilder, Type};
use utoipa::openapi::{KnownFormat, SchemaFormat};
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct AuditLogResponse {
pub id: i64,
pub project_uid: Uuid,
pub actor_uid: Uuid,
pub action: String,
pub details: Option<Value>,
pub created_at: DateTimeUtc,
}
impl ComposeSchema for AuditLogResponse {
fn compose(
_: Vec<utoipa::openapi::RefOr<utoipa::openapi::Schema>>,
) -> utoipa::openapi::RefOr<utoipa::openapi::Schema> {
utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(
ObjectBuilder::new()
.property(
"id",
ObjectBuilder::new().schema_type(Type::Integer).format(Some(
utoipa::openapi::schema::SchemaFormat::KnownFormat(
utoipa::openapi::schema::KnownFormat::Int64,
),
)),
)
.property(
"project_uid",
ObjectBuilder::new()
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(KnownFormat::Uuid))),
)
.property(
"actor_uid",
ObjectBuilder::new()
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(KnownFormat::Uuid))),
)
.property("action", ObjectBuilder::new().schema_type(Type::String))
.property("details", ObjectBuilder::new())
.required("id")
.required("project_uid")
.required("actor_uid")
.required("action")
.into(),
))
}
}
impl ToSchema for AuditLogResponse {}
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
pub struct AuditLogListResponse {
pub logs: Vec<AuditLogResponse>,
pub total: u64,
pub page: u64,
pub per_page: u64,
}
impl From<project_audit_log::Model> for AuditLogResponse {
fn from(log: project_audit_log::Model) -> Self {
AuditLogResponse {
id: log.id,
project_uid: log.project,
actor_uid: log.actor,
action: log.action,
details: log.details,
created_at: log.created_at,
}
}
}
impl AppService {
pub async fn project_log_audit(
&self,
project_name: String,
params: AuditLogParams,
ctx: &Session,
) -> Result<AuditLogResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project = self.utils_find_project_by_name(project_name).await?;
let log = project_audit_log::ActiveModel {
project: Set(project.id),
actor: Set(user_uid),
action: Set(params.action),
details: Set(params.details),
created_at: Set(chrono::Utc::now()),
..Default::default()
};
let created_log = log
.insert(&self.db)
.await
.map_err(|_| AppError::UserNotFound)?;
Ok(AuditLogResponse::from(created_log))
}
pub async fn project_get_audit_logs(
&self,
project_name: String,
page: Option<u64>,
per_page: Option<u64>,
ctx: &Session,
) -> Result<AuditLogListResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project = self.utils_find_project_by_name(project_name).await?;
self.utils_check_project_permission(
&project.id,
user_uid,
&[MemberRole::Owner, MemberRole::Admin],
)
.await?;
let page = page.unwrap_or(1);
let per_page = per_page.unwrap_or(20);
let logs = project_audit_log::Entity::find()
.filter(project_audit_log::Column::Project.eq(project.id))
.order_by_desc(project_audit_log::Column::CreatedAt)
.paginate(&self.db, per_page)
.fetch_page(page - 1)
.await
.map_err(|_| AppError::UserNotFound)?;
let total = project_audit_log::Entity::find()
.filter(project_audit_log::Column::Project.eq(project.id))
.count(&self.db)
.await
.map_err(|_| AppError::UserNotFound)?;
let logs = logs.into_iter().map(AuditLogResponse::from).collect();
Ok(AuditLogListResponse {
logs,
total,
page,
per_page,
})
}
pub async fn project_get_audit_log(
&self,
log_id: i64,
ctx: &Session,
) -> Result<AuditLogResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let log = project_audit_log::Entity::find_by_id(log_id)
.one(&self.db)
.await
.map_err(|_| AppError::UserNotFound)?
.ok_or(AppError::UserNotFound)?;
self.utils_check_project_permission(
&log.project,
user_uid,
&[MemberRole::Owner, MemberRole::Admin],
)
.await?;
Ok(AuditLogResponse::from(log))
}
}