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::TenantSearchIndex;
15use std::ops::DerefMut;
16use thiserror::Error;
17use tracing::error;
18use uuid::Uuid;
19
20#[derive(Debug, Error)]
21pub enum CreateLinkError {
22 #[error(transparent)]
24 Database(#[from] DbErr),
25
26 #[error("failed to create link search index: {0}")]
28 CreateIndex(anyhow::Error),
29}
30
31#[derive(Default)]
34struct CreateLinkState {
35 pub search_index_files: Vec<Uuid>,
37}
38
39pub struct CreateLinkData {
40 pub folder: Folder,
42
43 pub name: String,
45
46 pub value: String,
48
49 pub created_by: Option<UserId>,
51}
52
53pub async fn safe_create_link(
56 db: &DbPool,
57 search: TenantSearchIndex,
58 events: &TenantEventPublisher,
59 create: CreateLinkData,
60) -> Result<Link, CreateLinkError> {
61 let mut create_state = CreateLinkState::default();
62 create_link(db, &search, events, create, &mut create_state)
63 .await
64 .inspect_err(|_| {
65 tokio::spawn(rollback_create_link(search, create_state));
67 })
68}
69
70async fn create_link(
71 db: &DbPool,
72 search: &TenantSearchIndex,
73 events: &TenantEventPublisher,
74 create: CreateLinkData,
75 create_state: &mut CreateLinkState,
76) -> Result<Link, CreateLinkError> {
77 tracing::debug!("creating link");
78
79 let mut db = db
80 .begin()
81 .await
82 .inspect_err(|error| tracing::error!(?error, "failed to being transaction"))?;
83
84 let link = Link::create(
86 db.deref_mut(),
87 DbCreateLink {
88 name: create.name,
89 value: create.value,
90 folder_id: create.folder.id,
91 created_by: create.created_by,
92 },
93 )
94 .await
95 .inspect_err(|error| tracing::error!(?error, "failed to create link"))?;
96
97 store_link_index(search, &link, &create.folder.document_box).await?;
99 create_state.search_index_files.push(link.id);
100
101 db.commit()
102 .await
103 .inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
104
105 events.publish_event(TenantEventMessage::LinkCreated(WithScope::new(
107 link.clone(),
108 create.folder.document_box,
109 )));
110
111 Ok(link)
112}
113
114async fn rollback_create_link(search: TenantSearchIndex, create_state: CreateLinkState) {
115 for id in create_state.search_index_files {
117 if let Err(error) = search.delete_data(id).await {
118 tracing::error!(
119 ?error, index_id = %id,
120 "failed to rollback created link search index"
121 );
122 }
123 }
124}