docbox_core/folders/
delete_folder.rs1use crate::events::{TenantEventMessage, TenantEventPublisher};
2use crate::files::delete_file::{DeleteFileError, delete_file};
3use crate::links::delete_link::{DeleteLinkError, 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::{SearchError, TenantSearchIndex};
14use docbox_storage::TenantStorageLayer;
15use std::collections::VecDeque;
16use thiserror::Error;
17
18pub enum RemoveStackItem {
20 Folder(Folder),
22 EmptyFolder(Folder),
25 File(File),
27 Link(Link),
29}
30
31#[derive(Debug, Error)]
32pub enum DeleteFolderError {
33 #[error("failed to resolve folder for deletion")]
34 ResolveFolder,
35 #[error(transparent)]
36 Folder(#[from] InternalDeleteFolderError),
37 #[error(transparent)]
38 File(#[from] DeleteFileError),
39 #[error(transparent)]
40 Link(#[from] DeleteLinkError),
41}
42
43#[derive(Debug, Error)]
44pub enum InternalDeleteFolderError {
45 #[error(transparent)]
46 Search(#[from] SearchError),
47
48 #[error("failed to delete folder metadata")]
49 Database,
50}
51
52pub async fn delete_folder(
53 db: &DbPool,
54 storage: &TenantStorageLayer,
55 search: &TenantSearchIndex,
56 events: &TenantEventPublisher,
57 folder: Folder,
58) -> Result<(), DeleteFolderError> {
59 let mut stack = VecDeque::new();
61
62 let document_box = folder.document_box.clone();
63
64 stack.push_back(RemoveStackItem::Folder(folder));
66
67 while let Some(item) = stack.pop_front() {
68 match item {
69 RemoveStackItem::Folder(folder) => {
70 let resolved = ResolvedFolder::resolve(db, &folder)
72 .await
73 .map_err(|error| {
74 tracing::error!(?error, "failed to resolve folder for deletion");
75 DeleteFolderError::ResolveFolder
76 })?;
77
78 stack.push_front(RemoveStackItem::EmptyFolder(folder));
80
81 for item in resolved.folders {
83 stack.push_front(RemoveStackItem::Folder(item));
84 }
85
86 for item in resolved.files {
87 stack.push_front(RemoveStackItem::File(item));
88 }
89
90 for item in resolved.links {
91 stack.push_front(RemoveStackItem::Link(item));
92 }
93 }
94 RemoveStackItem::EmptyFolder(folder) => {
95 internal_delete_folder(db, search, events, folder).await?;
96 }
97 RemoveStackItem::File(file) => {
98 delete_file(db, storage, search, events, file, document_box.clone()).await?;
99 }
100 RemoveStackItem::Link(link) => {
101 delete_link(db, search, events, link, document_box.clone()).await?;
102 }
103 }
104 }
105
106 Ok(())
107}
108
109async fn internal_delete_folder(
112 db: &DbPool,
113 search: &TenantSearchIndex,
114 events: &TenantEventPublisher,
115 folder: Folder,
116) -> Result<(), InternalDeleteFolderError> {
117 search.delete_data(folder.id).await?;
119
120 let result = folder.delete(db).await.map_err(|error| {
121 tracing::error!(?error, "failed to delete folder");
122 InternalDeleteFolderError::Database
123 })?;
124
125 let document_box = folder.document_box.clone();
126
127 if result.rows_affected() < 1 {
129 return Ok(());
130 }
131
132 events.publish_event(TenantEventMessage::FolderDeleted(WithScope::new(
134 folder,
135 document_box,
136 )));
137
138 Ok(())
139}