docbox_core/folders/
delete_folder.rs

1use crate::events::{TenantEventMessage, TenantEventPublisher};
2use crate::files::delete_file::delete_file;
3use crate::links::delete_link::delete_link;
4use docbox_database::{
5    DbPool,
6    models::{
7        document_box::WithScope,
8        file::File,
9        folder::{Folder, ResolvedFolder},
10        link::Link,
11    },
12};
13use docbox_search::TenantSearchIndex;
14use docbox_storage::TenantStorageLayer;
15use std::collections::VecDeque;
16
17/// Item to be removed
18pub enum RemoveStackItem {
19    /// Folder that needs to have its children removed
20    Folder(Folder),
21    /// Folder that has already been processed, all children
22    /// should have been removed by previous stack passes
23    EmptyFolder(Folder),
24    /// File to remove
25    File(File),
26    /// Link to remove
27    Link(Link),
28}
29
30pub async fn delete_folder(
31    db: &DbPool,
32    storage: &TenantStorageLayer,
33    search: &TenantSearchIndex,
34    events: &TenantEventPublisher,
35    folder: Folder,
36) -> anyhow::Result<()> {
37    // Stack to store the next item to delete
38    let mut stack = VecDeque::new();
39
40    let document_box = folder.document_box.clone();
41
42    // Push the first folder item
43    stack.push_back(RemoveStackItem::Folder(folder));
44
45    while let Some(item) = stack.pop_front() {
46        match item {
47            RemoveStackItem::Folder(folder) => {
48                // Resolve the folder children
49                let resolved = ResolvedFolder::resolve(db, &folder).await?;
50
51                // Push the empty folder first (Will be taken out last)
52                stack.push_front(RemoveStackItem::EmptyFolder(folder));
53
54                // Populate the stack with the resolved files, links, and folders
55                for item in resolved.folders {
56                    stack.push_front(RemoveStackItem::Folder(item));
57                }
58
59                for item in resolved.files {
60                    stack.push_front(RemoveStackItem::File(item));
61                }
62
63                for item in resolved.links {
64                    stack.push_front(RemoveStackItem::Link(item));
65                }
66            }
67            RemoveStackItem::EmptyFolder(folder) => {
68                internal_delete_folder(db, search, events, folder).await?;
69            }
70            RemoveStackItem::File(file) => {
71                delete_file(db, storage, search, events, file, document_box.clone()).await?;
72            }
73            RemoveStackItem::Link(link) => {
74                delete_link(db, search, events, link, document_box.clone()).await?;
75            }
76        }
77    }
78
79    Ok(())
80}
81
82/// Deletes the folder itself and associated metadata, use [delete_folder]
83/// to properly delete the folder and all of its recursive contents
84async fn internal_delete_folder(
85    db: &DbPool,
86    search: &TenantSearchIndex,
87    events: &TenantEventPublisher,
88    folder: Folder,
89) -> anyhow::Result<()> {
90    // Delete the indexed file contents
91    search.delete_data(folder.id).await?;
92
93    let result = folder
94        .delete(db)
95        .await
96        .inspect_err(|error| tracing::error!(?error, "failed to delete folder"))?;
97
98    let document_box = folder.document_box.clone();
99
100    // Check we actually removed something before emitting an event
101    if result.rows_affected() < 1 {
102        return Ok(());
103    }
104
105    // Publish an event
106    events.publish_event(TenantEventMessage::FolderDeleted(WithScope::new(
107        folder,
108        document_box,
109    )));
110
111    Ok(())
112}