docbox_core/folders/
create_folder.rs

1use 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::TenantSearchIndex;
14use std::ops::DerefMut;
15use thiserror::Error;
16use uuid::Uuid;
17
18#[derive(Debug, Error)]
19pub enum CreateFolderError {
20    /// Database error occurred
21    #[error(transparent)]
22    Database(#[from] DbErr),
23
24    /// Failed to create the search index
25    #[error("failed to create folder search index: {0}")]
26    CreateIndex(anyhow::Error),
27}
28
29pub struct CreateFolderData {
30    /// Folder create the folder within
31    pub folder: Folder,
32
33    /// Folder name
34    pub name: String,
35
36    /// User creating the link
37    pub created_by: Option<UserId>,
38}
39
40/// State structure to keep track of resources created
41/// as a side effect from creating a folder
42#[derive(Default)]
43struct CreateFolderState {
44    /// Search index files
45    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            // Attempt to rollback any allocated resources in the background
60            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    // Create file to commit against
81    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    // Add folder to search index
94    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    // Publish an event
102    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    // Revert file index data
112    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}