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::TenantSearchIndex;
14use docbox_storage::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(anyhow::Error),
24
25 #[error(transparent)]
27 Database(#[from] DbErr),
28
29 #[error("failed to remove file from storage: {0}")]
31 DeleteFileStorage(anyhow::Error),
32
33 #[error("failed to remove generated file from storage: {0}")]
35 DeleteGeneratedFileStorage(anyhow::Error),
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 file.delete(db)
110 .await
111 .inspect_err(|error| tracing::error!(?error, "failed to delete file from database"))?;
112
113 events.publish_event(TenantEventMessage::FileDeleted(WithScope::new(file, scope)));
115
116 Ok(())
117}