Skip to content
Clone
d5939db
215 lines 6.0 KB Rust
use db::project::project_documents as doc_entity;
use db::project::project_documents::Column as Dc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, Set,
};
use time::OffsetDateTime;
use uuid::Uuid;
use crate::error::{ProjectError, ProjectResult};
use crate::features::lifecycle::usecase::log_activity;
use crate::features::ProjectService;
use crate::permission;
use super::{CreateDocumentCmd, DocumentPage, DocumentView, UpdateDocumentCmd};
pub async fn create_document(
svc: &ProjectService,
actor: Uuid,
space_id: Uuid,
project_id: Uuid,
cmd: CreateDocumentCmd,
) -> ProjectResult<DocumentView> {
crate::features::authz::require(
svc,
actor,
space_id,
project_id,
permission::MANAGE_DOCUMENTS,
)
.await?;
let slug_exists = doc_entity::Entity::find()
.filter(Dc::ProjectId.eq(project_id))
.filter(Dc::Slug.eq(&cmd.slug))
.count(&svc.db.reader())
.await?;
if slug_exists > 0 {
return Err(ProjectError::Conflict(format!(
"document slug '{}' already exists in project {project_id}",
cmd.slug
)));
}
let id = Uuid::new_v4();
let now = OffsetDateTime::now_utc();
let row = doc_entity::ActiveModel {
id: Set(id),
project_id: Set(project_id),
parent_id: Set(cmd.parent_id),
creator_id: Set(actor),
last_editor_id: Set(Some(actor)),
title: Set(cmd.title),
slug: Set(cmd.slug),
content: Set(cmd.content),
icon: Set(cmd.icon),
created_at: Set(now),
updated_at: Set(Some(now)),
}
.insert(&svc.db.writer())
.await?;
log_activity(svc, project_id, actor, "document_created", "document", Some(id), None).await;
Ok(document_view(&row))
}
pub async fn list_documents(
svc: &ProjectService,
actor: Uuid,
space_id: Uuid,
project_id: Uuid,
) -> ProjectResult<DocumentPage> {
crate::features::authz::require(svc, actor, space_id, project_id, permission::VIEW_PROJECT)
.await?;
let rows = doc_entity::Entity::find()
.filter(Dc::ProjectId.eq(project_id))
.order_by_asc(Dc::Title)
.all(&svc.db.reader())
.await?;
let items = rows.iter().map(document_view).collect::<Vec<_>>();
let total = items.len() as u64;
Ok(DocumentPage { total, items })
}
pub async fn get_document(
svc: &ProjectService,
actor: Uuid,
space_id: Uuid,
project_id: Uuid,
doc_id: Uuid,
) -> ProjectResult<DocumentView> {
crate::features::authz::require(svc, actor, space_id, project_id, permission::VIEW_PROJECT)
.await?;
let row = doc_entity::Entity::find_by_id(doc_id)
.filter(Dc::ProjectId.eq(project_id))
.one(&svc.db.reader())
.await?
.ok_or_else(|| {
ProjectError::NotFound(format!("document {doc_id} in project {project_id}"))
})?;
Ok(document_view(&row))
}
pub async fn update_document(
svc: &ProjectService,
actor: Uuid,
space_id: Uuid,
project_id: Uuid,
doc_id: Uuid,
cmd: UpdateDocumentCmd,
) -> ProjectResult<DocumentView> {
crate::features::authz::require(
svc,
actor,
space_id,
project_id,
permission::MANAGE_DOCUMENTS,
)
.await?;
let current = doc_entity::Entity::find_by_id(doc_id)
.filter(Dc::ProjectId.eq(project_id))
.one(&svc.db.reader())
.await?
.ok_or_else(|| {
ProjectError::NotFound(format!("document {doc_id} in project {project_id}"))
})?;
if let Some(slug) = &cmd.slug
&& slug != &current.slug
{
let slug_exists = doc_entity::Entity::find()
.filter(Dc::ProjectId.eq(project_id))
.filter(Dc::Slug.eq(slug))
.filter(Dc::Id.ne(doc_id))
.count(&svc.db.reader())
.await?;
if slug_exists > 0 {
return Err(ProjectError::Conflict(format!(
"document slug '{slug}' already exists in project {project_id}"
)));
}
}
let mut active: doc_entity::ActiveModel = current.into();
if cmd.parent_id.is_some() {
active.parent_id = Set(cmd.parent_id);
}
if let Some(title) = cmd.title {
active.title = Set(title);
}
if let Some(slug) = cmd.slug {
active.slug = Set(slug);
}
if let Some(content) = cmd.content {
active.content = Set(content);
}
if cmd.icon.is_some() {
active.icon = Set(cmd.icon);
}
active.last_editor_id = Set(Some(actor));
active.updated_at = Set(Some(OffsetDateTime::now_utc()));
let updated = active.update(&svc.db.writer()).await?;
log_activity(svc, project_id, actor, "document_updated", "document", Some(doc_id), None)
.await;
Ok(document_view(&updated))
}
pub async fn delete_document(
svc: &ProjectService,
actor: Uuid,
space_id: Uuid,
project_id: Uuid,
doc_id: Uuid,
) -> ProjectResult<()> {
crate::features::authz::require(
svc,
actor,
space_id,
project_id,
permission::MANAGE_DOCUMENTS,
)
.await?;
let deleted = doc_entity::Entity::delete_many()
.filter(Dc::Id.eq(doc_id))
.filter(Dc::ProjectId.eq(project_id))
.exec(&svc.db.writer())
.await?;
if deleted.rows_affected == 0 {
return Err(ProjectError::NotFound(format!(
"document {doc_id} in project {project_id}"
)));
}
log_activity(svc, project_id, actor, "document_deleted", "document", Some(doc_id), None)
.await;
Ok(())
}
fn document_view(m: &doc_entity::Model) -> DocumentView {
DocumentView {
id: m.id,
project_id: m.project_id,
parent_id: m.parent_id,
creator_id: m.creator_id,
last_editor_id: m.last_editor_id,
title: m.title.clone(),
slug: m.slug.clone(),
content: m.content.clone(),
icon: m.icon.clone(),
created_at: m.created_at,
updated_at: m.updated_at,
}
}