docbox_core/links/
update_link.rs

1use docbox_database::{
2    DbErr, DbPool, DbResult, DbTransaction,
3    models::{
4        document_box::DocumentBoxScopeRaw,
5        edit_history::{
6            CreateEditHistory, CreateEditHistoryType, EditHistory, EditHistoryMetadata,
7        },
8        folder::{Folder, FolderId},
9        link::Link,
10        user::UserId,
11    },
12};
13use docbox_search::{TenantSearchIndex, models::UpdateSearchIndexData};
14use std::ops::DerefMut;
15use thiserror::Error;
16
17#[derive(Debug, Error)]
18pub enum UpdateLinkError {
19    /// Database related error
20    #[error(transparent)]
21    Database(#[from] DbErr),
22
23    /// Target folder could not be found
24    #[error("unknown target folder")]
25    UnknownTargetFolder,
26
27    /// Failed to update the search index
28    #[error(transparent)]
29    SearchIndex(anyhow::Error),
30}
31
32pub struct UpdateLink {
33    /// Move the link to another folder
34    pub folder_id: Option<FolderId>,
35
36    /// Update the link name
37    pub name: Option<String>,
38
39    /// Update the link value
40    pub value: Option<String>,
41}
42
43pub async fn update_link(
44    db: &DbPool,
45    search: &TenantSearchIndex,
46    scope: &DocumentBoxScopeRaw,
47    link: Link,
48    user_id: Option<String>,
49    update: UpdateLink,
50) -> Result<(), UpdateLinkError> {
51    let mut link = link;
52
53    let mut db = db
54        .begin()
55        .await
56        .inspect_err(|error| tracing::error!(?error, "failed to begin transaction"))?;
57
58    if let Some(target_id) = update.folder_id {
59        // Ensure the target folder exists, also ensures the target folder is in the same scope
60        // (We may allow across scopes in the future, but would need additional checks for access control of target scope)
61        let target_folder = Folder::find_by_id(db.deref_mut(), scope, target_id)
62            .await
63            .inspect_err(|error| tracing::error!(?error, "failed to query target folder"))?
64            .ok_or(UpdateLinkError::UnknownTargetFolder)?;
65
66        link = move_link(&mut db, user_id.clone(), link, target_folder)
67            .await
68            .inspect_err(|error| tracing::error!(?error, "failed to move link"))?;
69    };
70
71    if let Some(new_name) = update.name {
72        link = update_link_name(&mut db, user_id.clone(), link, new_name)
73            .await
74            .inspect_err(|error| tracing::error!(?error, "failed to update link name"))?;
75    }
76
77    if let Some(new_value) = update.value {
78        link = update_link_value(&mut db, user_id, link, new_value)
79            .await
80            .inspect_err(|error| tracing::error!(?error, "failed to update link value"))?;
81    }
82
83    // Update search index data for the new name and value
84    search
85        .update_data(
86            link.id,
87            UpdateSearchIndexData {
88                folder_id: link.folder_id,
89                name: link.name.clone(),
90                content: Some(link.value.clone()),
91                pages: None,
92            },
93        )
94        .await
95        .inspect_err(|error| tracing::error!(?error, "failed to update search index"))
96        .map_err(UpdateLinkError::SearchIndex)?;
97
98    db.commit()
99        .await
100        .inspect_err(|error| tracing::error!(?error, "failed to commit transaction"))?;
101
102    Ok(())
103}
104
105/// Moves a link to the provided folder, creates a new edit history
106/// item for the change
107#[tracing::instrument(skip_all, fields(?user_id, link_id = %link.id, target_folder_id = %target_folder.id))]
108async fn move_link(
109    db: &mut DbTransaction<'_>,
110    user_id: Option<UserId>,
111    link: Link,
112    target_folder: Folder,
113) -> DbResult<Link> {
114    // Track the edit history
115    EditHistory::create(
116        db.deref_mut(),
117        CreateEditHistory {
118            ty: CreateEditHistoryType::Link(link.id),
119            user_id: user_id.clone(),
120            metadata: EditHistoryMetadata::MoveToFolder {
121                original_id: link.folder_id,
122                target_id: target_folder.id,
123            },
124        },
125    )
126    .await
127    .inspect_err(|error| tracing::error!(?error, "failed to store link move edit history"))?;
128
129    link.move_to_folder(db.deref_mut(), target_folder.id)
130        .await
131        .inspect_err(|error| tracing::error!(?error, "failed to move link"))
132}
133
134/// Updates a link value, creates a new edit history
135/// item for the change
136#[tracing::instrument(skip_all, fields(?user_id, link_id = %link.id, %new_value))]
137async fn update_link_value(
138    db: &mut DbTransaction<'_>,
139    user_id: Option<UserId>,
140    link: Link,
141    new_value: String,
142) -> DbResult<Link> {
143    // Track the edit history
144    EditHistory::create(
145        db.deref_mut(),
146        CreateEditHistory {
147            ty: CreateEditHistoryType::Link(link.id),
148            user_id: user_id.clone(),
149            metadata: EditHistoryMetadata::LinkValue {
150                previous_value: link.value.clone(),
151                new_value: new_value.clone(),
152            },
153        },
154    )
155    .await
156    .inspect_err(|error| tracing::error!(?error, "failed to store link value edit history"))?;
157
158    link.update_value(db.deref_mut(), new_value)
159        .await
160        .inspect_err(|error| tracing::error!(?error, "failed to update link value"))
161}
162
163/// Updates a link name, creates a new edit history
164/// item for the change
165#[tracing::instrument(skip_all, fields(?user_id, link_id = %link.id, %new_name))]
166async fn update_link_name(
167    db: &mut DbTransaction<'_>,
168    user_id: Option<UserId>,
169    link: Link,
170    new_name: String,
171) -> DbResult<Link> {
172    // Track the edit history
173    EditHistory::create(
174        db.deref_mut(),
175        CreateEditHistory {
176            ty: CreateEditHistoryType::Link(link.id),
177            user_id: user_id.clone(),
178            metadata: EditHistoryMetadata::Rename {
179                original_name: link.name.clone(),
180                new_name: new_name.clone(),
181            },
182        },
183    )
184    .await
185    .inspect_err(|error| tracing::error!(?error, "failed to store link rename edit history"))?;
186
187    link.rename(db.deref_mut(), new_name)
188        .await
189        .inspect_err(|error| tracing::error!(?error, "failed to rename link"))
190}