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
+23 -10
View File
@@ -15,6 +15,7 @@ use crate::application::ports::auth_ports::TokenServicePort;
use crate::common::di::AppState;
use crate::interfaces::errors::AppError;
use std::sync::Arc;
use uuid::Uuid;
/// Admin API routes — all require admin role.
pub fn admin_routes() -> Router<Arc<AppState>> {
@@ -41,7 +42,7 @@ pub fn admin_routes() -> Router<Arc<AppState>> {
}
/// Validate JWT and require admin role. Returns (user_id, role).
async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, String), AppError> {
async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(Uuid, String), AppError> {
let auth = state
.auth_service
.as_ref()
@@ -72,7 +73,7 @@ async fn admin_guard(state: &AppState, headers: &HeaderMap) -> Result<(String, S
));
}
Ok((claims.sub, claims.role))
Ok((Uuid::parse_str(&claims.sub).map_err(|_| AppError::internal_error("Invalid user ID in token"))?, claims.role))
}
/// GET /api/admin/settings/oidc — get OIDC settings for the admin panel
@@ -108,7 +109,7 @@ async fn save_oidc_settings(
.as_ref()
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
svc.save_oidc_settings(dto, &user_id)
svc.save_oidc_settings(dto, user_id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to save settings: {}", e)))?;
@@ -292,6 +293,8 @@ async fn get_user(
) -> Result<impl IntoResponse, AppError> {
admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
let auth = state
.auth_service
.as_ref()
@@ -299,7 +302,7 @@ async fn get_user(
let user = auth
.auth_application_service
.get_user_admin(&id)
.get_user_admin(id)
.await
.map_err(|e| AppError::not_found(format!("User not found: {}", e)))?;
@@ -314,6 +317,8 @@ async fn delete_user(
) -> Result<impl IntoResponse, AppError> {
let (admin_id, _) = admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
// Prevent self-deletion
if admin_id == id {
return Err(AppError::new(
@@ -329,7 +334,7 @@ async fn delete_user(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service
.delete_user_admin(&id)
.delete_user_admin(id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete user: {}", e)))?;
@@ -350,6 +355,8 @@ async fn update_user_role(
) -> Result<impl IntoResponse, AppError> {
let (admin_id, _) = admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
// Prevent changing own role
if admin_id == id {
return Err(AppError::new(
@@ -365,7 +372,7 @@ async fn update_user_role(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service
.change_user_role(&id, &dto.role)
.change_user_role(id, &dto.role)
.await
.map_err(|e| AppError::internal_error(format!("Failed to change role: {}", e)))?;
@@ -386,6 +393,8 @@ async fn update_user_active(
) -> Result<impl IntoResponse, AppError> {
let (admin_id, _) = admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
// Prevent deactivating yourself
if admin_id == id && !dto.active {
return Err(AppError::new(
@@ -401,7 +410,7 @@ async fn update_user_active(
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service
.set_user_active(&id, dto.active)
.set_user_active(id, dto.active)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update user status: {}", e)))?;
@@ -427,13 +436,15 @@ async fn update_user_quota(
) -> Result<impl IntoResponse, AppError> {
admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
let auth = state
.auth_service
.as_ref()
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service
.update_user_quota(&id, dto.quota_bytes)
.update_user_quota(id, dto.quota_bytes)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update quota: {}", e)))?;
@@ -487,13 +498,15 @@ async fn reset_user_password(
) -> Result<impl IntoResponse, AppError> {
admin_guard(&state, &headers).await?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
let auth = state
.auth_service
.as_ref()
.ok_or_else(|| AppError::internal_error("Auth service not configured"))?;
auth.auth_application_service
.admin_reset_password(&id, &dto.new_password)
.admin_reset_password(id, &dto.new_password)
.await
.map_err(|e| {
AppError::new(
@@ -558,7 +571,7 @@ async fn set_registration_setting(
.as_ref()
.ok_or_else(|| AppError::internal_error("Admin settings service not available"))?;
svc.set_registration_enabled(enabled, &admin_id)
svc.set_registration_enabled(enabled, admin_id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to save setting: {}", e)))?;
@@ -11,6 +11,7 @@ use axum::extract::State;
use axum::routing::{delete, get, post};
use axum::{Json, Router};
use std::sync::Arc;
use uuid::Uuid;
/// Protected routes — require JWT auth middleware.
pub fn app_password_routes() -> Router<Arc<AppState>> {
@@ -35,7 +36,7 @@ async fn create_app_password(
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
let response = service
.create(&user.id, request)
.create(user.id, request)
.await
.map_err(AppError::from)?;
@@ -55,7 +56,7 @@ async fn list_app_passwords(
.as_ref()
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
let response = service.list(&user.id).await.map_err(AppError::from)?;
let response = service.list(user.id).await.map_err(AppError::from)?;
Ok(Json(response))
}
@@ -72,8 +73,10 @@ async fn revoke_app_password(
.as_ref()
.ok_or_else(|| AppError::internal_error("App password service not configured"))?;
let id = Uuid::parse_str(&id).map_err(|_| AppError::bad_request("Invalid UUID"))?;
let response = service
.revoke(&user.id, &id)
.revoke(user.id, id)
.await
.map_err(AppError::from)?;
+10 -8
View File
@@ -6,6 +6,7 @@ use axum::{
routing::{get, post, put},
};
use std::sync::Arc;
use uuid::Uuid;
use crate::application::dtos::user_dto::{
ChangePasswordDto, LoginDto, OidcCallbackQueryDto, OidcExchangeDto, OidcProviderInfoDto,
@@ -273,7 +274,7 @@ async fn get_current_user(
if let Some(storage_usage_service) = state.storage_usage_service.as_ref() {
// Calculate storage synchronously (we await the result)
match storage_usage_service
.update_user_storage_usage(&user_id)
.update_user_storage_usage(user_id)
.await
{
Ok(usage) => {
@@ -293,7 +294,7 @@ async fn get_current_user(
// Now get the user data WITH the updated storage
let user = auth_service
.auth_application_service
.get_user_by_id(&user_id)
.get_user_by_id(user_id)
.await?;
Ok((StatusCode::OK, Json(user)))
@@ -311,7 +312,7 @@ async fn change_password(
auth_service
.auth_application_service
.change_password(&user_id, dto)
.change_password(user_id, dto)
.await?;
Ok(StatusCode::OK)
@@ -341,7 +342,7 @@ async fn logout(
auth_service
.auth_application_service
.logout(&user_id, &refresh_token)
.logout(user_id, &refresh_token)
.await?;
// Clear HttpOnly + CSRF cookies so the browser forgets the session
@@ -393,10 +394,10 @@ async fn setup_admin(
}
// 4. ATOMIC: claim initialization — only one concurrent request can win.
// We use a placeholder user_id ("pending") because the admin user
// We use Uuid::nil() as a placeholder because the admin user
// doesn't exist yet. It will be updated to the real id below.
let claimed = admin_svc
.try_claim_initialization("pending")
.try_claim_initialization(Uuid::nil())
.await
.map_err(|e| {
tracing::error!("Failed to claim system initialization: {}", e);
@@ -426,7 +427,8 @@ async fn setup_admin(
})?;
// 5. Update the initialization record with the real admin user_id
if let Err(e) = admin_svc.mark_system_initialized(&user.id).await {
let real_user_id = Uuid::parse_str(&user.id).unwrap_or_default();
if let Err(e) = admin_svc.mark_system_initialized(real_user_id).await {
// Not fatal — the claim already prevents concurrent re-initialization,
// and the "pending" marker is still "true" so the system stays locked.
tracing::error!(
@@ -603,7 +605,7 @@ async fn oidc_callback(
let (_id, app_password) = nextcloud
.app_passwords
.create_nc(&user_id, "Nextcloud (OIDC)")
.create_nc(user_id, "Nextcloud (OIDC)")
.await
.map_err(|e| {
tracing::error!(error = %e, user = %username, "OIDC+NC: failed to create app password");
+11 -11
View File
@@ -160,7 +160,7 @@ pub async fn move_files_batch(
// Execute batch operation
let result = state
.batch_service
.move_files(request.file_ids, request.target_folder_id, &auth_user.id)
.move_files(request.file_ids, request.target_folder_id, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch move_files failed: {}", e);
@@ -216,7 +216,7 @@ pub async fn copy_files_batch(
// Execute batch operation
let result = state
.batch_service
.copy_files(request.file_ids, request.target_folder_id, &auth_user.id)
.copy_files(request.file_ids, request.target_folder_id, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch copy_files failed: {}", e);
@@ -272,7 +272,7 @@ pub async fn delete_files_batch(
// Execute batch operation
let result = state
.batch_service
.delete_files(request.file_ids, &auth_user.id)
.delete_files(request.file_ids, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch delete_files failed: {}", e);
@@ -336,7 +336,7 @@ pub async fn delete_folders_batch(
// Execute batch operation
let result = state
.batch_service
.delete_folders(request.folder_ids, request.recursive, &auth_user.id)
.delete_folders(request.folder_ids, request.recursive, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch delete_folders failed: {}", e);
@@ -407,7 +407,7 @@ pub async fn create_folders_batch(
// Execute batch operation
let result = state
.batch_service
.create_folders(folders, &auth_user.id)
.create_folders(folders, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch create_folders failed: {}", e);
@@ -463,7 +463,7 @@ pub async fn get_files_batch(
// Execute batch operation
let result = state
.batch_service
.get_multiple_files(request.file_ids, &auth_user.id)
.get_multiple_files(request.file_ids, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch get_files failed: {}", e);
@@ -519,7 +519,7 @@ pub async fn get_folders_batch(
// Execute batch operation
let result = state
.batch_service
.get_multiple_folders(request.folder_ids, &auth_user.id)
.get_multiple_folders(request.folder_ids, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch get_folders failed: {}", e);
@@ -603,7 +603,7 @@ pub async fn trash_batch(
if !request.file_ids.is_empty() {
match state
.batch_service
.trash_files(request.file_ids, &auth_user.id)
.trash_files(request.file_ids, auth_user.id)
.await
{
Ok(result) => {
@@ -630,7 +630,7 @@ pub async fn trash_batch(
if !request.folder_ids.is_empty() {
match state
.batch_service
.trash_folders(request.folder_ids, &auth_user.id)
.trash_folders(request.folder_ids, auth_user.id)
.await
{
Ok(result) => {
@@ -707,7 +707,7 @@ pub async fn move_folders_batch(
let result = state
.batch_service
.move_folders(request.folder_ids, request.target_folder_id, &auth_user.id)
.move_folders(request.folder_ids, request.target_folder_id, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch move_folders failed: {}", e);
@@ -760,7 +760,7 @@ pub async fn download_batch(
let temp_file = state
.batch_service
.download_zip(request.file_ids, request.folder_ids, &auth_user.id)
.download_zip(request.file_ids, request.folder_ids, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Batch download ZIP failed: {}", e);
+24 -24
View File
@@ -209,7 +209,7 @@ async fn handle_propfind(
vec![]
} else {
calendar_service
.list_my_calendars(&user.id)
.list_my_calendars(user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list calendars: {}", e)))?
};
@@ -264,13 +264,13 @@ async fn handle_propfind(
if parts.len() == 1 {
// Single path segment: try as calendar ID first, fall back to user home
let calendar_result = calendar_service.get_calendar(first_segment, &user.id).await;
let calendar_result = calendar_service.get_calendar(first_segment, user.id).await;
if let Ok(calendar) = calendar_result {
// Valid calendar ID — return calendar collection
let events = if depth != "0" {
calendar_service
.list_events(first_segment, None, None, &user.id)
.list_events(first_segment, None, None, user.id)
.await
.unwrap_or_default()
} else {
@@ -300,7 +300,7 @@ async fn handle_propfind(
// List all calendars for this user
let calendars =
calendar_service
.list_my_calendars(&user.id)
.list_my_calendars(user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to list calendars: {}", e))
@@ -328,7 +328,7 @@ async fn handle_propfind(
let rest = parts[1];
// Check if first_segment is a valid calendar ID
let calendar_result = calendar_service.get_calendar(first_segment, &user.id).await;
let calendar_result = calendar_service.get_calendar(first_segment, user.id).await;
let (calendar_id, event_path) = if calendar_result.is_ok() {
// first_segment is a calendar ID, rest is event path
@@ -341,13 +341,13 @@ async fn handle_propfind(
// /caldav/{username}/{calendar_id}
// Try to get this as a calendar collection
let cal = calendar_service
.get_calendar(sub_parts[0], &user.id)
.get_calendar(sub_parts[0], user.id)
.await
.map_err(|e| AppError::not_found(format!("Calendar not found: {}", e)))?;
let events = if depth != "0" {
calendar_service
.list_events(sub_parts[0], None, None, &user.id)
.list_events(sub_parts[0], None, None, user.id)
.await
.unwrap_or_default()
} else {
@@ -384,7 +384,7 @@ async fn handle_propfind(
let ical_uid = event_path.trim_end_matches(".ics");
let events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
@@ -444,14 +444,14 @@ async fn handle_report(
CalDavReportType::CalendarQuery { time_range, .. } => {
if let Some((start, end)) = time_range {
calendar_service
.get_events_in_range(calendar_id, *start, *end, &user.id)
.get_events_in_range(calendar_id, *start, *end, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to query events: {}", e))
})?
} else {
calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to list events: {}", e))
@@ -460,7 +460,7 @@ async fn handle_report(
}
CalDavReportType::CalendarMultiget { hrefs, .. } => {
let all_events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
@@ -470,7 +470,7 @@ async fn handle_report(
.collect()
}
CalDavReportType::SyncCollection { .. } => calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?,
};
@@ -526,7 +526,7 @@ async fn handle_mkcalendar(
};
calendar_service
.create_calendar(create_dto, &user.id)
.create_calendar(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create calendar: {}", e)))?;
@@ -566,7 +566,7 @@ async fn handle_put(
let existing = if let Some(ref uid) = ical_uid {
let events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.unwrap_or_default();
events.into_iter().find(|e| e.ical_uid == *uid)
@@ -577,7 +577,7 @@ async fn handle_put(
if let Some(existing_event) = existing {
// Update existing event — re-create from iCal for full fidelity
calendar_service
.delete_event(&existing_event.id, &user.id)
.delete_event(&existing_event.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update event: {}", e)))?;
@@ -586,7 +586,7 @@ async fn handle_put(
ical_data,
};
let event = calendar_service
.create_event_from_ical(create_dto, &user.id)
.create_event_from_ical(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to recreate event: {}", e)))?;
@@ -602,7 +602,7 @@ async fn handle_put(
};
let event = calendar_service
.create_event_from_ical(create_dto, &user.id)
.create_event_from_ical(create_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to create event: {}", e)))?;
@@ -641,12 +641,12 @@ async fn handle_get(
if parts.len() < 2 {
// GET on calendar collection
let events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
let calendar = calendar_service
.get_calendar(calendar_id, &user.id)
.get_calendar(calendar_id, user.id)
.await
.map_err(|e| AppError::not_found(format!("Calendar not found: {}", e)))?;
@@ -664,7 +664,7 @@ async fn handle_get(
let ical_uid = event_file.trim_end_matches(".ics");
let events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
@@ -760,7 +760,7 @@ async fn handle_delete(
if parts.len() < 2 {
calendar_service
.delete_calendar(calendar_id, &user.id)
.delete_calendar(calendar_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete calendar: {}", e)))?;
} else {
@@ -768,7 +768,7 @@ async fn handle_delete(
let ical_uid = event_file.trim_end_matches(".ics");
let events = calendar_service
.list_events(calendar_id, None, None, &user.id)
.list_events(calendar_id, None, None, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list events: {}", e)))?;
@@ -778,7 +778,7 @@ async fn handle_delete(
.ok_or_else(|| AppError::not_found(format!("Event not found: {}", ical_uid)))?;
calendar_service
.delete_event(&event.id, &user.id)
.delete_event(&event.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete event: {}", e)))?;
}
@@ -833,7 +833,7 @@ async fn handle_proppatch(
if update.name.is_some() || update.description.is_some() || update.color.is_some() {
calendar_service
.update_calendar(calendar_id, update, &user.id)
.update_calendar(calendar_id, update, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update calendar: {}", e)))?;
}
+18 -18
View File
@@ -203,7 +203,7 @@ async fn handle_propfind(
if path.is_empty() {
// Root CardDAV path — list user's address books
let address_books = addressbook_service
.list_user_address_books(&user.id)
.list_user_address_books(user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to list address books: {}", e))
@@ -231,13 +231,13 @@ async fn handle_propfind(
if parts.len() == 1 {
// Address book collection
let address_book = addressbook_service
.get_address_book(address_book_id, &user.id)
.get_address_book(address_book_id, user.id)
.await
.map_err(|e| AppError::not_found(format!("Address book not found: {}", e)))?;
let contacts = if depth != "0" {
contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.unwrap_or_default()
} else {
@@ -269,7 +269,7 @@ async fn handle_propfind(
// Look up by UID across all contacts in this address book
let contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
@@ -331,12 +331,12 @@ async fn handle_report(
let contacts = match &report {
CardDavReportType::AddressbookQuery { .. } => contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?,
CardDavReportType::AddressbookMultiget { hrefs, .. } => {
let all_contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
@@ -346,7 +346,7 @@ async fn handle_report(
.collect()
}
CardDavReportType::SyncCollection { .. } => contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?,
};
@@ -403,7 +403,7 @@ async fn handle_mkcol(
let create_dto = CreateAddressBookDto {
name,
owner_id: user.id.clone(),
owner_id: user.id.to_string(),
description,
color,
is_public: Some(false),
@@ -452,7 +452,7 @@ async fn handle_put(
// Check if contact already exists
let existing = if let Some(ref uid) = vcard_uid {
let contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.unwrap_or_default();
contacts.into_iter().find(|c| c.uid == *uid)
@@ -463,14 +463,14 @@ async fn handle_put(
if let Some(existing_contact) = existing {
// Update: delete + recreate from vCard
contact_svc
.delete_contact(&existing_contact.id, &user.id)
.delete_contact(&existing_contact.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to update contact: {}", e)))?;
let create_dto = CreateContactVCardDto {
address_book_id: address_book_id.to_string(),
vcard: vcard_data,
user_id: user.id.clone(),
user_id: user.id.to_string(),
};
let contact = contact_svc
.create_contact_from_vcard(create_dto)
@@ -486,7 +486,7 @@ async fn handle_put(
let create_dto = CreateContactVCardDto {
address_book_id: address_book_id.to_string(),
vcard: vcard_data,
user_id: user.id.clone(),
user_id: user.id.to_string(),
};
let contact = contact_svc
@@ -529,7 +529,7 @@ async fn handle_get(
if parts.len() < 2 {
// GET on address book collection — return all contacts as vcf
let contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
@@ -549,7 +549,7 @@ async fn handle_get(
let contact_uid = contact_file.trim_end_matches(".vcf");
let contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
@@ -590,7 +590,7 @@ async fn handle_delete(
if parts.len() < 2 {
// Delete address book
addressbook_service
.delete_address_book(address_book_id, &user.id)
.delete_address_book(address_book_id, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to delete address book: {}", e))
@@ -601,7 +601,7 @@ async fn handle_delete(
let contact_uid = contact_file.trim_end_matches(".vcf");
let contacts = contact_svc
.list_contacts(address_book_id, &user.id)
.list_contacts(address_book_id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list contacts: {}", e)))?;
@@ -611,7 +611,7 @@ async fn handle_delete(
.ok_or_else(|| AppError::not_found(format!("Contact not found: {}", contact_uid)))?;
contact_svc
.delete_contact(&contact.id, &user.id)
.delete_contact(&contact.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete contact: {}", e)))?;
}
@@ -653,7 +653,7 @@ async fn handle_proppatch(
description: None,
color: None,
is_public: None,
user_id: user.id.clone(),
user_id: user.id.to_string(),
};
for prop in &props_to_set {
@@ -108,7 +108,7 @@ impl ChunkedUploadHandler {
// ── Quota enforcement ────────────────────────────────────
if let Some(storage_svc) = state.storage_usage_service.as_ref()
&& let Err(err) = storage_svc
.check_storage_quota(&auth_user.id, request.total_size)
.check_storage_quota(auth_user.id, request.total_size)
.await
{
tracing::warn!(
@@ -146,7 +146,7 @@ impl ChunkedUploadHandler {
match chunked_service
.create_session(
&auth_user.id,
auth_user.id,
request.filename,
request.folder_id,
content_type,
@@ -192,7 +192,7 @@ impl ChunkedUploadHandler {
match chunked_service
.upload_chunk(
&upload_id,
&auth_user.id,
auth_user.id,
params.chunk_index,
body,
checksum,
@@ -233,7 +233,7 @@ impl ChunkedUploadHandler {
) -> impl IntoResponse {
let chunked_service = &state.core.chunked_upload_service;
match chunked_service.get_status(&upload_id, &auth_user.id).await {
match chunked_service.get_status(&upload_id, auth_user.id).await {
Ok(status) => Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
@@ -268,7 +268,7 @@ impl ChunkedUploadHandler {
// Assemble chunks (hash-on-write: SHA-256 computed during assembly)
let (assembled_path, filename, folder_id, content_type, total_size, hash) =
match chunked_service
.complete_upload(&upload_id, &auth_user.id)
.complete_upload(&upload_id, auth_user.id)
.await
{
Ok(result) => result,
@@ -299,7 +299,7 @@ impl ChunkedUploadHandler {
Ok(file) => {
// Cleanup session
let _ = chunked_service
.finalize_upload(&upload_id, &auth_user.id)
.finalize_upload(&upload_id, auth_user.id)
.await;
tracing::info!(
@@ -338,7 +338,7 @@ impl ChunkedUploadHandler {
let chunked_service = &state.core.chunked_upload_service;
match chunked_service
.cancel_upload(&upload_id, &auth_user.id)
.cancel_upload(&upload_id, auth_user.id)
.await
{
Ok(_) => StatusCode::NO_CONTENT.into_response(),
+2 -2
View File
@@ -99,7 +99,7 @@ impl DedupHandler {
}
// Only reveal whether THIS user has the blob — no global oracle
let user_has_it = dedup.user_owns_blob_reference(&hash, &auth_user.id).await;
let user_has_it = dedup.user_owns_blob_reference(&hash, &auth_user.id.to_string()).await;
if user_has_it {
// Fetch size from metadata (safe — user owns a reference)
@@ -346,7 +346,7 @@ impl DedupHandler {
}
// Verify the user owns at least one file referencing this blob
if !dedup.user_owns_blob_reference(&hash, &auth_user.id).await {
if !dedup.user_owns_blob_reference(&hash, &auth_user.id.to_string()).await {
return Response::builder()
.status(StatusCode::NOT_FOUND)
.header(header::CONTENT_TYPE, "application/json")
@@ -16,6 +16,7 @@ use axum::{
routing::{delete, get, post},
};
use std::sync::Arc;
use uuid::Uuid;
use crate::application::dtos::device_auth_dto::*;
use crate::application::services::device_auth_service::DeviceAuthService;
@@ -145,7 +146,7 @@ async fn device_verify_action(
match body.action.to_lowercase().as_str() {
"approve" | "allow" | "accept" => {
device_service
.approve(&body.user_code, &auth_user.id)
.approve(&body.user_code, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Device approve failed: {}", e);
@@ -181,7 +182,7 @@ async fn list_devices(
let device_service = get_device_service(&state)?;
let devices = device_service
.list_user_devices(&auth_user.id)
.list_user_devices(auth_user.id)
.await
.map_err(|e| {
tracing::error!("List devices failed: {}", e);
@@ -202,8 +203,10 @@ async fn revoke_device(
) -> Result<impl IntoResponse, AppError> {
let device_service = get_device_service(&state)?;
let device_id = Uuid::parse_str(&device_id).map_err(|_| AppError::bad_request("Invalid device ID"))?;
device_service
.revoke_device(&device_id, &auth_user.id)
.revoke_device(device_id, auth_user.id)
.await
.map_err(|e| {
tracing::error!("Revoke device failed: {}", e);
@@ -30,7 +30,7 @@ pub async fn get_favorites(
State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
match favorites_service.get_favorites(user_id).await {
Ok(favorites) => {
@@ -56,7 +56,7 @@ pub async fn add_favorite(
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
// Validate item_type
if item_type != "file" && item_type != "folder" {
@@ -99,7 +99,7 @@ pub async fn remove_favorite(
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
match favorites_service
.remove_from_favorites(user_id, &item_id, &item_type)
@@ -143,7 +143,7 @@ pub async fn batch_add_favorites(
auth_user: AuthUser,
Json(body): Json<BatchFavoritesRequest>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
if body.items.is_empty() {
return (
+12 -12
View File
@@ -109,7 +109,7 @@ impl FileHandler {
if let Some(ref fid) = folder_id {
use crate::application::ports::inbound::FolderUseCase;
let folder_service = &state.applications.folder_service;
if folder_service.get_folder_owned(fid, &auth_user.id).await.is_err() {
if folder_service.get_folder_owned(fid, auth_user.id).await.is_err() {
tracing::warn!(
"⛔ UPLOAD REJECTED (IDOR): user='{}' attempted upload to folder '{}' owned by another user",
auth_user.username,
@@ -130,7 +130,7 @@ impl FileHandler {
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0);
if let Err(err) = storage_svc
.check_storage_quota(&auth_user.id, estimated_size)
.check_storage_quota(auth_user.id, estimated_size)
.await
{
tracing::warn!(
@@ -235,7 +235,7 @@ impl FileHandler {
// ── Quota enforcement ────────────────────────────────
if let Some(storage_svc) = state.storage_usage_service.as_ref()
&& let Err(err) = storage_svc
.check_storage_quota(&auth_user.id, total_size)
.check_storage_quota(auth_user.id, total_size)
.await
{
let _ = tokio::fs::remove_file(&temp_path).await;
@@ -321,7 +321,7 @@ impl FileHandler {
};
let file = match file_retrieval_service
.get_file_owned(&id, &auth_user.id)
.get_file_owned(&id, auth_user.id)
.await
{
Ok(f) => f,
@@ -397,7 +397,7 @@ impl FileHandler {
let retrieval = &state.applications.file_retrieval_service;
// ── Get file metadata (ownership-scoped) ────────────────────────
let file_dto = match retrieval.get_file_owned(&id, &auth_user.id).await {
let file_dto = match retrieval.get_file_owned(&id, auth_user.id).await {
Ok(f) => f,
Err(err) => {
return AppError::from(err).into_response();
@@ -455,7 +455,7 @@ impl FileHandler {
Self::content_disposition(&file_dto.name, &file_dto.mime_type, &params);
match retrieval
.get_file_range_stream_owned(&id, &auth_user.id, start, Some(end + 1))
.get_file_range_stream_owned(&id, auth_user.id, start, Some(end + 1))
.await
{
Ok(stream) => {
@@ -569,7 +569,7 @@ impl FileHandler {
tracing::info!("API: Listing files with folder_id: {:?}", folder_id);
let retrieval = &state.applications.file_retrieval_service;
match retrieval.list_files_owned(folder_id, &auth_user.id).await {
match retrieval.list_files_owned(folder_id, auth_user.id).await {
Ok(files) => {
// Compute lightweight ETag from max modified_at + count
let max_mod = files.iter().map(|f| f.modified_at).max().unwrap_or(0);
@@ -655,7 +655,7 @@ impl FileHandler {
) -> impl IntoResponse {
// Verify ownership
let file_read = &state.repositories.file_read_repository;
if let Err(e) = file_read.verify_file_owner(&file_id, &auth_user.id).await {
if let Err(e) = file_read.verify_file_owner(&file_id, auth_user.id).await {
let msg = e.to_string();
return (
StatusCode::NOT_FOUND,
@@ -703,7 +703,7 @@ impl FileHandler {
// Auth required: trash-first with dedup cleanup + ownership verification
let result = mgmt
.delete_with_cleanup(&id, &auth_user.id)
.delete_with_cleanup(&id, auth_user.id)
.await
.map(|was_trashed| {
if was_trashed {
@@ -745,7 +745,7 @@ impl FileHandler {
tracing::info!("Renaming file {} to \"{}\"", id, new_name);
let mgmt = &state.applications.file_management_service;
match mgmt.rename_file_owned(&id, &auth_user.id, &new_name).await {
match mgmt.rename_file_owned(&id, auth_user.id, &new_name).await {
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
Err(err) => AppError::from(err).into_response()
}
@@ -763,7 +763,7 @@ impl FileHandler {
let mgmt = &state.applications.file_management_service;
match mgmt
.move_file_owned(&id, &auth_user.id, payload.folder_id)
.move_file_owned(&id, auth_user.id, payload.folder_id)
.await
{
Ok(file) => (StatusCode::OK, Json(file)).into_response(),
@@ -784,7 +784,7 @@ impl FileHandler {
.map(|s| s.to_string());
let mgmt = &state.applications.file_management_service;
match mgmt.move_file_owned(&id, &auth_user.id, folder_id).await {
match mgmt.move_file_owned(&id, auth_user.id, folder_id).await {
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
Err(err) => AppError::from(err).into_response()
}
+12 -12
View File
@@ -42,7 +42,7 @@ impl FolderHandler {
"create_folder: parent_id is None for user '{}', resolving home folder",
auth_user.username
);
match service.list_folders_for_owner(None, &auth_user.id).await {
match service.list_folders_for_owner(None, auth_user.id).await {
Ok(folders) => {
if let Some(home) = folders.first() {
tracing::info!(
@@ -71,7 +71,7 @@ impl FolderHandler {
if let Some(ref parent_id) = dto.parent_id {
use crate::application::ports::inbound::FolderUseCase;
if service
.get_folder_owned(parent_id, &auth_user.id)
.get_folder_owned(parent_id, auth_user.id)
.await
.is_err()
{
@@ -102,7 +102,7 @@ impl FolderHandler {
Ok(folder) => {
// Access check: folder must belong to the requesting user
if let Some(ref owner) = folder.owner_id
&& owner != &auth_user.id
&& owner != &auth_user.id.to_string()
{
tracing::warn!(
"get_folder: user '{}' attempted to access folder '{}' owned by '{}'",
@@ -155,7 +155,7 @@ impl FolderHandler {
pagination: Query<PaginationRequestDto>,
) -> axum::response::Response {
match service
.list_folders_for_owner_paginated(Some(&id), &auth_user.id, &pagination)
.list_folders_for_owner_paginated(Some(&id), auth_user.id, &pagination)
.await
{
Ok(paginated_result) => (StatusCode::OK, Json(paginated_result)).into_response(),
@@ -172,7 +172,7 @@ impl FolderHandler {
auth_user: &AuthUser,
) -> axum::response::Response {
match service
.list_folders_for_owner(parent_id, &auth_user.id)
.list_folders_for_owner(parent_id, auth_user.id)
.await
{
Ok(folders) => (StatusCode::OK, Json(folders)).into_response(),
@@ -215,8 +215,8 @@ impl FolderHandler {
// Run both queries concurrently — no sequential wait.
let (folders_result, files_result) = tokio::join!(
folder_service.list_folders_for_owner(Some(&id), &auth_user.id),
file_service.list_files_owned(Some(&id), &auth_user.id)
folder_service.list_folders_for_owner(Some(&id), auth_user.id),
file_service.list_files_owned(Some(&id), auth_user.id)
);
match (folders_result, files_result) {
@@ -253,7 +253,7 @@ impl FolderHandler {
Path(id): Path<String>,
Json(dto): Json<RenameFolderDto>,
) -> impl IntoResponse {
match service.rename_folder(&id, dto, &auth_user.id).await {
match service.rename_folder(&id, dto, auth_user.id).await {
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
Err(err) => AppError::from(err).into_response(),
}
@@ -266,7 +266,7 @@ impl FolderHandler {
Path(id): Path<String>,
Json(dto): Json<MoveFolderDto>,
) -> impl IntoResponse {
match service.move_folder(&id, dto, &auth_user.id).await {
match service.move_folder(&id, dto, auth_user.id).await {
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
Err(err) => AppError::from(err).into_response(),
}
@@ -278,7 +278,7 @@ impl FolderHandler {
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
match service.delete_folder(&id, &auth_user.id).await {
match service.delete_folder(&id, auth_user.id).await {
Ok(_) => StatusCode::NO_CONTENT.into_response(),
Err(err) => AppError::from(err).into_response(),
}
@@ -290,7 +290,7 @@ impl FolderHandler {
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
// Check if trash service is available
if let Some(trash_service) = &state.trash_service {
tracing::info!("Moving folder to trash: {}", id);
@@ -337,7 +337,7 @@ impl FolderHandler {
match folder_service.get_folder(&id).await {
Ok(folder) => {
// Access check: folder must belong to the requesting user
if folder.owner_id.as_deref() != Some(&auth_user.id) {
if folder.owner_id.as_deref() != Some(&auth_user.id.to_string()) {
tracing::warn!(
"download_folder_zip: user '{}' attempted to download folder '{}' owned by '{:?}'",
auth_user.id,
@@ -31,7 +31,7 @@ pub async fn list_photos(
auth_user: AuthUser,
Query(params): Query<PhotosQueryParams>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
let limit = params.limit.unwrap_or(200).clamp(1, 500);
let file_read = &state.repositories.file_read_repository;
@@ -25,7 +25,7 @@ pub async fn get_recent_items(
auth_user: AuthUser,
Query(params): Query<GetRecentParams>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
match recent_service.get_recent_items(user_id, params.limit).await {
Ok(items) => {
@@ -51,7 +51,7 @@ pub async fn record_item_access(
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
// Validate item type
if item_type != "file" && item_type != "folder" {
@@ -97,7 +97,7 @@ pub async fn remove_from_recent(
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
match recent_service
.remove_from_recent(user_id, &item_id, &item_type)
@@ -142,7 +142,7 @@ pub async fn clear_recent_items(
State(recent_service): State<Arc<RecentService>>,
auth_user: AuthUser,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
match recent_service.clear_recent_items(user_id).await {
Ok(_) => {
@@ -60,7 +60,7 @@ impl SearchHandler {
sort_by: params.sort_by.unwrap_or_else(|| "relevance".to_string()),
};
match search_service.search(search_criteria, &auth_user.id).await {
match search_service.search(search_criteria, auth_user.id).await {
Ok(results) => {
info!(
"Search completed in {}ms — {} files, {} folders",
@@ -101,7 +101,7 @@ impl SearchHandler {
}
};
match search_service.search(criteria, &auth_user.id).await {
match search_service.search(criteria, auth_user.id).await {
Ok(results) => {
info!(
"Advanced search completed in {}ms — {} files, {} folders",
+18 -5
View File
@@ -1,4 +1,5 @@
use std::sync::Arc;
use uuid::Uuid;
use axum::{
Json,
@@ -40,7 +41,7 @@ pub async fn create_shared_link(
auth_user: AuthUser,
Json(dto): Json<CreateShareDto>,
) -> impl IntoResponse {
match share_use_case.create_shared_link(&auth_user.id, dto).await {
match share_use_case.create_shared_link(auth_user.id, dto).await {
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
Err(err) => AppError::from(err).into_response(),
}
@@ -52,7 +53,11 @@ pub async fn get_shared_link(
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
match share_use_case.get_shared_link(&id, &auth_user.id).await {
let id = match Uuid::parse_str(&id) {
Ok(id) => id,
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
};
match share_use_case.get_shared_link(id, auth_user.id).await {
Ok(share) => (StatusCode::OK, Json(share)).into_response(),
Err(err) => AppError::from(err).into_response(),
}
@@ -65,7 +70,7 @@ pub async fn get_user_shares(
auth_user: AuthUser,
Query(query): Query<GetSharesQuery>,
) -> impl IntoResponse {
let user_id = &auth_user.id;
let user_id = auth_user.id;
// If both item_id and item_type are provided, return shares for that specific item
if let (Some(item_id), Some(item_type_str)) = (&query.item_id, &query.item_type) {
@@ -108,8 +113,12 @@ pub async fn update_shared_link(
Path(id): Path<String>,
Json(dto): Json<UpdateShareDto>,
) -> impl IntoResponse {
let id = match Uuid::parse_str(&id) {
Ok(id) => id,
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
};
match share_use_case
.update_shared_link(&id, &auth_user.id, dto)
.update_shared_link(id, auth_user.id, dto)
.await
{
Ok(share) => (StatusCode::OK, Json(share)).into_response(),
@@ -123,7 +132,11 @@ pub async fn delete_shared_link(
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
match share_use_case.delete_shared_link(&id, &auth_user.id).await {
let id = match Uuid::parse_str(&id) {
Ok(id) => id,
Err(_) => return AppError::bad_request("Invalid UUID").into_response(),
};
match share_use_case.delete_shared_link(id, auth_user.id).await {
Ok(_) => StatusCode::NO_CONTENT.into_response(),
Err(err) => AppError::from(err).into_response(),
}
+7 -7
View File
@@ -18,7 +18,7 @@ pub async fn get_trash_items(
// SECURITY: Always use the authenticated user's ID from the JWT token.
// Never allow user ID override via query parameters to prevent
// privilege escalation attacks.
let effective_user = auth_user.id.clone();
let effective_user = auth_user.id;
debug!("Request to list trash items for user {}", effective_user);
@@ -34,7 +34,7 @@ pub async fn get_trash_items(
}
};
let result = trash_service.get_trash_items(&effective_user).await;
let result = trash_service.get_trash_items(effective_user).await;
match result {
Ok(items) => {
@@ -60,7 +60,7 @@ pub async fn move_file_to_trash(
auth_user: AuthUser,
Path(item_id): Path<String>,
) -> (StatusCode, Json<serde_json::Value>) {
let user_id = &auth_user.id;
let user_id = auth_user.id;
debug!(
"Request to move file to trash: id={}, user={}",
item_id, user_id
@@ -111,7 +111,7 @@ pub async fn move_folder_to_trash(
auth_user: AuthUser,
Path(item_id): Path<String>,
) -> (StatusCode, Json<serde_json::Value>) {
let user_id = &auth_user.id;
let user_id = auth_user.id;
debug!(
"Request to move folder to trash: id={}, user={}",
item_id, user_id
@@ -177,7 +177,7 @@ pub async fn restore_from_trash(
);
}
};
let result = trash_service.restore_item(&trash_id, &auth_user.id).await;
let result = trash_service.restore_item(&trash_id, auth_user.id).await;
match result {
Ok(_) => {
@@ -239,7 +239,7 @@ pub async fn delete_permanently(
}
};
let result = trash_service
.delete_permanently(&trash_id, &auth_user.id)
.delete_permanently(&trash_id, auth_user.id)
.await;
match result {
@@ -300,7 +300,7 @@ pub async fn empty_trash(
);
}
};
let result = trash_service.empty_trash(&auth_user.id).await;
let result = trash_service.empty_trash(auth_user.id).await;
match result {
Ok(_) => {
+45 -46
View File
@@ -183,7 +183,7 @@ async fn handle_webdav_methods(
/// If `path` doesn't already start with the user's home folder name, prepend
/// the home folder path so downstream services can find the resource in the DB.
/// Returns `None` when the path already includes the prefix or resolution fails.
async fn resolve_webdav_path(state: &Arc<AppState>, user_id: &str, path: &str) -> Option<String> {
async fn resolve_webdav_path(state: &Arc<AppState>, user_id: Uuid, path: &str) -> Option<String> {
let folder_service = &state.applications.folder_service;
let home_folders = folder_service
.list_folders_for_owner(None, user_id)
@@ -209,9 +209,9 @@ async fn handle_webdav_dispatch(
// prefix when the path doesn't already include it.
// Extract user_id before any async call to keep the future Send.
let path = if !path.is_empty() && method.as_str() != "OPTIONS" {
let user_id = req.extensions().get::<Arc<CurrentUser>>().map(|u| u.id.clone());
let user_id = req.extensions().get::<Arc<CurrentUser>>().map(|u| u.id);
if let Some(uid) = user_id {
resolve_webdav_path(&state, &uid, &path)
resolve_webdav_path(&state, uid, &path)
.await
.unwrap_or(path)
} else {
@@ -371,14 +371,14 @@ async fn handle_propfind(
propfind_request,
folder_service,
file_retrieval_service,
&user.id,
user.id,
)
.await;
}
// Single-query path resolution: folder OR file in one DB round-trip
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&path, &user.id).await {
match resolver.resolve_path_for_user(&path, user.id).await {
Ok(ResolvedResource::Folder(folder)) => {
let folder_id = folder.id.clone();
return build_streaming_propfind_response(
@@ -389,7 +389,7 @@ async fn handle_propfind(
propfind_request,
folder_service,
file_retrieval_service,
&user.id,
user.id,
)
.await;
}
@@ -420,7 +420,7 @@ async fn handle_propfind(
} else {
// Fallback: legacy double-query path when PathResolver is unavailable
if let Ok(folder) = folder_service.get_folder_by_path(&path).await {
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
let folder_id = folder.id.clone();
return build_streaming_propfind_response(
folder,
@@ -430,12 +430,12 @@ async fn handle_propfind(
propfind_request,
folder_service,
file_retrieval_service,
&user.id,
user.id,
)
.await;
}
if let Ok(file) = file_retrieval_service.get_file_by_path(&path).await {
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
let mut buf = Vec::with_capacity(1024);
{
let mut xml_writer = Writer::new(&mut buf);
@@ -477,12 +477,11 @@ async fn build_streaming_propfind_response(
propfind_request: PropFindRequest,
folder_service: std::sync::Arc<FolderService>,
file_retrieval_service: std::sync::Arc<FileRetrievalService>,
user_id: &str,
user_id: Uuid,
) -> Result<Response<Body>, AppError> {
let depth = depth.to_string();
let base_href = base_href.to_string();
let propfind_request = Arc::new(propfind_request);
let user_id = user_id.to_string();
let stream = async_stream::try_stream! {
// ── XML header + <D:multistatus> + folder entry ──────────
@@ -512,7 +511,7 @@ async fn build_streaming_propfind_response(
page_size: pagination.page_size,
};
let result = folder_service
.list_folders_for_owner_paginated(fid_ref, &user_id, &pag)
.list_folders_for_owner_paginated(fid_ref, user_id, &pag)
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
@@ -542,7 +541,7 @@ async fn build_streaming_propfind_response(
let mut offset: i64 = 0;
loop {
let batch: Vec<FileDto> = file_retrieval_service
.list_files_batch_for_owner(fid_ref, &user_id, offset, PROPFIND_BATCH_SIZE)
.list_files_batch_for_owner(fid_ref, user_id, offset, PROPFIND_BATCH_SIZE)
.await
.map_err(|e| std::io::Error::other(e.to_string()))?;
@@ -675,7 +674,7 @@ async fn handle_get(
// Resolve file — user-scoped when PathResolver is available
let file = if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&path, &user.id).await {
match resolver.resolve_path_for_user(&path, user.id).await {
Ok(ResolvedResource::File(f)) => f,
Ok(ResolvedResource::Folder(_)) => {
return Err(AppError::bad_request("Cannot GET a directory"));
@@ -690,7 +689,7 @@ async fn handle_get(
.get_file_by_path(&path)
.await
.map_err(|_e| AppError::not_found(format!("File not found: {}", path)))?;
assert_owner(f.owner_id.as_deref(), &user.id, &path)?;
assert_owner(f.owner_id.as_deref(), &user.id.to_string(), &path)?;
f
};
@@ -740,7 +739,7 @@ async fn handle_head(
// Single-query path resolution (user-scoped)
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&path, &user.id).await {
match resolver.resolve_path_for_user(&path, user.id).await {
Ok(ResolvedResource::Folder(folder)) => {
return Ok(Response::builder()
.status(StatusCode::OK)
@@ -771,7 +770,7 @@ async fn handle_head(
// Fallback: legacy double-query path (with ownership check)
if let Ok(folder) = folder_service.get_folder_by_path(&path).await {
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
return Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "httpd/unix-directory")
@@ -786,7 +785,7 @@ async fn handle_head(
.get_file_by_path(&path)
.await
.map_err(|_e| AppError::not_found(format!("Resource not found: {}", path)))?;
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
Ok(Response::builder()
.status(StatusCode::OK)
@@ -840,7 +839,7 @@ async fn handle_put(
// parent folder (create). Without this check a user could
// overwrite another user's file via a crafted PUT path.
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&path, &user.id).await {
match resolver.resolve_path_for_user(&path, user.id).await {
Ok(ResolvedResource::File(_)) => { /* existing file owned by user — OK */ }
Ok(ResolvedResource::Folder(_)) => {
return Err(AppError::bad_request("Cannot PUT to a directory"));
@@ -854,7 +853,7 @@ async fn handle_put(
};
if !parent_path.is_empty() {
resolver
.resolve_path_for_user(parent_path, &user.id)
.resolve_path_for_user(parent_path, user.id)
.await
.map_err(|_| {
AppError::not_found(format!("Parent folder not found: {}", parent_path))
@@ -1052,10 +1051,10 @@ async fn handle_delete(
// Single-query path resolution (user-scoped)
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&path, &user.id).await {
match resolver.resolve_path_for_user(&path, user.id).await {
Ok(ResolvedResource::Folder(folder)) => {
folder_service
.delete_folder(&folder.id, &user.id)
.delete_folder(&folder.id, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to delete folder: {}", e))
@@ -1076,9 +1075,9 @@ async fn handle_delete(
let folder_result = folder_service.get_folder_by_path(&path).await;
if let Ok(folder) = folder_result {
assert_owner(folder.owner_id.as_deref(), &user.id, &path)?;
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &path)?;
folder_service
.delete_folder(&folder.id, &user.id)
.delete_folder(&folder.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
} else {
@@ -1086,7 +1085,7 @@ async fn handle_delete(
.get_file_by_path(&path)
.await
.map_err(|_e| AppError::not_found(format!("Resource not found: {}", path)))?;
assert_owner(file.owner_id.as_deref(), &user.id, &path)?;
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &path)?;
file_management_service
.delete_file(&file.id)
@@ -1157,7 +1156,7 @@ async fn handle_move(
if !overwrite {
let dest_exists = if let Some(resolver) = &state.path_resolver {
resolver
.exists_for_user(&destination_path, &user.id)
.exists_for_user(&destination_path, user.id)
.await
.unwrap_or(false)
} else {
@@ -1179,7 +1178,7 @@ async fn handle_move(
// Resolve source: single-query when PathResolver is available (user-scoped)
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&source_path, &user.id).await {
match resolver.resolve_path_for_user(&source_path, user.id).await {
Ok(ResolvedResource::Folder(folder)) => {
let dest_folder_name = destination_path
.split('/')
@@ -1200,7 +1199,7 @@ async fn handle_move(
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(
parent.owner_id.as_deref(),
&user.id,
&user.id.to_string(),
dest_parent_path,
)?;
Some(parent.id)
@@ -1211,7 +1210,7 @@ async fn handle_move(
};
folder_service
.move_folder(&folder.id, move_dto, &user.id)
.move_folder(&folder.id, move_dto, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to move folder: {}", e))
@@ -1222,7 +1221,7 @@ async fn handle_move(
name: dest_folder_name.to_string(),
};
folder_service
.rename_folder(&folder.id, rename_dto, &user.id)
.rename_folder(&folder.id, rename_dto, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to rename folder: {}", e))
@@ -1251,7 +1250,7 @@ async fn handle_move(
&& let Ok(parent) =
folder_service.get_folder_by_path(dest_parent_path).await
{
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
}
file_management_service
.move_file(&file.id, Some(dest_parent_path.to_string()))
@@ -1281,7 +1280,7 @@ async fn handle_move(
let folder_result = folder_service.get_folder_by_path(&source_path).await;
if let Ok(folder) = folder_result {
assert_owner(folder.owner_id.as_deref(), &user.id, &source_path)?;
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
let dest_folder_name = destination_path
.split('/')
.next_back()
@@ -1299,7 +1298,7 @@ async fn handle_move(
match folder_service.get_folder_by_path(dest_parent_path).await {
Ok(parent) => {
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
Some(parent.id)
}
Err(_) => None,
@@ -1308,7 +1307,7 @@ async fn handle_move(
};
folder_service
.move_folder(&folder.id, move_dto, &user.id)
.move_folder(&folder.id, move_dto, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to move folder: {}", e)))?;
@@ -1317,7 +1316,7 @@ async fn handle_move(
name: dest_folder_name.to_string(),
};
folder_service
.rename_folder(&folder.id, rename_dto, &user.id)
.rename_folder(&folder.id, rename_dto, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to rename folder: {}", e))
@@ -1330,7 +1329,7 @@ async fn handle_move(
.map_err(|_e| {
AppError::not_found(format!("Resource not found: {}", source_path))
})?;
assert_owner(file.owner_id.as_deref(), &user.id, &source_path)?;
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
let dest_filename = destination_path
.split('/')
@@ -1352,7 +1351,7 @@ async fn handle_move(
if !dest_parent_path.is_empty()
&& let Ok(parent) = folder_service.get_folder_by_path(dest_parent_path).await
{
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
}
file_management_service
.move_file(&file.id, Some(dest_parent_path.to_string()))
@@ -1438,7 +1437,7 @@ async fn handle_copy(
if !overwrite {
let dest_exists = if let Some(resolver) = &state.path_resolver {
resolver
.exists_for_user(&destination_path, &user.id)
.exists_for_user(&destination_path, user.id)
.await
.unwrap_or(false)
} else {
@@ -1460,7 +1459,7 @@ async fn handle_copy(
// Resolve source: single-query when PathResolver is available (user-scoped)
if let Some(resolver) = &state.path_resolver {
match resolver.resolve_path_for_user(&source_path, &user.id).await {
match resolver.resolve_path_for_user(&source_path, user.id).await {
Ok(ResolvedResource::Folder(folder)) => {
let recursive = depth != "0";
@@ -1480,7 +1479,7 @@ async fn handle_copy(
match folder_service.get_folder_by_path(dest_parent_path).await {
Ok(parent) => {
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
Some(parent.id)
}
Err(_) => None,
@@ -1528,7 +1527,7 @@ async fn handle_copy(
match folder_service.get_folder_by_path(dest_parent_path).await {
Ok(parent) => {
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
Some(parent.id)
}
Err(_) => None,
@@ -1553,7 +1552,7 @@ async fn handle_copy(
let folder_result = folder_service.get_folder_by_path(&source_path).await;
if let Ok(folder) = folder_result {
assert_owner(folder.owner_id.as_deref(), &user.id, &source_path)?;
assert_owner(folder.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
let recursive = depth != "0";
let dest_folder_name = destination_path
@@ -1572,7 +1571,7 @@ async fn handle_copy(
match folder_service.get_folder_by_path(dest_parent_path).await {
Ok(parent) => {
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
Some(parent.id)
}
Err(_) => None,
@@ -1613,7 +1612,7 @@ async fn handle_copy(
.map_err(|_e| {
AppError::not_found(format!("Resource not found: {}", source_path))
})?;
assert_owner(file.owner_id.as_deref(), &user.id, &source_path)?;
assert_owner(file.owner_id.as_deref(), &user.id.to_string(), &source_path)?;
let dest_parent_path = if let Some(idx) = destination_path.rfind('/') {
&destination_path[..idx]
@@ -1627,7 +1626,7 @@ async fn handle_copy(
match folder_service.get_folder_by_path(dest_parent_path).await {
Ok(parent) => {
// SECURITY: verify destination parent belongs to caller (V-08)
assert_owner(parent.owner_id.as_deref(), &user.id, dest_parent_path)?;
assert_owner(parent.owner_id.as_deref(), &user.id.to_string(), dest_parent_path)?;
Some(parent.id)
}
Err(_) => None,
@@ -1737,7 +1736,7 @@ async fn handle_lock(
let token = format!("opaquelocktoken:{}", Uuid::new_v4());
let lock_info = LockInfo {
token,
owner: owner.or(Some(user.id.clone())),
owner: owner.or(Some(user.id.to_string())),
depth: depth.to_string(),
timeout,
scope,
+8 -4
View File
@@ -396,7 +396,7 @@ pub struct EditorUrlResponse {
async fn authorize_wopi_access<S: FileRetrievalUseCase>(
file_retrieval: &S,
file_id: &str,
caller_id: &str,
caller_id: uuid::Uuid,
requested_action: &str,
) -> Result<(crate::application::dtos::file_dto::FileDto, bool), StatusCode> {
let file = file_retrieval
@@ -425,7 +425,7 @@ pub async fn get_editor_url(
let (file, can_write) = match authorize_wopi_access(
state.app_state.applications.file_retrieval_service.as_ref(),
&params.file_id,
&user_id,
user_id,
&params.action,
)
.await
@@ -464,7 +464,7 @@ pub async fn get_editor_url(
let (access_token, access_token_ttl) =
match state
.token_service
.generate_token(&params.file_id, &user_id, &username, can_write)
.generate_token(&params.file_id, &user_id.to_string(), &username, can_write)
{
Ok(t) => t,
Err(e) => {
@@ -503,10 +503,14 @@ async fn host_page(
// Re-verify ownership even though the token was valid — defence in depth.
let requested_action = if claims.can_write { "edit" } else { "view" };
let caller_uuid = match uuid::Uuid::parse_str(&claims.sub) {
Ok(u) => u,
Err(_) => return StatusCode::UNAUTHORIZED.into_response(),
};
let file = match authorize_wopi_access(
state.app_state.applications.file_retrieval_service.as_ref(),
&file_id,
&claims.sub,
caller_uuid,
requested_action,
)
.await
+16 -9
View File
@@ -6,6 +6,7 @@ use axum::{
};
use std::convert::Infallible;
use std::sync::Arc;
use uuid::Uuid;
use crate::common::di::AppState;
@@ -22,7 +23,7 @@ pub struct CookieAuthenticated;
// Structure for use in Axum extractors
#[derive(Clone, Debug)]
pub struct AuthUser {
pub id: String,
pub id: Uuid,
pub username: String,
pub role: String,
}
@@ -35,7 +36,7 @@ pub struct AuthUser {
/// async fn my_handler(CurrentUserId(user_id): CurrentUserId) -> impl IntoResponse { ... }
/// ```
#[derive(Clone, Debug)]
pub struct CurrentUserId(pub String);
pub struct CurrentUserId(pub Uuid);
// Implement FromRequestParts for AuthUser — allows using `auth_user: AuthUser` in handlers
impl<S> FromRequestParts<S> for AuthUser
@@ -49,7 +50,7 @@ where
.extensions
.get::<Arc<CurrentUser>>()
.map(|cu| AuthUser {
id: cu.id.clone(),
id: cu.id,
username: cu.username.clone(),
role: cu.role.clone(),
})
@@ -86,7 +87,7 @@ where
parts
.extensions
.get::<Arc<CurrentUser>>()
.map(|cu| CurrentUserId(cu.id.clone()))
.map(|cu| CurrentUserId(cu.id))
.ok_or(AuthError::UserNotFound)
}
}
@@ -94,7 +95,7 @@ where
/// Optional user ID extractor – never fails.
/// Yields `Some(id)` when auth middleware ran, `None` otherwise.
#[derive(Clone, Debug)]
pub struct OptionalUserId(pub Option<String>);
pub struct OptionalUserId(pub Option<Uuid>);
impl<S> FromRequestParts<S> for OptionalUserId
where
@@ -107,7 +108,7 @@ where
parts
.extensions
.get::<Arc<CurrentUser>>()
.map(|cu| cu.id.clone()),
.map(|cu| cu.id),
))
}
}
@@ -126,7 +127,7 @@ where
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
Ok(OptionalAuthUser(parts.extensions.get::<Arc<CurrentUser>>().map(
|cu| AuthUser {
id: cu.id.clone(),
id: cu.id,
username: cu.username.clone(),
role: cu.role.clone(),
},
@@ -216,8 +217,11 @@ pub async fn auth_middleware(
"Token validated successfully for user: {}",
claims.username
);
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
AuthError::InvalidToken("Invalid user ID in token".to_string())
})?;
let current_user = Arc::new(CurrentUser {
id: claims.sub,
id: user_id,
username: claims.username,
email: claims.email,
role: claims.role,
@@ -303,8 +307,11 @@ pub async fn auth_middleware(
match token_service.validate_token(&token_str) {
Ok(claims) => {
tracing::debug!("Cookie token validated for user: {}", claims.username);
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
AuthError::InvalidToken("Invalid user ID in token".to_string())
})?;
let current_user = Arc::new(CurrentUser {
id: claims.sub,
id: user_id,
username: claims.username,
email: claims.email,
role: claims.role,
+1 -1
View File
@@ -174,7 +174,7 @@ pub async fn handle_login_submit(
let app_password = match nextcloud
.app_passwords
.create_nc(&current_user.id, "Nextcloud")
.create_nc(current_user.id, "Nextcloud")
.await
{
Ok((_id, password)) => password,
+4 -4
View File
@@ -47,7 +47,7 @@ pub async fn handle_capabilities_v2(State(state): State<Arc<AppState>>) -> Respo
pub async fn handle_user_info(State(state): State<Arc<AppState>>, user: CurrentUser) -> Response {
let quota: (i64, i64) = match state.storage_usage_service.as_ref() {
Some(service) => match service.get_user_storage_info(&user.id).await {
Some(service) => match service.get_user_storage_info(user.id).await {
Ok((used, total)) => (used, total),
Err(_) => (0, 0),
},
@@ -151,7 +151,7 @@ async fn user_provisioning_response(
// Fetch quota from storage usage service
let quota: (i64, i64) = match state.storage_usage_service.as_ref() {
Some(service) => match service.get_user_storage_info(&user_dto.id).await {
Some(service) => match service.get_user_storage_info(uuid::Uuid::parse_str(&user_dto.id).unwrap_or_default()).await {
Ok((used, total)) => (used, total),
Err(_) => (0, 0),
},
@@ -212,7 +212,7 @@ pub async fn handle_revoke_apppassword(
if let Err(e) = nextcloud
.app_passwords
.revoke_by_password(&user.id, &app_password)
.revoke_by_password(user.id, &app_password)
.await
{
tracing::warn!("Failed to revoke app password for {}: {}", user.id, e);
@@ -367,7 +367,7 @@ pub async fn handle_search(
..SearchCriteriaDto::default()
};
let results = match search_service.search(criteria, &user.id).await {
let results = match search_service.search(criteria, user.id).await {
Ok(r) => r,
Err(_) => return empty_search_response().into_response(),
};
+2 -1
View File
@@ -90,7 +90,8 @@ pub async fn handle_preview(
};
// Verify the authenticated user owns this file
if file.owner_id.as_deref() != Some(&user.id) {
let user_id_str = user.id.to_string();
if file.owner_id.as_deref() != Some(user_id_str.as_str()) {
return Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("File not found"))
+2 -2
View File
@@ -67,7 +67,7 @@ async fn handle_filter_files(
};
let favorites = fav_svc
.get_favorites(&user.id)
.get_favorites(user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to get favorites: {}", e)))?;
@@ -179,7 +179,7 @@ async fn handle_search(
};
let results = search_svc
.search(criteria, &user.id)
.search(criteria, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Search failed: {}", e)))?;
+4 -4
View File
@@ -75,7 +75,7 @@ async fn handle_propfind(
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
let items = trash_svc
.get_trash_items(&user.id)
.get_trash_items(user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to list trash: {}", e)))?;
@@ -109,7 +109,7 @@ async fn handle_restore(
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
trash_svc
.restore_item(&id, &user.id)
.restore_item(&id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to restore item: {}", e)))?;
@@ -131,7 +131,7 @@ async fn handle_empty_trash(
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
trash_svc
.empty_trash(&user.id)
.empty_trash(user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to empty trash: {}", e)))?;
@@ -156,7 +156,7 @@ async fn handle_delete_permanent(
.ok_or_else(|| AppError::internal_error("Trash service not available"))?;
trash_svc
.delete_permanently(&id, &user.id)
.delete_permanently(&id, user.id)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to permanently delete item: {}", e))
+10 -10
View File
@@ -193,7 +193,7 @@ async fn handle_propfind(
items.push((&sf.id, "folder"));
}
fav_svc
.batch_check_favorites(&user.id, &items)
.batch_check_favorites(user.id, &items)
.await
.unwrap_or_default()
} else {
@@ -234,7 +234,7 @@ async fn handle_propfind(
let favorite_ids = if let Some(fav_svc) = state.favorites_service.as_ref() {
let items: Vec<(&str, &str)> = vec![(&file.id, "file")];
fav_svc
.batch_check_favorites(&user.id, &items)
.batch_check_favorites(user.id, &items)
.await
.unwrap_or_default()
} else {
@@ -414,14 +414,14 @@ async fn handle_proppatch(
if let Some(fav_svc) = state.favorites_service.as_ref() {
if value == 1 {
fav_svc
.add_to_favorites(&user.id, &item_id, item_type)
.add_to_favorites(user.id, &item_id, item_type)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to add favorite: {}", e))
})?;
} else {
fav_svc
.remove_from_favorites(&user.id, &item_id, item_type)
.remove_from_favorites(user.id, &item_id, item_type)
.await
.map_err(|e| {
AppError::internal_error(format!("Failed to remove favorite: {}", e))
@@ -685,7 +685,7 @@ async fn handle_delete(
if let Some(trash_svc) = state.trash_service.as_ref() {
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
trash_svc
.move_to_trash(&folder.id, "folder", &user.id)
.move_to_trash(&folder.id, "folder", user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to trash folder: {}", e)))?;
return Ok(Response::builder()
@@ -695,7 +695,7 @@ async fn handle_delete(
}
if let Ok(file) = file_service.get_file_by_path(&internal_path).await {
trash_svc
.move_to_trash(&file.id, "file", &user.id)
.move_to_trash(&file.id, "file", user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to trash file: {}", e)))?;
return Ok(Response::builder()
@@ -711,7 +711,7 @@ async fn handle_delete(
if let Ok(folder) = folder_service.get_folder_by_path(&internal_path).await {
folder_service
.delete_folder(&folder.id, &user.id)
.delete_folder(&folder.id, user.id)
.await
.map_err(|e| AppError::internal_error(format!("Failed to delete folder: {}", e)))?;
@@ -835,7 +835,7 @@ async fn handle_move(
RenameFolderDto {
name: dest_name.to_string(),
},
&user.id,
user.id,
)
.await
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;
@@ -853,7 +853,7 @@ async fn handle_move(
MoveFolderDto {
parent_id: Some(dest_parent.id.clone()),
},
&user.id,
user.id,
)
.await
.map_err(|e| AppError::internal_error(format!("Move failed: {}", e)))?;
@@ -867,7 +867,7 @@ async fn handle_move(
RenameFolderDto {
name: dest_name.to_string(),
},
&user.id,
user.id,
)
.await
.map_err(|e| AppError::internal_error(format!("Rename failed: {}", e)))?;