use crate::{
events::{TenantEventMessage, TenantEventPublisher},
files::generated::{GeneratedFileDeleteResult, delete_generated_files},
};
use docbox_database::{
DbErr, DbPool,
models::{
document_box::{DocumentBoxScopeRaw, WithScope},
file::File,
generated_file::GeneratedFile,
},
};
use docbox_search::{SearchError, TenantSearchIndex};
use docbox_storage::{StorageLayer, StorageLayerError};
use futures::{StreamExt, stream::FuturesUnordered};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum DeleteFileError {
#[error("failed to delete tenant search index: {0}")]
DeleteIndex(SearchError),
#[error(transparent)]
Database(#[from] DbErr),
#[error("failed to remove file from storage: {0}")]
DeleteFileStorage(StorageLayerError),
#[error("failed to remove generated file from storage: {0}")]
DeleteGeneratedFileStorage(StorageLayerError),
}
pub async fn delete_file(
db: &DbPool,
storage: &StorageLayer,
search: &TenantSearchIndex,
events: &TenantEventPublisher,
file: File,
scope: DocumentBoxScopeRaw,
) -> Result<(), DeleteFileError> {
let generated = GeneratedFile::find_all(db, file.id)
.await
.inspect_err(|error| tracing::error!(?error, "failed to query generated files"))?;
match delete_generated_files(storage, &generated).await {
GeneratedFileDeleteResult::Ok => {}
GeneratedFileDeleteResult::Err(deleted, err) => {
let mut delete_files_future = generated
.into_iter()
.filter(|file| deleted.contains(&file.id))
.map(|file| file.delete(db))
.collect::<FuturesUnordered<_>>();
while let Some(result) = delete_files_future.next().await {
if let Err(error) = result {
tracing::error!(?error, "failed to delete generated file from db");
}
}
return Err(DeleteFileError::DeleteGeneratedFileStorage(err));
}
}
let mut delete_files_future = generated
.into_iter()
.map(|file| file.delete(db))
.collect::<FuturesUnordered<_>>();
while let Some(result) = delete_files_future.next().await {
if let Err(error) = result {
tracing::error!(?error, "failed to delete generated file");
return Err(DeleteFileError::Database(error));
}
}
storage
.delete_file(&file.file_key)
.await
.map_err(DeleteFileError::DeleteFileStorage)?;
search
.delete_data(file.id)
.await
.map_err(DeleteFileError::DeleteIndex)?;
let result = file
.delete(db)
.await
.inspect_err(|error| tracing::error!(?error, "failed to delete file from database"))?;
if result.rows_affected() < 1 {
return Ok(());
}
events.publish_event(TenantEventMessage::FileDeleted(WithScope::new(file, scope)));
Ok(())
}