docbox_core/files/
delete_file.rs1use crate::{
2 events::{TenantEventMessage, TenantEventPublisher},
3 files::generated::{GeneratedFileDeleteResult, delete_generated_files},
4};
5use docbox_database::{
6 DbErr, DbPool,
7 models::{
8 document_box::{DocumentBoxScopeRaw, WithScope},
9 file::File,
10 generated_file::GeneratedFile,
11 },
12};
13use docbox_search::{SearchError, TenantSearchIndex};
14use docbox_storage::{StorageLayerError, TenantStorageLayer};
15use futures::{StreamExt, stream::FuturesUnordered};
16use thiserror::Error;
17use tracing::error;
18
19#[derive(Debug, Error)]
20pub enum DeleteFileError {
21 #[error("failed to delete tenant search index: {0}")]
23 DeleteIndex(SearchError),
24
25 #[error(transparent)]
27 Database(#[from] DbErr),
28
29 #[error("failed to remove file from storage: {0}")]
31 DeleteFileStorage(StorageLayerError),
32
33 #[error("failed to remove generated file from storage: {0}")]
35 DeleteGeneratedFileStorage(StorageLayerError),
36}
37
38pub async fn delete_file(
51 db: &DbPool,
52 storage: &TenantStorageLayer,
53 search: &TenantSearchIndex,
54 events: &TenantEventPublisher,
55 file: File,
56 scope: DocumentBoxScopeRaw,
57) -> Result<(), DeleteFileError> {
58 let generated = GeneratedFile::find_all(db, file.id)
59 .await
60 .inspect_err(|error| tracing::error!(?error, "failed to query generated files"))?;
61
62 match delete_generated_files(storage, &generated).await {
63 GeneratedFileDeleteResult::Ok => {}
64 GeneratedFileDeleteResult::Err(deleted, err) => {
65 let mut delete_files_future = generated
67 .into_iter()
68 .filter(|file| deleted.contains(&file.id))
69 .map(|file| file.delete(db))
70 .collect::<FuturesUnordered<_>>();
71
72 while let Some(result) = delete_files_future.next().await {
74 if let Err(cause) = result {
75 tracing::error!(?cause, "failed to delete generated file from db");
76 }
77 }
78
79 return Err(DeleteFileError::DeleteGeneratedFileStorage(err));
80 }
81 }
82
83 let mut delete_files_future = generated
84 .into_iter()
85 .map(|file| file.delete(db))
86 .collect::<FuturesUnordered<_>>();
87
88 while let Some(result) = delete_files_future.next().await {
90 if let Err(cause) = result {
91 tracing::error!(?cause, "failed to delete generated file");
92 return Err(DeleteFileError::Database(cause));
93 }
94 }
95
96 storage
98 .delete_file(&file.file_key)
99 .await
100 .map_err(DeleteFileError::DeleteFileStorage)?;
101
102 search
104 .delete_data(file.id)
105 .await
106 .map_err(DeleteFileError::DeleteIndex)?;
107
108 let result = file
110 .delete(db)
111 .await
112 .inspect_err(|error| tracing::error!(?error, "failed to delete file from database"))?;
113
114 if result.rows_affected() < 1 {
116 return Ok(());
117 }
118
119 events.publish_event(TenantEventMessage::FileDeleted(WithScope::new(file, scope)));
121
122 Ok(())
123}