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