use crate::{
events::{TenantEventMessage, TenantEventPublisher},
links::index_link::store_link_index,
};
use docbox_database::{
DbErr, DbPool,
models::{
document_box::WithScope,
folder::Folder,
link::{CreateLink as DbCreateLink, Link},
user::UserId,
},
};
use docbox_search::{SearchError, TenantSearchIndex};
use std::ops::DerefMut;
use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Error)]
pub enum CreateLinkError {
#[error(transparent)]
Database(#[from] DbErr),
#[error("failed to create link search index: {0}")]
CreateIndex(SearchError),
}
#[derive(Default)]
struct CreateLinkState {
pub search_index_files: Vec<Uuid>,
}
pub struct CreateLinkData {
pub folder: Folder,
pub name: String,
pub value: String,
pub created_by: Option<UserId>,
}
pub async fn safe_create_link(
db: &DbPool,
search: TenantSearchIndex,
events: &TenantEventPublisher,
create: CreateLinkData,
) -> Result<Link, CreateLinkError> {
let mut create_state = CreateLinkState::default();
create_link(db, &search, events, create, &mut create_state)
.await
.inspect_err(|_| {
tokio::spawn(rollback_create_link(search, create_state));
})
}
async fn create_link(
db: &DbPool,
search: &TenantSearchIndex,
events: &TenantEventPublisher,
create: CreateLinkData,
create_state: &mut CreateLinkState,
) -> Result<Link, CreateLinkError> {
tracing::debug!("creating link");
let mut db = db
.begin()
.await
.inspect_err(|error| tracing::error!(?error, "failed to being transaction"))?;
let link = Link::create(
db.deref_mut(),
DbCreateLink {
name: create.name,
value: create.value,
folder_id: create.folder.id,
created_by: create.created_by,
},
)
.await
.inspect_err(|error| tracing::error!(?error, "failed to create link"))?;
store_link_index(search, &link, &create.folder.document_box).await?;
create_state.search_index_files.push(link.id);
db.commit()
.await
.inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
events.publish_event(TenantEventMessage::LinkCreated(WithScope::new(
link.clone(),
create.folder.document_box,
)));
Ok(link)
}
async fn rollback_create_link(search: TenantSearchIndex, create_state: CreateLinkState) {
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 link search index"
);
}
}
}