Skip to main content

docbox_core/folders/
delete_folder.rs

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