Skip to main content

docbox_core/links/
create_link.rs

1use 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    /// Database error occurred
22    #[error(transparent)]
23    Database(#[from] DbErr),
24
25    /// Failed to create the search index
26    #[error("failed to create link search index: {0}")]
27    CreateIndex(SearchError),
28}
29
30/// State structure to keep track of resources created
31/// as a side effect from a link
32#[derive(Default)]
33struct CreateLinkState {
34    /// Search index files
35    pub search_index_files: Vec<Uuid>,
36}
37
38pub struct CreateLinkData {
39    /// Folder to upload the link into
40    pub folder: Folder,
41
42    /// Link name
43    pub name: String,
44
45    /// Link value
46    pub value: String,
47
48    /// User creating the link
49    pub created_by: Option<UserId>,
50}
51
52/// Safely perform [create_link] ensuring that if an error
53/// occurs the changes will be properly rolled back
54pub 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            // Attempt to rollback any allocated resources in the background
65            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    // Create link
84    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    // Add link to search index
97    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    // Publish an event
105    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    // Revert file index data
115    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}