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 #[error(transparent)]
21 Database(#[from] DbErr),
22
23 #[error("unknown target folder")]
25 UnknownTargetFolder,
26
27 #[error(transparent)]
29 SearchIndex(anyhow::Error),
30}
31
32pub struct UpdateLink {
33 pub folder_id: Option<FolderId>,
35
36 pub name: Option<String>,
38
39 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 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 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#[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 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#[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 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#[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 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}