refactoring hexagonal and clean architecture
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
})?;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod handlers;
|
||||
pub mod routes;
|
||||
|
||||
pub use routes::create_api_routes;
|
||||
pub use routes::create_api_routes;
|
||||
pub use routes::create_public_api_routes;
|
||||
+83
-605
@@ -1,192 +1,85 @@
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use axum::{
|
||||
routing::{get, post, put, delete},
|
||||
Router,
|
||||
extract::{State, Query, Path},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use tower_http::{
|
||||
compression::CompressionLayer,
|
||||
trace::TraceLayer,
|
||||
};
|
||||
use serde_json::json;
|
||||
use crate::common::config::AppConfig;
|
||||
use crate::common::di::AppState;
|
||||
use crate::interfaces::middleware::auth::CurrentUserId;
|
||||
|
||||
use crate::interfaces::middleware::cache::{HttpCache, start_cache_cleanup_task};
|
||||
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::file_service::FileService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::batch_operations::BatchOperationService;
|
||||
use crate::application::ports::trash_ports::TrashUseCase;
|
||||
use crate::application::ports::inbound::SearchUseCase;
|
||||
use crate::application::ports::share_ports::ShareUseCase;
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::application::ports::recent_ports::RecentItemsUseCase;
|
||||
|
||||
use crate::interfaces::api::handlers::folder_handler::FolderHandler;
|
||||
use crate::interfaces::api::handlers::file_handler::FileHandler;
|
||||
use crate::interfaces::api::handlers::i18n_handler::I18nHandler;
|
||||
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
||||
// Eliminamos la importación de ShareHandler ya que ahora usamos directamente el servicio
|
||||
use crate::interfaces::api::handlers::trash_handler;
|
||||
use crate::interfaces::api::handlers::batch_handler::{
|
||||
self, BatchHandlerState
|
||||
};
|
||||
use crate::application::dtos::pagination::PaginationRequestDto;
|
||||
|
||||
/// Creates API routes for the application
|
||||
pub fn create_api_routes(
|
||||
folder_service: Arc<FolderService>,
|
||||
file_service: Arc<FileService>,
|
||||
i18n_service: Option<Arc<I18nApplicationService>>,
|
||||
trash_service: Option<Arc<dyn TrashUseCase>>,
|
||||
search_service: Option<Arc<dyn SearchUseCase>>,
|
||||
share_service: Option<Arc<dyn ShareUseCase>>,
|
||||
favorites_service: Option<Arc<dyn FavoritesUseCase>>,
|
||||
recent_service: Option<Arc<dyn RecentItemsUseCase>>,
|
||||
) -> Router<crate::common::di::AppState> {
|
||||
// Create a simplified AppState for the trash view
|
||||
// Setup required components for repository construction
|
||||
let path_service = Arc::new(crate::infrastructure::services::path_service::PathService::new(std::path::PathBuf::from("./storage")));
|
||||
let storage_mediator = Arc::new(crate::application::services::storage_mediator::FileSystemStorageMediator::new_stub());
|
||||
let id_mapping_service = Arc::new(crate::infrastructure::services::id_mapping_service::IdMappingService::dummy());
|
||||
let path_resolver = Arc::new(crate::infrastructure::repositories::file_path_resolver::FilePathResolver::new(
|
||||
path_service.clone(),
|
||||
storage_mediator.clone(),
|
||||
id_mapping_service.clone()
|
||||
));
|
||||
let metadata_cache = Arc::new(crate::infrastructure::services::file_metadata_cache::FileMetadataCache::new(
|
||||
crate::common::config::AppConfig::default(),
|
||||
1000 // Default max entries
|
||||
));
|
||||
|
||||
// Create file and folder repositories
|
||||
let file_repository = Arc::new(crate::infrastructure::repositories::file_fs_repository::FileFsRepository::new(
|
||||
std::path::PathBuf::from("./storage"),
|
||||
storage_mediator.clone(),
|
||||
id_mapping_service.clone(),
|
||||
path_service.clone(),
|
||||
metadata_cache.clone(),
|
||||
));
|
||||
|
||||
let folder_repository = Arc::new(crate::infrastructure::repositories::folder_fs_repository::FolderFsRepository::new(
|
||||
std::path::PathBuf::from("./storage"),
|
||||
storage_mediator.clone(),
|
||||
id_mapping_service.clone(),
|
||||
path_service.clone(),
|
||||
));
|
||||
|
||||
// Create concrete id_mapping_service for optimizer
|
||||
let id_mapping_service_concrete = Arc::new(crate::infrastructure::services::id_mapping_service::IdMappingService::dummy());
|
||||
let id_mapping_optimizer = Arc::new(crate::infrastructure::services::id_mapping_optimizer::IdMappingOptimizer::new(id_mapping_service_concrete.clone()));
|
||||
|
||||
// Create dummy thumbnail service for routes
|
||||
let thumbnail_service = Arc::new(
|
||||
crate::infrastructure::services::thumbnail_service::ThumbnailService::new(
|
||||
&std::path::PathBuf::from("./storage"),
|
||||
100,
|
||||
10 * 1024 * 1024,
|
||||
)
|
||||
);
|
||||
|
||||
// Create dummy write-behind cache for routes
|
||||
let write_behind_cache = crate::infrastructure::services::write_behind_cache::WriteBehindCache::new();
|
||||
|
||||
// Create dummy chunked upload service for routes
|
||||
let chunked_upload_service = Arc::new(
|
||||
crate::infrastructure::services::chunked_upload_service::ChunkedUploadService::new(
|
||||
std::path::PathBuf::from("./storage/.uploads")
|
||||
)
|
||||
);
|
||||
|
||||
// Create dummy image transcode service for routes
|
||||
let image_transcode_service = Arc::new(
|
||||
crate::infrastructure::services::image_transcode_service::ImageTranscodeService::new(
|
||||
&std::path::PathBuf::from("./storage"),
|
||||
100,
|
||||
10 * 1024 * 1024,
|
||||
)
|
||||
);
|
||||
|
||||
// Create dummy dedup service for routes
|
||||
let dedup_service = Arc::new(
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(
|
||||
&std::path::PathBuf::from("./storage")
|
||||
)
|
||||
);
|
||||
|
||||
let mut app_state = crate::common::di::AppState {
|
||||
core: crate::common::di::CoreServices {
|
||||
path_service: path_service.clone(),
|
||||
cache_manager: Arc::new(crate::infrastructure::services::cache_manager::StorageCacheManager::default()),
|
||||
file_content_cache: Arc::new(crate::infrastructure::services::file_content_cache::FileContentCache::default()),
|
||||
id_mapping_service: id_mapping_service.clone(),
|
||||
file_id_mapping_service: id_mapping_service_concrete.clone(),
|
||||
id_mapping_optimizer: id_mapping_optimizer.clone(),
|
||||
thumbnail_service: thumbnail_service.clone(),
|
||||
write_behind_cache: write_behind_cache.clone(),
|
||||
chunked_upload_service: chunked_upload_service.clone(),
|
||||
image_transcode_service: image_transcode_service.clone(),
|
||||
dedup_service: dedup_service.clone(),
|
||||
config: crate::common::config::AppConfig::default(),
|
||||
},
|
||||
repositories: crate::common::di::RepositoryServices {
|
||||
folder_repository: folder_repository.clone(),
|
||||
file_repository: file_repository.clone(),
|
||||
file_read_repository: Arc::new(crate::infrastructure::repositories::FileFsReadRepository::default_stub()),
|
||||
file_write_repository: Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub()),
|
||||
i18n_repository: Arc::new(crate::infrastructure::services::file_system_i18n_service::FileSystemI18nService::dummy()),
|
||||
storage_mediator: storage_mediator.clone(),
|
||||
metadata_manager: Arc::new(crate::infrastructure::repositories::FileMetadataManager::default()),
|
||||
path_resolver: path_resolver.clone(),
|
||||
metadata_cache: metadata_cache.clone(),
|
||||
trash_repository: None, // This is OK to be None since we use the trash_service directly
|
||||
},
|
||||
storage_usage_service: None,
|
||||
applications: crate::common::di::ApplicationServices {
|
||||
folder_service_concrete: folder_service.clone(),
|
||||
file_service_concrete: file_service.clone(),
|
||||
folder_service: folder_service.clone(),
|
||||
file_service: file_service.clone(),
|
||||
file_upload_service: Arc::new(crate::application::services::file_upload_service::FileUploadService::new(
|
||||
Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
||||
)),
|
||||
file_retrieval_service: Arc::new(crate::application::services::file_retrieval_service::FileRetrievalService::new(
|
||||
Arc::new(crate::infrastructure::repositories::FileFsReadRepository::default_stub())
|
||||
)),
|
||||
file_management_service: Arc::new(crate::application::services::file_management_service::FileManagementService::new(
|
||||
Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
||||
)),
|
||||
file_use_case_factory: Arc::new(crate::application::services::file_use_case_factory::AppFileUseCaseFactory::new(
|
||||
Arc::new(crate::infrastructure::repositories::FileFsReadRepository::default_stub()),
|
||||
Arc::new(crate::infrastructure::repositories::FileFsWriteRepository::default_stub())
|
||||
)),
|
||||
i18n_service: i18n_service.clone().unwrap_or_else(||
|
||||
Arc::new(crate::application::services::i18n_application_service::I18nApplicationService::dummy())
|
||||
),
|
||||
trash_service: trash_service.clone(), // Include the trash service here too for consistency
|
||||
search_service: search_service.clone(), // Include the search service
|
||||
share_service: share_service.clone(), // Include the share service
|
||||
favorites_service: favorites_service.clone(), // Include the favorites service
|
||||
recent_service: recent_service.clone(), // Include the recent service
|
||||
},
|
||||
db_pool: None,
|
||||
auth_service: None,
|
||||
trash_service: trash_service.clone(), // This is the important part - include the trash service
|
||||
share_service: share_service.clone(), // Include the share service for routes
|
||||
favorites_service: favorites_service.clone(), // Include the favorites service for routes
|
||||
recent_service: recent_service.clone(), // Include the recent service for routes
|
||||
calendar_service: None, // Adding missing field
|
||||
contact_service: None // Adding missing field
|
||||
};
|
||||
/// Creates public API routes that should NOT require authentication.
|
||||
///
|
||||
/// Currently this includes:
|
||||
/// - `/s/{token}` — public access to shared items via share link
|
||||
/// - `/s/{token}/verify` — password verification for protected share links
|
||||
/// - `/i18n/*` — internationalization/translation endpoints
|
||||
pub fn create_public_api_routes(app_state: &AppState) -> Router<AppState> {
|
||||
let share_service = app_state.share_service.clone();
|
||||
let i18n_service = Some(app_state.applications.i18n_service.clone());
|
||||
|
||||
let mut router = Router::new();
|
||||
|
||||
// Public share access routes — no auth required
|
||||
if let Some(share_service) = share_service {
|
||||
use crate::interfaces::api::handlers::share_handler;
|
||||
|
||||
let public_share_router = Router::new()
|
||||
.route("/{token}", get(share_handler::access_shared_item))
|
||||
.route("/{token}/verify", post(share_handler::verify_shared_item_password))
|
||||
.with_state(share_service);
|
||||
|
||||
router = router.nest("/s", public_share_router);
|
||||
}
|
||||
|
||||
// i18n routes — no auth required (localization should be available before login)
|
||||
if let Some(i18n_service) = i18n_service {
|
||||
let i18n_router = Router::new()
|
||||
.route("/locales", get(I18nHandler::get_locales))
|
||||
.route("/translate", get(I18nHandler::translate))
|
||||
.route("/locales/{locale_code}", get(I18nHandler::get_translations_by_locale))
|
||||
.with_state(i18n_service);
|
||||
|
||||
router = router.nest("/i18n", i18n_router);
|
||||
}
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
/// Creates protected API routes for the application.
|
||||
///
|
||||
/// These routes require authentication when auth is enabled.
|
||||
/// Receives the fully-assembled `AppState` and extracts all needed services
|
||||
/// from it, avoiding a long parameter list.
|
||||
pub fn create_api_routes(app_state: &AppState) -> Router<AppState> {
|
||||
// Extract services from the pre-built AppState
|
||||
let folder_service = app_state.applications.folder_service_concrete.clone();
|
||||
let file_retrieval_service = app_state.applications.file_retrieval_service.clone();
|
||||
let file_management_service = app_state.applications.file_management_service.clone();
|
||||
let trash_service = app_state.trash_service.clone();
|
||||
let search_service = app_state.applications.search_service.clone();
|
||||
let share_service = app_state.share_service.clone();
|
||||
let favorites_service = app_state.favorites_service.clone();
|
||||
let recent_service = app_state.recent_service.clone();
|
||||
|
||||
// Inicializar el servicio de operaciones por lotes
|
||||
let batch_service = Arc::new(BatchOperationService::default(
|
||||
file_service.clone(),
|
||||
file_retrieval_service.clone(),
|
||||
file_management_service.clone(),
|
||||
folder_service.clone()
|
||||
));
|
||||
|
||||
@@ -209,33 +102,11 @@ pub fn create_api_routes(
|
||||
// Create the basic folders router with service operations
|
||||
let folders_basic_router = Router::new()
|
||||
.route("/", post(FolderHandler::create_folder))
|
||||
.route("/", get(|State(service): State<Arc<FolderService>>| async move {
|
||||
// No parent ID means list root folders
|
||||
FolderHandler::list_folders(State(service), None).await
|
||||
}))
|
||||
.route("/paginated", get(|
|
||||
State(service): State<Arc<FolderService>>,
|
||||
pagination: Query<PaginationRequestDto>
|
||||
| async move {
|
||||
// Paginación para carpetas raíz (sin parent)
|
||||
FolderHandler::list_folders_paginated(State(service), pagination, None).await
|
||||
}))
|
||||
.route("/", get(FolderHandler::list_root_folders))
|
||||
.route("/paginated", get(FolderHandler::list_root_folders_paginated))
|
||||
.route("/{id}", get(FolderHandler::get_folder))
|
||||
.route("/{id}/contents", get(|
|
||||
State(service): State<Arc<FolderService>>,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
// Listar contenido de una carpeta por su ID
|
||||
FolderHandler::list_folders(State(service), Some(&id)).await
|
||||
}))
|
||||
.route("/{id}/contents/paginated", get(|
|
||||
State(service): State<Arc<FolderService>>,
|
||||
Path(id): Path<String>,
|
||||
pagination: Query<PaginationRequestDto>
|
||||
| async move {
|
||||
// Listar contenido paginado de una carpeta por su ID
|
||||
FolderHandler::list_folders_paginated(State(service), pagination, Some(&id)).await
|
||||
}))
|
||||
.route("/{id}/contents", get(FolderHandler::list_folder_contents))
|
||||
.route("/{id}/contents/paginated", get(FolderHandler::list_folder_contents_paginated))
|
||||
.route("/{id}/rename", put(FolderHandler::rename_folder))
|
||||
.route("/{id}/move", put(FolderHandler::move_folder))
|
||||
.with_state(folder_service.clone());
|
||||
@@ -245,144 +116,25 @@ pub fn create_api_routes(
|
||||
.route("/{id}/download", get(FolderHandler::download_folder_zip))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
// Create folder operations that use trash separately
|
||||
// Create folder operations that use trash (requires full AppState)
|
||||
let folders_ops_router = Router::new()
|
||||
.route("/{id}", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
// Try to use trash service if available
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
tracing::info!("Moving folder to trash: {}", id);
|
||||
|
||||
match trash_service.move_to_trash(&id, "folder", &user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Folder successfully moved to trash: {}", id);
|
||||
return StatusCode::NO_CONTENT.into_response();
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::warn!("Could not move folder to trash, falling back to permanent delete: {}", err);
|
||||
// Fall through to regular delete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to permanent delete
|
||||
let folder_service = &state.applications.folder_service;
|
||||
match folder_service.delete_folder(&id).await {
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}));
|
||||
.route("/{id}", delete(FolderHandler::delete_folder_with_trash));
|
||||
|
||||
// Merge the routers
|
||||
let folders_router = folders_basic_router.merge(folders_ops_router).merge(folder_zip_router);
|
||||
|
||||
// Create file routes for basic operations and trash-enabled delete
|
||||
let basic_file_router = Router::new()
|
||||
.route("/", get(|
|
||||
State(state): State<AppState>,
|
||||
axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
|
||||
| async move {
|
||||
// Get folder_id from query parameter if present
|
||||
let folder_id = params.get("folder_id").map(|id| id.as_str());
|
||||
tracing::info!("API: Listando archivos con folder_id: {:?}", folder_id);
|
||||
let service = &state.applications.file_service_concrete;
|
||||
match service.list_files(folder_id).await {
|
||||
Ok(files) => {
|
||||
tracing::info!("Found {} files", files.len());
|
||||
(StatusCode::OK, Json(files)).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Error listing files: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
||||
"error": format!("Error listing files: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
}))
|
||||
.route("/upload", post(|
|
||||
State(state): State<AppState>,
|
||||
multipart: axum::extract::Multipart,
|
||||
| async move {
|
||||
use crate::infrastructure::services::thumbnail_service::ThumbnailService;
|
||||
|
||||
// Use the new upload handler with write-behind cache
|
||||
let response = FileHandler::upload_file_with_cache(
|
||||
State(state.clone()),
|
||||
multipart
|
||||
).await;
|
||||
|
||||
// Try to extract file info for thumbnail generation
|
||||
if let Ok(body_bytes) = axum::body::to_bytes(response.into_response().into_body(), 10 * 1024).await {
|
||||
if let Ok(file_info) = serde_json::from_slice::<serde_json::Value>(&body_bytes) {
|
||||
if let (Some(file_id), Some(mime_type), Some(file_path_str)) = (
|
||||
file_info.get("id").and_then(|v| v.as_str()),
|
||||
file_info.get("mime_type").and_then(|v| v.as_str()),
|
||||
file_info.get("path").and_then(|v| v.as_str())
|
||||
) {
|
||||
// Generate thumbnails for images in background
|
||||
if ThumbnailService::is_supported_image(mime_type) {
|
||||
let file_id = file_id.to_string();
|
||||
let file_path_rel = file_path_str.to_string();
|
||||
let thumbnail_service = state.core.thumbnail_service.clone();
|
||||
let path_service = state.core.path_service.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let file_path = path_service.get_root_path().join(&file_path_rel);
|
||||
tracing::info!("🖼️ Generating thumbnails for: {}", file_id);
|
||||
thumbnail_service.generate_all_sizes_background(file_id, file_path);
|
||||
});
|
||||
}
|
||||
|
||||
// Return the response
|
||||
return axum::http::Response::builder()
|
||||
.status(axum::http::StatusCode::CREATED)
|
||||
.header(axum::http::header::CONTENT_TYPE, "application/json")
|
||||
.header(axum::http::header::CACHE_CONTROL, "no-cache, no-store, must-revalidate")
|
||||
.body(axum::body::Body::from(body_bytes))
|
||||
.unwrap()
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for errors
|
||||
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Upload processing error").into_response()
|
||||
}))
|
||||
.route("/", get(FileHandler::list_files_query))
|
||||
.route("/upload", post(FileHandler::upload_file_with_thumbnails))
|
||||
.route("/{id}", get(FileHandler::download_file))
|
||||
.route("/{id}/thumbnail/{size}", get(FileHandler::get_thumbnail))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
// Let's create a router for file operations with trash support
|
||||
// File operations with trash support
|
||||
let file_operations_router = Router::new()
|
||||
// CRITICAL FIX: Ensure file deletion route correctly calls FileHandler::delete_file
|
||||
// Uses the correct URL pattern
|
||||
.route("/{id}", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
tracing::info!("File delete route called explicitly for ID: {}", id);
|
||||
FileHandler::delete_file(State(state), CurrentUserId(user_id), Path(id)).await
|
||||
}))
|
||||
.route("/{id}/move", put(|
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(payload): Json<serde_json::Value>,
|
||||
| async move {
|
||||
// Simplified move implementation just to get it working
|
||||
let folder_id = payload.get("folder_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let file_service = &state.applications.file_service;
|
||||
match file_service.move_file(&id, folder_id).await {
|
||||
Ok(file_dto) => (StatusCode::OK, Json(file_dto)).into_response(),
|
||||
Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}));
|
||||
.route("/{id}", delete(FileHandler::delete_file))
|
||||
.route("/{id}/move", put(FileHandler::move_file_simple));
|
||||
|
||||
// Merge the routers
|
||||
let files_router = basic_file_router.merge(file_operations_router);
|
||||
@@ -418,7 +170,7 @@ pub fn create_api_routes(
|
||||
|
||||
// Implementaciones directas de handlers para compartir, sin depender de ShareHandler
|
||||
|
||||
// Create routes for shared resources if the service is available
|
||||
// Create routes for shared resources management (requires auth)
|
||||
let share_router = if let Some(share_service) = share_service.clone() {
|
||||
use crate::interfaces::api::handlers::share_handler;
|
||||
|
||||
@@ -432,18 +184,6 @@ pub fn create_api_routes(
|
||||
} else {
|
||||
Router::new()
|
||||
};
|
||||
|
||||
// Public route for accessing shared links
|
||||
let public_share_router = if let Some(share_service) = share_service.clone() {
|
||||
use crate::interfaces::api::handlers::share_handler;
|
||||
|
||||
Router::new()
|
||||
.route("/{token}", get(share_handler::access_shared_item))
|
||||
.route("/{token}/verify", post(share_handler::verify_shared_item_password))
|
||||
.with_state(share_service.clone())
|
||||
} else {
|
||||
Router::new()
|
||||
};
|
||||
|
||||
// Create a router without the i18n routes
|
||||
// Create routes for favorites if the service is available
|
||||
@@ -500,236 +240,21 @@ pub fn create_api_routes(
|
||||
.nest("/batch", batch_router)
|
||||
.nest("/search", search_router)
|
||||
.nest("/shares", share_router)
|
||||
.nest("/s", public_share_router)
|
||||
.nest("/favorites", favorites_router)
|
||||
.nest("/recent", recent_router)
|
||||
;
|
||||
|
||||
// Store the share service in app_state for future use
|
||||
if let Some(share_service) = share_service.clone() {
|
||||
app_state.share_service = Some(share_service);
|
||||
}
|
||||
|
||||
// Re-enable trash routes to make the trash view work
|
||||
if let Some(_trash_service_ref) = trash_service.clone() {
|
||||
tracing::info!("Setting up trash routes for trash view");
|
||||
|
||||
// Create a router for trash specific endpoints that handles the auth requirements
|
||||
// Implement all trash operations needed by the frontend
|
||||
let trash_router = Router::new()
|
||||
// Get all trash items
|
||||
.route("/", get(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Query(params): Query<HashMap<String, String>>
|
||||
| async move {
|
||||
tracing::info!("Getting trash items");
|
||||
// Use a valid UUID for the default user or from query params
|
||||
let effective_user = params.get("userId")
|
||||
.cloned()
|
||||
.unwrap_or(user_id);
|
||||
|
||||
tracing::info!("Using user ID: {}", effective_user);
|
||||
// Get the trash service directly
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
// Get trash items for default user
|
||||
match trash_service.get_trash_items(&effective_user).await {
|
||||
Ok(items) => {
|
||||
tracing::info!("Found {} items in trash", items.len());
|
||||
let response_data = serde_json::json!(items);
|
||||
tracing::info!("Response data: {:?}", response_data);
|
||||
(StatusCode::OK, Json(response_data)).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Error getting trash items: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error getting trash items: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
// Move file to trash
|
||||
.route("/files/{id}", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
tracing::info!("Moving file to trash: {}", id);
|
||||
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
match trash_service.move_to_trash(&id, "file", &user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("File moved to trash successfully");
|
||||
(StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "File moved to trash successfully"
|
||||
}))).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Error moving file to trash: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error moving file to trash: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
// Move folder to trash
|
||||
.route("/folders/{id}", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
tracing::info!("Moving folder to trash: {}", id);
|
||||
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
match trash_service.move_to_trash(&id, "folder", &user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Folder moved to trash successfully");
|
||||
(StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Folder moved to trash successfully"
|
||||
}))).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Error moving folder to trash: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error moving folder to trash: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
// Restore item from trash
|
||||
.route("/{id}/restore", post(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
tracing::info!("Restoring item from trash: {}", id);
|
||||
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
match trash_service.restore_item(&id, &user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Item restored from trash successfully");
|
||||
(StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Item restored from trash successfully"
|
||||
}))).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
let err_str = format!("{}", err);
|
||||
// Check if the error is due to item not being found
|
||||
if err_str.contains("not found") || err_str.contains("NotFound") {
|
||||
tracing::warn!("Item not found in trash, but reporting success: {}", id);
|
||||
// Return success even if the item is not found
|
||||
return (StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Item restored (or was already removed from trash)"
|
||||
}))).into_response();
|
||||
}
|
||||
|
||||
tracing::error!("Error restoring item from trash: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error restoring item from trash: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
// Permanently delete an item from trash
|
||||
.route("/{id}", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
Path(id): Path<String>
|
||||
| async move {
|
||||
tracing::info!("Permanently deleting item from trash: {}", id);
|
||||
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
match trash_service.delete_permanently(&id, &user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Item permanently deleted successfully");
|
||||
(StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Item permanently deleted"
|
||||
}))).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
let err_str = format!("{}", err);
|
||||
// Check if the error is due to item not being found
|
||||
if err_str.contains("not found") || err_str.contains("NotFound") {
|
||||
tracing::warn!("Item not found in trash, but reporting success: {}", id);
|
||||
// Return success even if the item is not found
|
||||
return (StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Item deleted (or was already removed from trash)"
|
||||
}))).into_response();
|
||||
}
|
||||
|
||||
tracing::error!("Error permanently deleting item: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error permanently deleting item: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
// Empty trash
|
||||
.route("/empty", delete(|
|
||||
State(state): State<AppState>,
|
||||
CurrentUserId(user_id): CurrentUserId,
|
||||
| async move {
|
||||
tracing::info!("Emptying trash");
|
||||
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
match trash_service.empty_trash(&user_id).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Trash emptied successfully");
|
||||
(StatusCode::OK, Json(json!({
|
||||
"success": true,
|
||||
"message": "Trash emptied successfully"
|
||||
}))).into_response()
|
||||
},
|
||||
Err(err) => {
|
||||
tracing::error!("Error emptying trash: {}", err);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": format!("Error emptying trash: {}", err)
|
||||
}))).into_response()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Trash service not available");
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(json!({
|
||||
"error": "Trash feature is not enabled"
|
||||
}))).into_response()
|
||||
}
|
||||
}))
|
||||
.route("/", get(trash_handler::get_trash_items))
|
||||
.route("/files/{id}", delete(trash_handler::move_file_to_trash))
|
||||
.route("/folders/{id}", delete(trash_handler::move_folder_to_trash))
|
||||
.route("/{id}/restore", post(trash_handler::restore_from_trash))
|
||||
.route("/{id}", delete(trash_handler::delete_permanently))
|
||||
.route("/empty", delete(trash_handler::empty_trash))
|
||||
.with_state(app_state.clone());
|
||||
|
||||
router = router.nest("/trash", trash_router);
|
||||
@@ -737,66 +262,19 @@ pub fn create_api_routes(
|
||||
tracing::warn!("Trash service not available - trash view will not work");
|
||||
}
|
||||
|
||||
// Add i18n routes if the service is provided
|
||||
if let Some(i18n_service) = i18n_service {
|
||||
let i18n_router = Router::new()
|
||||
.route("/locales", get(I18nHandler::get_locales))
|
||||
.route("/translate", get(I18nHandler::translate))
|
||||
.route("/locales/{locale_code}", get(|
|
||||
State(service): State<Arc<I18nApplicationService>>,
|
||||
axum::extract::Path(locale_code): axum::extract::Path<String>,
|
||||
| async move {
|
||||
I18nHandler::get_translations(State(service), locale_code).await
|
||||
}))
|
||||
.with_state(i18n_service);
|
||||
|
||||
router = router.nest("/i18n", i18n_router);
|
||||
}
|
||||
|
||||
// Get the app configuration
|
||||
let _config = AppConfig::from_env();
|
||||
|
||||
// For now, just use the router as is - we'll properly implement the auth middleware later
|
||||
// when all implementation details are fixed
|
||||
let router = router;
|
||||
|
||||
// Apply compression and tracing layers
|
||||
// Note: We've removed the direct trash endpoints due to handler type compatibility issues
|
||||
// These will need to be implemented directly in main.rs or by modifying the file/folder handlers
|
||||
if trash_service.is_some() {
|
||||
tracing::info!("Trash service is available - trash view is functional");
|
||||
}
|
||||
|
||||
// Add WebDAV routes if needed
|
||||
let webdav_enabled = true; // In production, you'd read this from a config
|
||||
let router = if webdav_enabled {
|
||||
// Add WebDAV routes
|
||||
{
|
||||
use crate::interfaces::api::handlers::webdav_handler;
|
||||
router.merge(webdav_handler::webdav_routes())
|
||||
} else {
|
||||
router
|
||||
};
|
||||
router = router.merge(webdav_handler::webdav_routes());
|
||||
}
|
||||
|
||||
// Add CalDAV routes if needed
|
||||
let caldav_enabled = true; // In production, you'd read this from a config
|
||||
let router = if caldav_enabled {
|
||||
// Add CalDAV routes
|
||||
{
|
||||
use crate::interfaces::api::handlers::caldav_handler;
|
||||
router.nest("/caldav", caldav_handler::caldav_routes())
|
||||
} else {
|
||||
router
|
||||
};
|
||||
|
||||
// Add CardDAV routes if needed
|
||||
let carddav_enabled = true; // In production, you'd read this from a config
|
||||
let router = if carddav_enabled {
|
||||
// Note: We'll implement carddav_handler in the next phase
|
||||
router
|
||||
} else {
|
||||
router
|
||||
};
|
||||
router = router.nest("/caldav", caldav_handler::caldav_routes());
|
||||
}
|
||||
|
||||
router
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
// HTTP caching is disabled temporarily due to compatibility issues
|
||||
// .layer(HttpCacheLayer::new(http_cache.clone()).with_max_age(folders_ttl))
|
||||
}
|
||||
Reference in New Issue
Block a user