use docbox_database::{
DbErr, DbPool, DbResult, DbTransaction,
models::{
document_box::DocumentBoxScopeRaw,
edit_history::{
CreateEditHistory, CreateEditHistoryType, EditHistory, EditHistoryMetadata,
},
file::{File, FileId},
folder::{Folder, FolderId},
user::UserId,
},
};
use docbox_search::{SearchError, TenantSearchIndex, models::UpdateSearchIndexData};
use std::ops::DerefMut;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum UpdateFileError {
#[error(transparent)]
Database(#[from] DbErr),
#[error("unknown target folder")]
UnknownTargetFolder,
#[error(transparent)]
SearchIndex(SearchError),
}
pub struct UpdateFile {
pub folder_id: Option<FolderId>,
pub name: Option<String>,
pub pinned: Option<bool>,
}
pub async fn update_file(
db: &DbPool,
search: &TenantSearchIndex,
scope: &DocumentBoxScopeRaw,
file: File,
user_id: Option<String>,
update: UpdateFile,
) -> Result<(), UpdateFileError> {
let mut file = file;
let mut db = db
.begin()
.await
.inspect_err(|error| tracing::error!(?error, "failed to begin transaction"))?;
if let Some(target_id) = update.folder_id {
let target_folder = Folder::find_by_id(db.deref_mut(), scope, target_id)
.await
.inspect_err(|error| tracing::error!(?error, "failed to query target folder"))?
.ok_or(UpdateFileError::UnknownTargetFolder)?;
file = move_file(&mut db, user_id.clone(), file, target_folder)
.await
.inspect_err(|error| tracing::error!(?error, "failed to move file"))?;
};
if let Some(new_name) = update.name {
file = update_file_name(&mut db, user_id.clone(), file, new_name)
.await
.inspect_err(|error| tracing::error!(?error, "failed to update file name"))?;
}
if let Some(new_value) = update.pinned {
file = update_file_pinned(&mut db, user_id, file, new_value)
.await
.inspect_err(|error| tracing::error!(?error, "failed to update file pinned state"))?;
}
search
.update_data(
file.id,
UpdateSearchIndexData {
folder_id: file.folder_id,
name: file.name.clone(),
content: None,
pages: None,
},
)
.await
.map_err(|error| {
tracing::error!(?error, "failed to update search index");
UpdateFileError::SearchIndex(error)
})?;
db.commit().await.inspect_err(|error| {
tracing::error!(?error, "failed to commit transaction");
})?;
Ok(())
}
#[tracing::instrument(skip_all, fields(?user_id, %file_id, ?metadata))]
async fn add_edit_history(
db: &mut DbTransaction<'_>,
user_id: Option<UserId>,
file_id: FileId,
metadata: EditHistoryMetadata,
) -> DbResult<()> {
EditHistory::create(
db.deref_mut(),
CreateEditHistory {
ty: CreateEditHistoryType::File(file_id),
user_id,
metadata,
},
)
.await
.inspect_err(|error| tracing::error!(?error, "failed to store file edit history entry"))?;
Ok(())
}
#[tracing::instrument(skip_all, fields(?user_id, file_id = %file.id, %new_value))]
async fn update_file_pinned(
db: &mut DbTransaction<'_>,
user_id: Option<UserId>,
file: File,
new_value: bool,
) -> DbResult<File> {
add_edit_history(
db,
user_id,
file.id,
EditHistoryMetadata::ChangePinned {
previous_value: file.pinned,
new_value,
},
)
.await?;
file.set_pinned(db.deref_mut(), new_value)
.await
.inspect_err(|error| tracing::error!(?error, "failed to update file pinned state"))
}
#[tracing::instrument(skip_all, fields(?user_id, file_id = %file.id, %new_name))]
async fn update_file_name(
db: &mut DbTransaction<'_>,
user_id: Option<UserId>,
file: File,
new_name: String,
) -> DbResult<File> {
add_edit_history(
db,
user_id,
file.id,
EditHistoryMetadata::Rename {
original_name: file.name.clone(),
new_name: new_name.clone(),
},
)
.await?;
file.rename(db.deref_mut(), new_name.clone())
.await
.inspect_err(|error| tracing::error!(?error, "failed to rename file in database"))
}
#[tracing::instrument(skip_all, fields(user_id = ?user_id, file_id = %file.id, target_folder_id = %target_folder.id))]
async fn move_file(
db: &mut DbTransaction<'_>,
user_id: Option<UserId>,
file: File,
target_folder: Folder,
) -> DbResult<File> {
add_edit_history(
db,
user_id,
file.id,
EditHistoryMetadata::MoveToFolder {
original_id: file.folder_id,
target_id: target_folder.id,
},
)
.await?;
file.move_to_folder(db.deref_mut(), target_folder.id)
.await
.inspect_err(|error| tracing::error!(?error, "failed to move file in database"))
}