From 5b420a6b62f729b9d0947882b7e668ebfe82934b Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Wed, 15 Oct 2025 22:00:14 +0200 Subject: [PATCH] update filename on title change --- backend/src/routes/documents.rs | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/documents.rs b/backend/src/routes/documents.rs index 0f5f594..f93e4d6 100644 --- a/backend/src/routes/documents.rs +++ b/backend/src/routes/documents.rs @@ -9,7 +9,7 @@ use axum::http::StatusCode; use axum::response::IntoResponse; use chrono::{DateTime, NaiveDateTime, Utc}; use diesel::dsl::exists; -use diesel::{prelude::*, select, PgConnection}; +use diesel::{prelude::*, result::DatabaseErrorKind, select, PgConnection}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use sha2::{Digest, Sha256}; @@ -726,9 +726,24 @@ pub async fn update_document( if let Some(title) = new_title { let now = Utc::now().naive_utc(); - diesel::update(documents::table.find(document_id)) - .set((documents::title.eq(title), documents::updated_at.eq(now))) - .execute(&mut conn)?; + let new_filename = filename_with_retained_extension(&title, &document.filename); + + let update_result = diesel::update(documents::table.find(document_id)).set(( + documents::title.eq(&title), + documents::filename.eq(&new_filename), + documents::updated_at.eq(now), + )); + + match update_result.execute(&mut conn) { + Ok(_) => {} + Err(diesel::result::Error::DatabaseError(DatabaseErrorKind::UniqueViolation, _)) => { + return Err(AppError::bad_request( + "another document in this folder already uses that filename", + )); + } + Err(err) => return Err(AppError::from(err)), + } + document = documents::table.find(document_id).first(&mut conn)?; } @@ -1515,6 +1530,26 @@ fn derive_document_title(original: &str) -> String { stem.unwrap_or_else(|| trimmed.to_string()) } +fn filename_with_retained_extension(title: &str, current_filename: &str) -> String { + let extension = FsPath::new(current_filename) + .extension() + .and_then(|ext| ext.to_str()); + + if let Some(ext) = extension { + if title + .rsplit_once('.') + .map(|(_, existing_ext)| existing_ext.eq_ignore_ascii_case(ext)) + .unwrap_or(false) + { + title.to_string() + } else { + format!("{title}.{ext}") + } + } else { + title.to_string() + } +} + async fn load_asset_responses( state: &AppState, version_id: Uuid,