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 user::UserId,
10 },
11};
12use docbox_search::{TenantSearchIndex, models::UpdateSearchIndexData};
13use std::ops::DerefMut;
14use thiserror::Error;
15
16#[derive(Debug, Error)]
17pub enum UpdateFolderError {
18 #[error(transparent)]
20 Database(#[from] DbErr),
21
22 #[error("unknown target folder")]
24 UnknownTargetFolder,
25
26 #[error("cannot modify root")]
28 CannotModifyRoot,
29
30 #[error("cannot move into self")]
32 CannotMoveIntoSelf,
33
34 #[error(transparent)]
36 SearchIndex(anyhow::Error),
37}
38
39pub struct UpdateFolder {
40 pub folder_id: Option<FolderId>,
42
43 pub name: Option<String>,
45}
46
47pub async fn update_folder(
48 db: &DbPool,
49 search: &TenantSearchIndex,
50 scope: &DocumentBoxScopeRaw,
51 folder: Folder,
52 user_id: Option<String>,
53 update: UpdateFolder,
54) -> Result<(), UpdateFolderError> {
55 let mut folder = folder;
56
57 let mut folder_id = folder
58 .folder_id
59 .ok_or(UpdateFolderError::CannotModifyRoot)?;
61
62 let mut db = db
63 .begin()
64 .await
65 .inspect_err(|cause| tracing::error!(?cause, "failed to begin transaction"))?;
66
67 if let Some(target_id) = update.folder_id {
68 if target_id == folder.id {
70 return Err(UpdateFolderError::CannotMoveIntoSelf);
71 }
72
73 let target_folder = Folder::find_by_id(db.deref_mut(), scope, target_id)
76 .await
77 .inspect_err(|cause| tracing::error!(?cause, "failed to query target folder"))?
78 .ok_or(UpdateFolderError::UnknownTargetFolder)?;
79
80 folder_id = target_folder.id;
81
82 folder = move_folder(&mut db, user_id.clone(), folder, folder_id, target_folder)
83 .await
84 .inspect_err(|cause| tracing::error!(?cause, "failed to move folder"))?;
85 };
86
87 if let Some(new_name) = update.name {
88 folder = update_folder_name(&mut db, user_id, folder, new_name)
89 .await
90 .inspect_err(|cause| tracing::error!(?cause, "failed to update folder name"))?;
91 }
92
93 search
95 .update_data(
96 folder.id,
97 UpdateSearchIndexData {
98 folder_id,
99 name: folder.name.clone(),
100 content: None,
101 pages: None,
102 },
103 )
104 .await
105 .map_err(|cause| {
106 tracing::error!(?cause, "failed to update search index");
107 UpdateFolderError::SearchIndex(cause)
108 })?;
109
110 db.commit().await.inspect_err(|cause| {
111 tracing::error!(?cause, "failed to commit transaction");
112 })?;
113
114 Ok(())
115}
116
117#[tracing::instrument(skip_all, fields(?user_id, folder_id = %folder.id, target_folder_id = %target_folder.id))]
118async fn move_folder(
119 db: &mut DbTransaction<'_>,
120 user_id: Option<UserId>,
121 folder: Folder,
122 folder_id: FolderId,
123 target_folder: Folder,
124) -> DbResult<Folder> {
125 EditHistory::create(
127 db.deref_mut(),
128 CreateEditHistory {
129 ty: CreateEditHistoryType::Folder(folder.id),
130 user_id: user_id.clone(),
131 metadata: EditHistoryMetadata::MoveToFolder {
132 original_id: folder_id,
133 target_id: target_folder.id,
134 },
135 },
136 )
137 .await
138 .inspect_err(|error| tracing::error!(?error, "failed to store folder move edit history"))?;
139
140 folder
142 .move_to_folder(db.deref_mut(), target_folder.id)
143 .await
144 .inspect_err(|error| tracing::error!(?error, "failed to move folder"))
145}
146
147#[tracing::instrument(skip_all, fields(?user_id, folder_id = %folder.id, %new_name))]
148async fn update_folder_name(
149 db: &mut DbTransaction<'_>,
150 user_id: Option<UserId>,
151 folder: Folder,
152 new_name: String,
153) -> DbResult<Folder> {
154 EditHistory::create(
156 db.deref_mut(),
157 CreateEditHistory {
158 ty: CreateEditHistoryType::Folder(folder.id),
159 user_id: user_id.clone(),
160 metadata: EditHistoryMetadata::Rename {
161 original_name: folder.name.clone(),
162 new_name: new_name.clone(),
163 },
164 },
165 )
166 .await
167 .inspect_err(|error| tracing::error!(?error, "failed to store folder rename edit history"))?;
168
169 folder
171 .rename(db.deref_mut(), new_name)
172 .await
173 .inspect_err(|error| tracing::error!(?error, "failed to rename folder in database"))
174}