docbox_core/files/
update_file.rs1use docbox_database::{
2 DbErr, DbPool, DbResult, DbTransaction,
3 models::{
4 document_box::DocumentBoxScopeRaw,
5 edit_history::{
6 CreateEditHistory, CreateEditHistoryType, EditHistory, EditHistoryMetadata,
7 },
8 file::File,
9 folder::{Folder, FolderId},
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 UpdateFileError {
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 UpdateFile {
33 pub folder_id: Option<FolderId>,
35
36 pub name: Option<String>,
38}
39
40pub async fn update_file(
41 db: &DbPool,
42 search: &TenantSearchIndex,
43 scope: &DocumentBoxScopeRaw,
44 file: File,
45 user_id: Option<String>,
46 update: UpdateFile,
47) -> Result<(), UpdateFileError> {
48 let mut file = file;
49
50 let mut db = db
51 .begin()
52 .await
53 .inspect_err(|cause| tracing::error!(?cause, "failed to begin transaction"))?;
54
55 if let Some(target_id) = update.folder_id {
56 let target_folder = Folder::find_by_id(db.deref_mut(), scope, target_id)
59 .await
60 .inspect_err(|cause| tracing::error!(?cause, "failed to query target folder"))?
61 .ok_or(UpdateFileError::UnknownTargetFolder)?;
62
63 file = move_file(&mut db, user_id.clone(), file, target_folder)
64 .await
65 .inspect_err(|cause| tracing::error!(?cause, "failed to move file"))?;
66 };
67
68 if let Some(new_name) = update.name {
69 file = update_file_name(&mut db, user_id, file, new_name)
70 .await
71 .inspect_err(|cause| tracing::error!(?cause, "failed to update file name"))?;
72 }
73
74 search
76 .update_data(
77 file.id,
78 UpdateSearchIndexData {
79 folder_id: file.folder_id,
80 name: file.name.clone(),
81 content: None,
83 pages: None,
84 },
85 )
86 .await
87 .map_err(|cause| {
88 tracing::error!(?cause, "failed to update search index");
89 UpdateFileError::SearchIndex(cause)
90 })?;
91
92 db.commit().await.inspect_err(|cause| {
93 tracing::error!(?cause, "failed to commit transaction");
94 })?;
95
96 Ok(())
97}
98
99#[tracing::instrument(skip_all, fields(user_id = ?user_id, file_id = %file.id, new_name = %new_name))]
100async fn update_file_name(
101 db: &mut DbTransaction<'_>,
102 user_id: Option<UserId>,
103 file: File,
104 new_name: String,
105) -> DbResult<File> {
106 EditHistory::create(
108 db.deref_mut(),
109 CreateEditHistory {
110 ty: CreateEditHistoryType::File(file.id),
111 user_id: user_id.clone(),
112 metadata: EditHistoryMetadata::Rename {
113 original_name: file.name.clone(),
114 new_name: new_name.clone(),
115 },
116 },
117 )
118 .await
119 .inspect_err(|error| tracing::error!(?error, "failed to store file rename edit history"))?;
120
121 file.rename(db.deref_mut(), new_name.clone())
122 .await
123 .inspect_err(|error| tracing::error!(?error, "failed to rename file in database"))
124}
125
126#[tracing::instrument(skip_all, fields(user_id = ?user_id, file_id = %file.id, target_folder_id = %target_folder.id))]
127async fn move_file(
128 db: &mut DbTransaction<'_>,
129 user_id: Option<UserId>,
130 file: File,
131 target_folder: Folder,
132) -> DbResult<File> {
133 EditHistory::create(
135 db.deref_mut(),
136 CreateEditHistory {
137 ty: CreateEditHistoryType::File(file.id),
138 user_id: user_id.clone(),
139 metadata: EditHistoryMetadata::MoveToFolder {
140 original_id: file.folder_id,
141 target_id: target_folder.id,
142 },
143 },
144 )
145 .await
146 .inspect_err(|error| tracing::error!(?error, "failed to store file move edit history"))?;
147
148 file.move_to_folder(db.deref_mut(), target_folder.id)
149 .await
150 .inspect_err(|error| tracing::error!(?error, "failed to move file in database"))
151}