docbox_core/folders/
create_folder.rs1use crate::{
2 events::{TenantEventMessage, TenantEventPublisher},
3 folders::index_folder::store_folder_index,
4};
5use docbox_database::{
6 DbErr, DbPool,
7 models::{
8 document_box::WithScope,
9 folder::{CreateFolder, Folder},
10 user::UserId,
11 },
12};
13use docbox_search::{SearchError, TenantSearchIndex};
14use std::ops::DerefMut;
15use thiserror::Error;
16use uuid::Uuid;
17
18#[derive(Debug, Error)]
19pub enum CreateFolderError {
20 #[error(transparent)]
22 Database(#[from] DbErr),
23
24 #[error("failed to create folder search index: {0}")]
26 CreateIndex(SearchError),
27}
28
29pub struct CreateFolderData {
30 pub folder: Folder,
32
33 pub name: String,
35
36 pub created_by: Option<UserId>,
38}
39
40#[derive(Default)]
43struct CreateFolderState {
44 pub search_index_files: Vec<Uuid>,
46}
47
48pub async fn safe_create_folder(
49 db: &DbPool,
50 search: TenantSearchIndex,
51 events: &TenantEventPublisher,
52 create: CreateFolderData,
53) -> Result<Folder, CreateFolderError> {
54 let mut create_state = CreateFolderState::default();
55
56 create_folder(db, &search, events, create, &mut create_state)
57 .await
58 .inspect_err(|_| {
59 tokio::spawn(rollback_create_folder(search, create_state));
61 })
62}
63
64async fn create_folder(
65 db: &DbPool,
66 search: &TenantSearchIndex,
67 events: &TenantEventPublisher,
68 create: CreateFolderData,
69 create_state: &mut CreateFolderState,
70) -> Result<Folder, CreateFolderError> {
71 tracing::debug!("creating folder");
72
73 let mut db = db
74 .begin()
75 .await
76 .inspect_err(|error| tracing::error!(?error, "failed to being transaction"))?;
77
78 let folder_id = create.folder.id;
79
80 let folder = Folder::create(
82 db.deref_mut(),
83 CreateFolder {
84 name: create.name,
85 document_box: create.folder.document_box,
86 folder_id: Some(folder_id),
87 created_by: create.created_by,
88 },
89 )
90 .await
91 .inspect_err(|error| tracing::error!(?error, "failed to create folder"))?;
92
93 store_folder_index(search, &folder, folder_id).await?;
95 create_state.search_index_files.push(folder.id);
96
97 db.commit()
98 .await
99 .inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
100
101 events.publish_event(TenantEventMessage::FolderCreated(WithScope::new(
103 folder.clone(),
104 folder.document_box.clone(),
105 )));
106
107 Ok(folder)
108}
109
110async fn rollback_create_folder(search: TenantSearchIndex, create_state: CreateFolderState) {
111 for id in create_state.search_index_files {
113 if let Err(error) = search.delete_data(id).await {
114 tracing::error!(
115 ?error, index_id = %id,
116 "failed to rollback created folder search index",
117 );
118 }
119 }
120}