docbox_core/folders/
delete_folder.rs

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