refactoring hexagonal and clean architecture

This commit is contained in:
Diocrafts
2026-02-08 13:40:23 +01:00
parent 3e4fb67c19
commit a82faa5eaf
101 changed files with 7433 additions and 9721 deletions
+2 -87
View File
@@ -9,7 +9,7 @@ use axum::{
use crate::common::di::AppState;
use crate::application::dtos::user_dto::{
LoginDto, RegisterDto, UserDto, ChangePasswordDto, RefreshTokenDto, AuthResponseDto
LoginDto, RegisterDto, ChangePasswordDto, RefreshTokenDto
};
use crate::interfaces::errors::AppError;
@@ -51,29 +51,6 @@ async fn register(
}
};
// Create a temporary mock response for testing
// This is a fallback solution to bypass database issues
if cfg!(debug_assertions) && dto.username == "test" {
tracing::info!("Using test registration, bypassing database");
// Create a mock user response
let now = chrono::Utc::now();
let mock_user = UserDto {
id: "test-user-id".to_string(),
username: dto.username.clone(),
email: dto.email.clone(),
role: "user".to_string(),
active: true,
storage_quota_bytes: 1024 * 1024 * 1024, // 1GB
storage_used_bytes: 0,
created_at: now,
updated_at: now,
last_login_at: None,
};
return Ok((StatusCode::CREATED, Json(mock_user)));
}
// Check if this is a fresh install
tracing::info!("New user registration detected, checking if it's a fresh install");
@@ -153,8 +130,6 @@ async fn login(
// Add detailed logging for debugging
tracing::info!("Login attempt for user: {}", dto.username);
// Normal login process
// Verify auth service exists
let auth_service = match state.auth_service.as_ref() {
Some(service) => {
@@ -167,35 +142,6 @@ async fn login(
}
};
// Create a temporary mock response for testing
// This is a fallback solution to bypass database issues
if cfg!(debug_assertions) && dto.username == "test" && dto.password == "test" {
tracing::info!("Using test credentials, bypassing database");
// Create a mock response
let now = chrono::Utc::now();
let mock_response = AuthResponseDto {
user: UserDto {
id: "test-user-id".to_string(),
username: dto.username.clone(),
email: format!("{}@example.com", dto.username),
role: "user".to_string(),
active: true,
storage_quota_bytes: 1024 * 1024 * 1024, // 1GB
storage_used_bytes: 0,
created_at: now,
updated_at: now,
last_login_at: None,
},
access_token: "mock_access_token".to_string(),
refresh_token: "mock_refresh_token".to_string(),
token_type: "Bearer".to_string(),
expires_in: 3600,
};
return Ok((StatusCode::OK, Json(mock_response)));
}
// Try the normal login process
match auth_service.auth_application_service.login(dto.clone()).await {
Ok(auth_response) => {
@@ -226,38 +172,7 @@ async fn refresh_token(
// Check if this refresh token is being used too frequently
// Log the refresh attempt for debugging
tracing::info!("Token refresh requested with refresh token: {}",
dto.refresh_token.chars().take(8).collect::<String>() + "...");
// Handle test/mock tokens with simplified response
if dto.refresh_token.contains("mock") || dto.refresh_token == "mock_refresh_token" {
tracing::info!("Mock refresh token detected, returning simplified response");
// Create a mock response that will work with our frontend
let now = chrono::Utc::now();
let mock_user = UserDto {
id: "test-user-id".to_string(),
username: "test".to_string(),
email: "test@example.com".to_string(),
role: "user".to_string(),
active: true,
storage_quota_bytes: 1024 * 1024 * 1024, // 1GB
storage_used_bytes: 0,
created_at: now,
updated_at: now,
last_login_at: None,
};
let auth_response = AuthResponseDto {
user: mock_user,
access_token: "mock_access_token_new".to_string(),
refresh_token: "mock_refresh_token_new".to_string(),
token_type: "Bearer".to_string(),
expires_in: 86400 * 30, // 30 days
};
return Ok((StatusCode::OK, Json(auth_response)));
}
tracing::info!("Token refresh requested");
// Normal process for real tokens
let auth_service = state.auth_service.as_ref()
+49 -24
View File
@@ -9,6 +9,7 @@ use std::sync::Arc;
use serde_json::json;
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::AuthUser;
use crate::application::dtos::address_book_dto::{
AddressBookDto, CreateAddressBookDto, UpdateAddressBookDto,
ShareAddressBookDto, UnshareAddressBookDto
@@ -83,8 +84,9 @@ pub fn carddav_routes() -> Router<AppState> {
// Address Book handlers
async fn list_address_books(
State(state): State<AppState>,
auth_user: AuthUser,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -142,9 +144,10 @@ async fn create_address_book(
async fn get_address_book(
State(state): State<AppState>,
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -176,10 +179,11 @@ async fn get_address_book(
async fn update_address_book(
State(state): State<AppState>,
auth_user: AuthUser,
Path(id): Path<String>,
Json(mut update): Json<UpdateAddressBookDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
update.user_id = user_id.to_string();
match &state.contact_service {
@@ -214,9 +218,10 @@ async fn update_address_book(
async fn delete_address_book(
State(state): State<AppState>,
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -246,9 +251,10 @@ async fn delete_address_book(
async fn get_address_book_shares(
State(state): State<AppState>,
auth_user: AuthUser,
Path(id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -278,10 +284,11 @@ async fn get_address_book_shares(
async fn share_address_book(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
Json(mut dto): Json<ShareAddressBookDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
dto.address_book_id = address_book_id;
match &state.contact_service {
@@ -314,9 +321,10 @@ async fn share_address_book(
async fn unshare_address_book(
State(state): State<AppState>,
auth_user: AuthUser,
Path((address_book_id, shared_with)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -354,9 +362,10 @@ async fn unshare_address_book(
// Contact handlers
async fn list_contacts(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -388,10 +397,11 @@ async fn list_contacts(
async fn search_contacts(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
let query = params.get("q").unwrap_or(&String::new()).to_string();
match &state.contact_service {
@@ -425,10 +435,11 @@ async fn search_contacts(
async fn create_contact(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
Json(mut dto): Json<CreateContactDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
dto.address_book_id = address_book_id;
dto.user_id = user_id.to_string();
@@ -457,10 +468,11 @@ async fn create_contact(
async fn create_contact_from_vcard(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
Json(mut dto): Json<CreateContactVCardDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
dto.address_book_id = address_book_id;
dto.user_id = user_id.to_string();
@@ -489,9 +501,10 @@ async fn create_contact_from_vcard(
async fn get_contact(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, contact_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -523,10 +536,11 @@ async fn get_contact(
async fn update_contact(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, contact_id)): Path<(String, String)>,
Json(mut update): Json<UpdateContactDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
update.user_id = user_id.to_string();
match &state.contact_service {
@@ -561,9 +575,10 @@ async fn update_contact(
async fn delete_contact(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, contact_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -593,9 +608,10 @@ async fn delete_contact(
async fn get_contact_vcard(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, contact_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -639,9 +655,10 @@ async fn get_contact_vcard(
// Group handlers
async fn list_groups(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -673,10 +690,11 @@ async fn list_groups(
async fn create_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path(address_book_id): Path<String>,
Json(mut dto): Json<CreateContactGroupDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
dto.address_book_id = address_book_id;
dto.user_id = user_id.to_string();
@@ -705,9 +723,10 @@ async fn create_group(
async fn get_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, group_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -739,10 +758,11 @@ async fn get_group(
async fn update_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, group_id)): Path<(String, String)>,
Json(mut update): Json<UpdateContactGroupDto>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
update.user_id = user_id.to_string();
match &state.contact_service {
@@ -777,9 +797,10 @@ async fn update_group(
async fn delete_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, group_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -809,9 +830,10 @@ async fn delete_group(
async fn list_contacts_in_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((_, group_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -843,9 +865,10 @@ async fn list_contacts_in_group(
async fn add_contact_to_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((group_id, contact_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -882,9 +905,10 @@ async fn add_contact_to_group(
async fn remove_contact_from_group(
State(state): State<AppState>,
auth_user: AuthUser,
Path((group_id, contact_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -921,9 +945,10 @@ async fn remove_contact_from_group(
async fn list_groups_for_contact(
State(state): State<AppState>,
auth_user: AuthUser,
Path(contact_id): Path<String>,
) -> impl IntoResponse {
let user_id = "default_user"; // In production, get this from auth middleware
let user_id = &auth_user.id;
match &state.contact_service {
Some(contact_service) => {
@@ -18,7 +18,8 @@ use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::common::di::AppState;
use crate::infrastructure::services::chunked_upload_service::DEFAULT_CHUNK_SIZE;
use crate::application::ports::chunked_upload_ports::DEFAULT_CHUNK_SIZE;
use crate::domain::errors::ErrorKind;
/// Request body for creating an upload session
#[derive(Debug, Deserialize)]
@@ -115,7 +116,7 @@ impl ChunkedUploadHandler {
Err(e) => {
tracing::error!("Failed to create upload session: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
"error": e
"error": e.to_string()
}))).into_response()
}
}
@@ -166,18 +167,15 @@ impl ChunkedUploadHandler {
.into_response()
}
Err(e) => {
let status = if e.contains("not found") {
StatusCode::NOT_FOUND
} else if e.contains("Invalid") || e.contains("already uploaded") {
StatusCode::BAD_REQUEST
} else if e.contains("Checksum") {
StatusCode::CONFLICT
} else {
StatusCode::INTERNAL_SERVER_ERROR
let status = match e.kind {
ErrorKind::NotFound => StatusCode::NOT_FOUND,
ErrorKind::InvalidInput => StatusCode::BAD_REQUEST,
ErrorKind::AlreadyExists => StatusCode::CONFLICT,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
(status, Json(serde_json::json!({
"error": e
"error": e.to_string()
}))).into_response()
}
}
@@ -208,7 +206,7 @@ impl ChunkedUploadHandler {
}
Err(e) => {
(StatusCode::NOT_FOUND, Json(serde_json::json!({
"error": e
"error": e.to_string()
}))).into_response()
}
}
@@ -222,23 +220,21 @@ impl ChunkedUploadHandler {
Path(upload_id): Path<String>,
) -> impl IntoResponse {
let chunked_service = &state.core.chunked_upload_service;
let file_service = &state.applications.file_service_concrete;
let upload_service = &state.applications.file_upload_service;
// Assemble chunks
let (assembled_path, filename, folder_id, content_type, total_size) =
match chunked_service.complete_upload(&upload_id).await {
Ok(result) => result,
Err(e) => {
let status = if e.contains("not found") {
StatusCode::NOT_FOUND
} else if e.contains("not complete") {
StatusCode::CONFLICT
} else {
StatusCode::INTERNAL_SERVER_ERROR
let status = match e.kind {
ErrorKind::NotFound => StatusCode::NOT_FOUND,
ErrorKind::InvalidInput | ErrorKind::AlreadyExists => StatusCode::CONFLICT,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
return (status, Json(serde_json::json!({
"error": e
"error": e.to_string()
}))).into_response();
}
};
@@ -255,7 +251,7 @@ impl ChunkedUploadHandler {
};
// Upload via normal service (this handles path resolution, metadata, etc.)
match file_service.upload_file_from_bytes(
match upload_service.upload_file(
filename.clone(),
folder_id.clone(),
content_type,
@@ -299,7 +295,7 @@ impl ChunkedUploadHandler {
Ok(_) => StatusCode::NO_CONTENT.into_response(),
Err(e) => {
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
"error": e
"error": e.to_string()
}))).into_response()
}
}
+3 -3
View File
@@ -8,7 +8,7 @@ use bytes::Bytes;
use serde::Serialize;
use crate::common::di::AppState;
use crate::infrastructure::services::dedup_service::DedupResult;
use crate::application::ports::dedup_ports::DedupResultDto;
/// Global application state for dependency injection
type GlobalState = AppState;
@@ -183,8 +183,8 @@ impl DedupHandler {
match dedup.store_bytes(&data, Some(content_type)).await {
Ok(result) => {
let (is_new, bytes_saved) = match &result {
DedupResult::NewBlob { .. } => (true, 0),
DedupResult::ExistingBlob { saved_bytes, .. } => (false, *saved_bytes),
DedupResultDto::NewBlob { .. } => (true, 0),
DedupResultDto::ExistingBlob { saved_bytes, .. } => (false, *saved_bytes),
};
let metadata = dedup.get_blob_metadata(result.hash()).await;
File diff suppressed because it is too large Load Diff
+34 -7
View File
@@ -14,7 +14,6 @@ use crate::common::errors::ErrorKind;
use crate::application::ports::inbound::FolderUseCase;
use crate::common::di::AppState as GlobalAppState;
use crate::interfaces::middleware::auth::AuthUser;
use crate::infrastructure::services::zip_service::ZipService;
type AppState = Arc<FolderService>;
@@ -59,6 +58,38 @@ impl FolderHandler {
}
}
/// Lists root folders (no parent ID)
pub async fn list_root_folders(
State(service): State<AppState>,
) -> impl IntoResponse {
Self::list_folders(State(service), None).await
}
/// Lists contents of a specific folder by its ID
pub async fn list_folder_contents(
State(service): State<AppState>,
Path(id): Path<String>,
) -> impl IntoResponse {
Self::list_folders(State(service), Some(&id)).await
}
/// Lists root folders with pagination support
pub async fn list_root_folders_paginated(
State(service): State<AppState>,
pagination: Query<PaginationRequestDto>,
) -> impl IntoResponse {
Self::list_folders_paginated(State(service), pagination, None).await
}
/// Lists contents of a specific folder with pagination
pub async fn list_folder_contents_paginated(
State(service): State<AppState>,
Path(id): Path<String>,
pagination: Query<PaginationRequestDto>,
) -> impl IntoResponse {
Self::list_folders_paginated(State(service), pagination, Some(&id)).await
}
/// Lists folders, optionally filtered by parent ID
pub async fn list_folders(
State(service): State<AppState>,
@@ -226,17 +257,13 @@ impl FolderHandler {
// Get folder information first to check it exists and get name
let folder_service = &state.applications.folder_service;
let file_service = &state.applications.file_service;
match folder_service.get_folder(&id).await {
Ok(folder) => {
tracing::info!("Preparing ZIP for folder: {} ({})", folder.name, id);
// Create ZIP service with the required services
let zip_service = ZipService::new(
file_service.clone(),
folder_service.clone()
);
// Use ZIP service from DI container
let zip_service = &state.core.zip_service;
// Create the ZIP file
match zip_service.create_folder_zip(&id, &folder.name).await {
+9 -1
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use axum::{
extract::{State, Query},
extract::{State, Query, Path},
http::StatusCode,
response::IntoResponse,
Json,
@@ -75,6 +75,14 @@ impl I18nHandler {
}
}
/// Gets all translations for a locale (Axum-compatible: extracts locale from path)
pub async fn get_translations_by_locale(
State(service): State<AppState>,
Path(locale_code): Path<String>,
) -> impl IntoResponse {
Self::get_translations(State(service), locale_code).await
}
/// Gets all translations for a locale
pub async fn get_translations(
State(_service): State<AppState>,
+5 -4
View File
@@ -15,6 +15,7 @@ use crate::{
ports::share_ports::ShareUseCase
},
common::errors::ErrorKind,
interfaces::middleware::auth::AuthUser,
};
#[derive(Debug, Deserialize)]
@@ -31,10 +32,10 @@ pub struct VerifyPasswordRequest {
/// Create a new shared link
pub async fn create_shared_link(
State(share_use_case): State<Arc<dyn ShareUseCase>>,
auth_user: AuthUser,
Json(dto): Json<CreateShareDto>,
) -> impl IntoResponse {
// For now, we'll use a default user ID until auth is implemented
let user_id = "default-user";
let user_id = &auth_user.id;
match share_use_case.create_shared_link(&user_id, dto).await {
Ok(share) => (StatusCode::CREATED, Json(share)).into_response(),
Err(err) => {
@@ -68,10 +69,10 @@ pub async fn get_shared_link(
/// Get all shared links created by the current user
pub async fn get_user_shares(
State(share_use_case): State<Arc<dyn ShareUseCase>>,
auth_user: AuthUser,
Query(query): Query<GetSharesQuery>,
) -> impl IntoResponse {
// For now, we'll use a default user ID until auth is implemented
let user_id = "default-user";
let user_id = &auth_user.id;
let page = query.page.unwrap_or(1);
let per_page = query.per_page.unwrap_or(20);
+28 -3
View File
@@ -2,7 +2,7 @@ use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::Json;
use serde_json::json;
use tracing::{debug, error, instrument};
use tracing::{debug, error, warn, instrument};
// use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::di::AppState;
@@ -14,7 +14,12 @@ pub async fn get_trash_items(
State(state): State<AppState>,
auth_user: AuthUser,
) -> (StatusCode, Json<serde_json::Value>) {
debug!("Solicitud para listar elementos en papelera para usuario {}", auth_user.id);
// 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();
debug!("Solicitud para listar elementos en papelera para usuario {}", effective_user);
let trash_service = match state.trash_service.as_ref() {
Some(service) => service,
@@ -25,7 +30,7 @@ pub async fn get_trash_items(
}
};
let result = trash_service.get_trash_items(&auth_user.id).await;
let result = trash_service.get_trash_items(&effective_user).await;
match result {
Ok(items) => {
@@ -184,6 +189,16 @@ pub async fn restore_from_trash(
})))
},
Err(e) => {
let err_str = format!("{}", e);
// If item not found, report success (it was already restored or removed)
if err_str.contains("not found") || err_str.contains("NotFound") {
warn!("Item not found in trash, but reporting success: {}", trash_id);
return (StatusCode::OK, Json(json!({
"success": true,
"message": "Item restored (or was already removed from trash)"
})));
}
error!("Error al restaurar elemento de papelera: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
"error": format!("Error restoring item from trash: {}", e)
@@ -220,6 +235,16 @@ pub async fn delete_permanently(
})))
},
Err(e) => {
let err_str = format!("{}", e);
// If item not found, report success (it was already deleted)
if err_str.contains("not found") || err_str.contains("NotFound") {
warn!("Item not found in trash, but reporting success: {}", trash_id);
return (StatusCode::OK, Json(json!({
"success": true,
"message": "Item deleted (or was already removed from trash)"
})));
}
error!("Error al eliminar permanentemente elemento: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
"error": format!("Error deleting item permanently: {}", e)
+37 -57
View File
@@ -171,7 +171,7 @@ async fn handle_propfind(
// Get folder service from state
let folder_service = &state.applications.folder_service;
let file_service = &state.applications.file_service;
let file_retrieval_service = &state.applications.file_retrieval_service;
// Determine base HREF
let base_href = format!("/webdav/{}/", path);
@@ -183,7 +183,7 @@ async fn handle_propfind(
AppError::internal_error(format!("Failed to get subfolders: {}", e))
})?;
let files = file_service.list_files(None).await.map_err(|e| {
let files = file_retrieval_service.list_files(None).await.map_err(|e| {
AppError::internal_error(format!("Failed to get files: {}", e))
})?;
@@ -224,7 +224,7 @@ async fn handle_propfind(
if let Ok(folder) = folder_result {
// Path is a folder
let files = if depth != "0" {
file_service.list_files(Some(&folder.id)).await.map_err(|e| {
file_retrieval_service.list_files(Some(&folder.id)).await.map_err(|e| {
AppError::internal_error(format!("Failed to get files: {}", e))
})?
} else {
@@ -260,7 +260,7 @@ async fn handle_propfind(
.unwrap())
} else {
// Check if path is a file
let file_result = file_service.get_file_by_path(&path).await;
let file_result = file_retrieval_service.get_file_by_path(&path).await;
if let Ok(file) = file_result {
// Path is a file
@@ -395,7 +395,6 @@ async fn handle_get(
})?;
// Get file service from state
let file_service = &state.applications.file_service;
let file_retrieval_service = &state.applications.file_retrieval_service;
// Check if path is empty (root folder)
@@ -404,7 +403,7 @@ async fn handle_get(
}
// Get file metadata
let file = file_service.get_file_by_path(&path).await.map_err(|_e| {
let file = file_retrieval_service.get_file_by_path(&path).await.map_err(|_e| {
AppError::not_found(format!("File not found: {}", path))
})?;
@@ -467,7 +466,7 @@ async fn handle_put(
};
// Get file service from state
let file_service = &state.applications.file_service;
let file_upload_service = &state.applications.file_upload_service;
// Check if path is empty (root folder)
if path.is_empty() || path == "/" {
@@ -475,7 +474,7 @@ async fn handle_put(
}
// Extract content type before consuming the request
let content_type = req.headers()
let _content_type = req.headers()
.get(header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream")
@@ -495,38 +494,19 @@ async fn handle_put(
};
// Check if file exists
let file_exists = file_service.get_file_by_path(&path).await.is_ok();
let file_exists = file_upload_service.update_file(&path, &body_bytes).await;
if file_exists {
// Update existing file
file_service.update_file(&path, &body_bytes).await.map_err(|e| {
AppError::internal_error(format!("Failed to update file: {}", e))
})?;
Ok(Response::builder()
.status(StatusCode::NO_CONTENT)
.body(Body::empty())
.unwrap())
} else {
// Create new file
// Extract filename from path
let filename = path.split('/').last().unwrap_or("unnamed");
// Get parent folder path
let parent_path = if let Some(idx) = path.rfind('/') {
&path[..idx]
} else {
""
};
file_service.create_file(parent_path, filename, &body_bytes, &content_type).await.map_err(|e| {
AppError::internal_error(format!("Failed to create file: {}", e))
})?;
Ok(Response::builder()
.status(StatusCode::CREATED)
.body(Body::empty())
.unwrap())
match file_exists {
Ok(_) => {
// update_file handles both update and create-if-not-found
Ok(Response::builder()
.status(StatusCode::NO_CONTENT)
.body(Body::empty())
.unwrap())
}
Err(e) => {
Err(AppError::internal_error(format!("Failed to put file: {}", e)))
}
}
}
@@ -658,7 +638,8 @@ async fn handle_delete(
})?;
// Get services from state
let file_service = &state.applications.file_service;
let file_retrieval_service = &state.applications.file_retrieval_service;
let file_management_service = &state.applications.file_management_service;
let folder_service = &state.applications.folder_service;
// Check if path is empty (root folder)
@@ -676,11 +657,11 @@ async fn handle_delete(
})?;
} else {
// Try to delete file
let file = file_service.get_file_by_path(&path).await.map_err(|_e| {
let file = file_retrieval_service.get_file_by_path(&path).await.map_err(|_e| {
AppError::not_found(format!("Resource not found: {}", path))
})?;
file_service.delete_file(&file.id).await.map_err(|e| {
file_management_service.delete_file(&file.id).await.map_err(|e| {
AppError::internal_error(format!("Failed to delete file: {}", e))
})?;
}
@@ -736,7 +717,8 @@ async fn handle_move(
};
// Get services from state
let file_service = &state.applications.file_service;
let file_retrieval_service = &state.applications.file_retrieval_service;
let file_management_service = &state.applications.file_management_service;
let folder_service = &state.applications.folder_service;
// Check if source is a folder
@@ -778,7 +760,7 @@ async fn handle_move(
}
} else {
// Try to move file
let file = file_service.get_file_by_path(&source_path).await.map_err(|_e| {
let file = file_retrieval_service.get_file_by_path(&source_path).await.map_err(|_e| {
AppError::not_found(format!("Resource not found: {}", source_path))
})?;
@@ -788,7 +770,7 @@ async fn handle_move(
""
};
file_service.move_file(&file.id, Some(dest_parent_path.to_string())).await.map_err(|e| {
file_management_service.move_file(&file.id, Some(dest_parent_path.to_string())).await.map_err(|e| {
AppError::internal_error(format!("Failed to move file: {}", e))
})?;
}
@@ -850,9 +832,9 @@ async fn handle_copy(
.unwrap_or("infinity");
// Get services from state
let file_service = &state.applications.file_service;
let folder_service = &state.applications.folder_service;
let file_retrieval_service = &state.applications.file_retrieval_service;
let file_upload_service = &state.applications.file_upload_service;
let folder_service = &state.applications.folder_service;
// Check if source is a folder
let folder_result = folder_service.get_folder_by_path(&source_path).await;
@@ -889,25 +871,23 @@ async fn handle_copy(
if recursive {
// Copy subfolders and files (simplified implementation)
let files = file_service.list_files(Some(&folder.id)).await.map_err(|e| {
let files = file_retrieval_service.list_files(Some(&folder.id)).await.map_err(|e| {
AppError::internal_error(format!("Failed to list files: {}", e))
})?;
for file in files {
// Get file content
if let Ok(file_source) = file_service.get_file_by_path(&format!("{}/{}", source_path, file.name)).await {
if let Ok(content) = file_retrieval_service.get_file_content(&file_source.id).await {
// Create new file in destination
file_service.create_file(&destination_path, &file.name, &content, &file.mime_type).await.map_err(|e| {
AppError::internal_error(format!("Failed to copy file {}: {}", file.name, e))
})?;
}
if let Ok(content) = file_retrieval_service.get_file_content(&file.id).await {
// Create new file in destination
file_upload_service.create_file(&destination_path, &file.name, &content, &file.mime_type).await.map_err(|e| {
AppError::internal_error(format!("Failed to copy file {}: {}", file.name, e))
})?;
}
}
}
} else {
// Try to copy file
let file = file_service.get_file_by_path(&source_path).await.map_err(|_e| {
let file = file_retrieval_service.get_file_by_path(&source_path).await.map_err(|_e| {
AppError::not_found(format!("Resource not found: {}", source_path))
})?;
@@ -925,7 +905,7 @@ async fn handle_copy(
};
// Create new file in destination
file_service.create_file(dest_parent_path, dest_filename, &content, &file.mime_type).await.map_err(|e| {
file_upload_service.create_file(dest_parent_path, dest_filename, &content, &file.mime_type).await.map_err(|e| {
AppError::internal_error(format!("Failed to copy file: {}", e))
})?;
}