use crate::{
events::{TenantEventMessage, TenantEventPublisher},
folders::index_folder::store_folder_index,
};
use docbox_database::{
DbErr, DbPool,
models::{
document_box::WithScope,
folder::{CreateFolder, Folder},
user::UserId,
},
};
use docbox_search::{SearchError, TenantSearchIndex};
use std::ops::DerefMut;
use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Error)]
pub enum CreateFolderError {
#[error(transparent)]
Database(#[from] DbErr),
#[error("failed to create folder search index: {0}")]
CreateIndex(SearchError),
}
pub struct CreateFolderData {
pub folder: Folder,
pub name: String,
pub created_by: Option<UserId>,
}
#[derive(Default)]
struct CreateFolderState {
pub search_index_files: Vec<Uuid>,
}
pub async fn safe_create_folder(
db: &DbPool,
search: TenantSearchIndex,
events: &TenantEventPublisher,
create: CreateFolderData,
) -> Result<Folder, CreateFolderError> {
let mut create_state = CreateFolderState::default();
create_folder(db, &search, events, create, &mut create_state)
.await
.inspect_err(|_| {
tokio::spawn(rollback_create_folder(search, create_state));
})
}
async fn create_folder(
db: &DbPool,
search: &TenantSearchIndex,
events: &TenantEventPublisher,
create: CreateFolderData,
create_state: &mut CreateFolderState,
) -> Result<Folder, CreateFolderError> {
tracing::debug!("creating folder");
let mut db = db
.begin()
.await
.inspect_err(|error| tracing::error!(?error, "failed to being transaction"))?;
let folder_id = create.folder.id;
let folder = Folder::create(
db.deref_mut(),
CreateFolder {
name: create.name,
document_box: create.folder.document_box,
folder_id: Some(folder_id),
created_by: create.created_by,
},
)
.await
.inspect_err(|error| tracing::error!(?error, "failed to create folder"))?;
store_folder_index(search, &folder, folder_id).await?;
create_state.search_index_files.push(folder.id);
db.commit()
.await
.inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
events.publish_event(TenantEventMessage::FolderCreated(WithScope::new(
folder.clone(),
folder.document_box.clone(),
)));
Ok(folder)
}
async fn rollback_create_folder(search: TenantSearchIndex, create_state: CreateFolderState) {
for id in create_state.search_index_files {
if let Err(error) = search.delete_data(id).await {
tracing::error!(
?error, index_id = %id,
"failed to rollback created folder search index",
);
}
}
}