docbox_core/links/
create_link.rs1use crate::{
2 events::{TenantEventMessage, TenantEventPublisher},
3 links::index_link::store_link_index,
4};
5use docbox_database::{
6 DbErr, DbPool,
7 models::{
8 document_box::WithScope,
9 folder::Folder,
10 link::{CreateLink as DbCreateLink, Link},
11 user::UserId,
12 },
13};
14use docbox_search::{SearchError, TenantSearchIndex};
15use std::ops::DerefMut;
16use thiserror::Error;
17use uuid::Uuid;
18
19#[derive(Debug, Error)]
20pub enum CreateLinkError {
21 #[error(transparent)]
23 Database(#[from] DbErr),
24
25 #[error("failed to create link search index: {0}")]
27 CreateIndex(SearchError),
28}
29
30#[derive(Default)]
33struct CreateLinkState {
34 pub search_index_files: Vec<Uuid>,
36}
37
38pub struct CreateLinkData {
39 pub folder: Folder,
41
42 pub name: String,
44
45 pub value: String,
47
48 pub created_by: Option<UserId>,
50}
51
52pub async fn safe_create_link(
55 db: &DbPool,
56 search: TenantSearchIndex,
57 events: &TenantEventPublisher,
58 create: CreateLinkData,
59) -> Result<Link, CreateLinkError> {
60 let mut create_state = CreateLinkState::default();
61 create_link(db, &search, events, create, &mut create_state)
62 .await
63 .inspect_err(|_| {
64 tokio::spawn(rollback_create_link(search, create_state));
66 })
67}
68
69async fn create_link(
70 db: &DbPool,
71 search: &TenantSearchIndex,
72 events: &TenantEventPublisher,
73 create: CreateLinkData,
74 create_state: &mut CreateLinkState,
75) -> Result<Link, CreateLinkError> {
76 tracing::debug!("creating link");
77
78 let mut db = db
79 .begin()
80 .await
81 .inspect_err(|error| tracing::error!(?error, "failed to being transaction"))?;
82
83 let link = Link::create(
85 db.deref_mut(),
86 DbCreateLink {
87 name: create.name,
88 value: create.value,
89 folder_id: create.folder.id,
90 created_by: create.created_by,
91 },
92 )
93 .await
94 .inspect_err(|error| tracing::error!(?error, "failed to create link"))?;
95
96 store_link_index(search, &link, &create.folder.document_box).await?;
98 create_state.search_index_files.push(link.id);
99
100 db.commit()
101 .await
102 .inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
103
104 events.publish_event(TenantEventMessage::LinkCreated(WithScope::new(
106 link.clone(),
107 create.folder.document_box,
108 )));
109
110 Ok(link)
111}
112
113async fn rollback_create_link(search: TenantSearchIndex, create_state: CreateLinkState) {
114 for id in create_state.search_index_files {
116 if let Err(error) = search.delete_data(id).await {
117 tracing::error!(
118 ?error, index_id = %id,
119 "failed to rollback created link search index"
120 );
121 }
122 }
123}