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, } 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, pub created_at: DateTimeUtc, } impl ComposeSchema for AuditLogResponse { fn compose( _: Vec>, ) -> utoipa::openapi::RefOr { 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, pub total: u64, pub page: u64, pub per_page: u64, } impl From 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 { 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, per_page: Option, ctx: &Session, ) -> Result { 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 { 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)) } }