perf: migrate all user/session/auth IDs from VARCHAR(36) to native UUID

- Schema: all ~15 VARCHAR(36) columns → UUID with DEFAULT gen_random_uuid()
- Domain entities: User, Session, DeviceCode, AppPassword, Share → id: Uuid
- DTOs: CurrentUser.id → Uuid (API boundary DTOs keep String for JSON)
- Auth middleware: parse JWT claims.sub (String) → Uuid at boundary
- All repository traits, port traits, service impls updated end-to-end
- Handlers: pass Uuid by value (Copy, 16 bytes) instead of String refs
- Settings chain: updated_by column → Uuid (was text, caused setup crash)
- Removed ~650 lines of String↔Uuid conversion boilerplate
- Eliminates per-request heap allocations for ID cloning
- 16-byte binary comparison vs 36-byte string comparison in all queries
- Native UUID indexing in PostgreSQL (btree on 16 bytes vs 36-char text)

85 files changed, 1090 insertions(+), 1739 deletions(-)
This commit is contained in:
Diocrafts
2026-03-07 14:59:32 +01:00
parent 9f08460027
commit 06ed0455ce
85 changed files with 1090 additions and 1739 deletions
+32 -58
View File
@@ -20,6 +20,7 @@ use crate::application::services::folder_service::FolderService;
use crate::application::services::trash_service::TrashService;
use crate::common::config::AppConfig;
use crate::common::errors::DomainError;
use uuid::Uuid;
/// Specific errors for batch operations
#[derive(Debug, Error)]
@@ -117,7 +118,7 @@ impl BatchOperationService {
&self,
file_ids: Vec<String>,
target_folder_id: Option<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FileDto>, BatchOperationError> {
info!("Starting batch copy of {} files", file_ids.len());
let start_time = std::time::Instant::now();
@@ -135,17 +136,15 @@ impl BatchOperationService {
// Arc<str> avoids N heap-clones of the same string
let target_folder: Option<Arc<str>> = target_folder_id.map(|s| Arc::from(s.as_str()));
let caller: Arc<str> = Arc::from(caller_id);
// buffer_unordered materialises only max_concurrent futures at a time
let mut operation_stream = stream::iter(file_ids.into_iter().map(|file_id| {
let mgmt = self.file_management.clone();
let target_folder = target_folder.clone();
let caller = caller.clone();
async move {
let copy_result = mgmt
.copy_file_owned(&file_id, &caller, target_folder.map(|s| s.to_string()))
.copy_file_owned(&file_id, user_id, target_folder.map(|s| s.to_string()))
.await;
(file_id, copy_result)
}
@@ -187,7 +186,7 @@ impl BatchOperationService {
&self,
file_ids: Vec<String>,
target_folder_id: Option<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FileDto>, BatchOperationError> {
info!("Starting batch move of {} files", file_ids.len());
let start_time = std::time::Instant::now();
@@ -204,16 +203,14 @@ impl BatchOperationService {
};
let target_folder: Option<Arc<str>> = target_folder_id.map(|s| Arc::from(s.as_str()));
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(file_ids.into_iter().map(|file_id| {
let mgmt = self.file_management.clone();
let target_folder = target_folder.clone();
let caller = caller.clone();
async move {
let move_result = mgmt
.move_file_owned(&file_id, &caller, target_folder.map(|s| s.to_string()))
.move_file_owned(&file_id, user_id, target_folder.map(|s| s.to_string()))
.await;
(file_id, move_result)
}
@@ -253,7 +250,7 @@ impl BatchOperationService {
pub async fn delete_files(
&self,
file_ids: Vec<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<String>, BatchOperationError> {
info!("Starting batch deletion of {} files", file_ids.len());
let start_time = std::time::Instant::now();
@@ -268,15 +265,11 @@ impl BatchOperationService {
},
};
// Define the operation to perform for each file
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(file_ids.into_iter().map(|file_id| {
let mgmt = self.file_management.clone();
let caller = caller.clone();
async move {
let delete_result = mgmt.delete_file_owned(&file_id, &caller).await;
let delete_result = mgmt.delete_file_owned(&file_id, user_id).await;
let id_for_result = file_id.clone();
(file_id, delete_result.map(|_| id_for_result))
}
@@ -317,7 +310,7 @@ impl BatchOperationService {
pub async fn get_multiple_files(
&self,
file_ids: Vec<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FileDto>, BatchOperationError> {
info!("Starting batch load of {} files", file_ids.len());
let start_time = std::time::Instant::now();
@@ -332,15 +325,11 @@ impl BatchOperationService {
},
};
// Define the operation to perform for each file
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(file_ids.into_iter().map(|file_id| {
let retrieval = self.file_retrieval.clone();
let caller = caller.clone();
async move {
let get_result = retrieval.get_file_owned(&file_id, &caller).await;
let get_result = retrieval.get_file_owned(&file_id, user_id).await;
(file_id, get_result)
}
}))
@@ -381,7 +370,7 @@ impl BatchOperationService {
&self,
folder_ids: Vec<String>,
_recursive: bool,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<String>, BatchOperationError> {
info!("Starting batch deletion of {} folders", folder_ids.len());
let start_time = std::time::Instant::now();
@@ -396,16 +385,11 @@ impl BatchOperationService {
},
};
// Define the operation to perform for each folder
// Arc<str> avoids N heap-clones of the caller string
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(folder_ids.into_iter().map(|folder_id| {
let folder_service = self.folder_service.clone();
let caller = caller.clone();
async move {
let delete_result = folder_service.delete_folder(&folder_id, &caller).await;
let delete_result = folder_service.delete_folder(&folder_id, user_id).await;
let id_for_result = folder_id.clone();
(folder_id, delete_result.map(|_| id_for_result))
}
@@ -445,7 +429,7 @@ impl BatchOperationService {
pub async fn trash_files(
&self,
file_ids: Vec<String>,
user_id: &str,
user_id: Uuid,
) -> Result<BatchResult<String>, BatchOperationError> {
let trash_service = self
.trash_service
@@ -464,14 +448,14 @@ impl BatchOperationService {
},
};
let uid: Arc<str> = Arc::from(user_id);
let uid = user_id;
let mut operation_stream = stream::iter(file_ids.into_iter().map(|file_id| {
let trash = trash_service.clone();
let uid = uid.clone();
let uid = uid;
async move {
let trash_result = trash.move_to_trash(&file_id, "file", &uid).await;
let trash_result = trash.move_to_trash(&file_id, "file", uid).await;
let id_for_result = file_id.clone();
(file_id, trash_result.map(|_| id_for_result))
}
@@ -510,7 +494,7 @@ impl BatchOperationService {
pub async fn trash_folders(
&self,
folder_ids: Vec<String>,
user_id: &str,
user_id: Uuid,
) -> Result<BatchResult<String>, BatchOperationError> {
let trash_service = self
.trash_service
@@ -529,14 +513,14 @@ impl BatchOperationService {
},
};
let uid: Arc<str> = Arc::from(user_id);
let uid = user_id;
let mut operation_stream = stream::iter(folder_ids.into_iter().map(|folder_id| {
let trash = trash_service.clone();
let uid = uid.clone();
let uid = uid;
async move {
let trash_result = trash.move_to_trash(&folder_id, "folder", &uid).await;
let trash_result = trash.move_to_trash(&folder_id, "folder", uid).await;
let id_for_result = folder_id.clone();
(folder_id, trash_result.map(|_| id_for_result))
}
@@ -576,7 +560,7 @@ impl BatchOperationService {
&self,
folder_ids: Vec<String>,
target_folder_id: Option<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FolderDto>, BatchOperationError> {
info!("Starting batch move of {} folders", folder_ids.len());
let start_time = std::time::Instant::now();
@@ -591,18 +575,16 @@ impl BatchOperationService {
};
let target: Option<Arc<str>> = target_folder_id.map(|s| Arc::from(s.as_str()));
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(folder_ids.into_iter().map(|folder_id| {
let folder_service = self.folder_service.clone();
let target = target.clone();
let caller = caller.clone();
async move {
let dto = MoveFolderDto {
parent_id: target.map(|s| s.to_string()),
};
let move_result = folder_service.move_folder(&folder_id, dto, &caller).await;
let move_result = folder_service.move_folder(&folder_id, dto, user_id).await;
(folder_id, move_result)
}
}))
@@ -645,7 +627,7 @@ impl BatchOperationService {
&self,
file_ids: Vec<String>,
folder_ids: Vec<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<NamedTempFile, BatchOperationError> {
info!(
"Starting batch download: {} files, {} folders",
@@ -665,10 +647,10 @@ impl BatchOperationService {
// ── Add individual files at the root of the ZIP ──────────────────
for file_id in &file_ids {
match self.file_retrieval.get_file_owned(file_id, caller_id).await {
match self.file_retrieval.get_file_owned(file_id, user_id).await {
Ok(file_dto) => {
if let Err(e) = self
.add_file_entry_streamed(&mut zip, file_id, &file_dto.name, caller_id)
.add_file_entry_streamed(&mut zip, file_id, &file_dto.name, user_id)
.await
{
info!("Could not add file {} to ZIP: {}", file_dto.name, e);
@@ -684,12 +666,12 @@ impl BatchOperationService {
for folder_id in &folder_ids {
match self
.folder_service
.get_folder_owned(folder_id, caller_id)
.get_folder_owned(folder_id, user_id)
.await
{
Ok(root_folder) => {
if let Err(e) = self
.add_folder_subtree_to_zip(&mut zip, folder_id, &root_folder, caller_id)
.add_folder_subtree_to_zip(&mut zip, folder_id, &root_folder, user_id)
.await
{
info!("Could not add folder {} to ZIP: {}", root_folder.name, e);
@@ -728,7 +710,7 @@ impl BatchOperationService {
zip: &mut ZipFileWriter<tokio_util::compat::Compat<BufWriter<tokio::fs::File>>>,
file_id: &str,
entry_name: &str,
caller_id: &str,
caller_id: Uuid,
) -> Result<(), BatchOperationError> {
let entry = ZipEntryBuilder::new(entry_name.to_string().into(), Compression::Deflate);
let mut writer = zip
@@ -769,7 +751,7 @@ impl BatchOperationService {
zip: &mut ZipFileWriter<tokio_util::compat::Compat<BufWriter<tokio::fs::File>>>,
folder_id: &str,
root_folder: &FolderDto,
caller_id: &str,
caller_id: Uuid,
) -> Result<(), BatchOperationError> {
// Bulk-fetch folder tree (small — one entry per folder)
let all_folders = self
@@ -908,7 +890,7 @@ impl BatchOperationService {
pub async fn create_folders(
&self,
folders: Vec<(String, Option<String>)>, // (name, parent_id)
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FolderDto>, BatchOperationError> {
info!("Starting batch creation of {} folders", folders.len());
let start_time = std::time::Instant::now();
@@ -923,17 +905,13 @@ impl BatchOperationService {
},
};
// Define the operation for each folder
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(folders.into_iter().map(|(name, parent_id)| {
let folder_service = self.folder_service.clone();
let caller = caller.clone();
async move {
// If a parent is specified, verify the caller owns it
if let Some(ref pid) = parent_id
&& let Err(e) = folder_service.get_folder_owned(pid, &caller).await
&& let Err(e) = folder_service.get_folder_owned(pid, user_id).await
{
let id = format!("{}:{}", name, pid);
return (id, Err(e));
@@ -983,7 +961,7 @@ impl BatchOperationService {
pub async fn get_multiple_folders(
&self,
folder_ids: Vec<String>,
caller_id: &str,
user_id: Uuid,
) -> Result<BatchResult<FolderDto>, BatchOperationError> {
info!("Starting batch load of {} folders", folder_ids.len());
let start_time = std::time::Instant::now();
@@ -998,15 +976,11 @@ impl BatchOperationService {
},
};
// Define the operation for each folder
let caller: Arc<str> = Arc::from(caller_id);
let mut operation_stream = stream::iter(folder_ids.into_iter().map(|folder_id| {
let folder_service = self.folder_service.clone();
let caller = caller.clone();
async move {
let get_result = folder_service.get_folder_owned(&folder_id, &caller).await;
let get_result = folder_service.get_folder_owned(&folder_id, user_id).await;
(folder_id, get_result)
}
}))