2025-03-17 21:28:08 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
use axum::{
|
|
|
|
|
routing::{get, post, put, delete},
|
|
|
|
|
Router,
|
2026-02-08 22:44:42 +01:00
|
|
|
response::Json as AxumJson,
|
2025-03-17 21:28:08 +01:00
|
|
|
};
|
2026-02-08 22:44:42 +01:00
|
|
|
use serde_json::json;
|
2025-03-19 00:44:27 +01:00
|
|
|
use tower_http::{
|
|
|
|
|
compression::CompressionLayer,
|
|
|
|
|
trace::TraceLayer,
|
|
|
|
|
};
|
2025-03-24 17:49:53 +01:00
|
|
|
use crate::common::di::AppState;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
2026-02-08 22:44:42 +01:00
|
|
|
/// Returns the application version from Cargo.toml (compile-time constant)
|
|
|
|
|
async fn get_version() -> AxumJson<serde_json::Value> {
|
|
|
|
|
AxumJson(json!({
|
|
|
|
|
"name": "OxiCloud",
|
|
|
|
|
"version": env!("CARGO_PKG_VERSION")
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
use crate::interfaces::middleware::cache::{HttpCache, start_cache_cleanup_task};
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
use crate::application::services::batch_operations::BatchOperationService;
|
|
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
use crate::interfaces::api::handlers::folder_handler::FolderHandler;
|
|
|
|
|
use crate::interfaces::api::handlers::file_handler::FileHandler;
|
|
|
|
|
use crate::interfaces::api::handlers::i18n_handler::I18nHandler;
|
2026-02-03 17:59:04 +01:00
|
|
|
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
2026-02-08 13:40:23 +01:00
|
|
|
use crate::interfaces::api::handlers::trash_handler;
|
2025-03-19 00:44:27 +01:00
|
|
|
use crate::interfaces::api::handlers::batch_handler::{
|
|
|
|
|
self, BatchHandlerState
|
|
|
|
|
};
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
/// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-08 22:44:42 +01:00
|
|
|
// Version endpoint — public, no auth required
|
|
|
|
|
router = router.route("/version", get(get_version));
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
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();
|
|
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Inicializar el servicio de operaciones por lotes
|
|
|
|
|
let batch_service = Arc::new(BatchOperationService::default(
|
2026-02-08 13:40:23 +01:00
|
|
|
file_retrieval_service.clone(),
|
|
|
|
|
file_management_service.clone(),
|
2025-03-19 00:44:27 +01:00
|
|
|
folder_service.clone()
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
// Crear estado para el manejador de operaciones por lotes
|
|
|
|
|
let batch_handler_state = BatchHandlerState {
|
|
|
|
|
batch_service: batch_service.clone(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Implement HTTP Cache
|
|
|
|
|
let http_cache = HttpCache::new();
|
|
|
|
|
|
|
|
|
|
// Define TTL values for different resource types (in seconds)
|
|
|
|
|
let _folders_ttl = 300; // 5 minutes
|
|
|
|
|
let _files_list_ttl = 300; // 5 minutes
|
|
|
|
|
let _i18n_ttl = 3600; // 1 hour
|
|
|
|
|
|
|
|
|
|
// Start the cleanup task for HTTP cache
|
|
|
|
|
start_cache_cleanup_task(http_cache.clone());
|
|
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
// Create the basic folders router with service operations
|
|
|
|
|
let folders_basic_router = Router::new()
|
2025-03-17 21:28:08 +01:00
|
|
|
.route("/", post(FolderHandler::create_folder))
|
2026-02-08 13:40:23 +01:00
|
|
|
.route("/", get(FolderHandler::list_root_folders))
|
|
|
|
|
.route("/paginated", get(FolderHandler::list_root_folders_paginated))
|
2025-03-17 21:28:08 +01:00
|
|
|
.route("/{id}", get(FolderHandler::get_folder))
|
2026-02-08 13:40:23 +01:00
|
|
|
.route("/{id}/contents", get(FolderHandler::list_folder_contents))
|
|
|
|
|
.route("/{id}/contents/paginated", get(FolderHandler::list_folder_contents_paginated))
|
2025-03-17 21:28:08 +01:00
|
|
|
.route("/{id}/rename", put(FolderHandler::rename_folder))
|
|
|
|
|
.route("/{id}/move", put(FolderHandler::move_folder))
|
2025-03-26 18:33:22 +01:00
|
|
|
.with_state(folder_service.clone());
|
|
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Special route for ZIP download that requires AppState instead of just FolderService
|
|
|
|
|
let folder_zip_router = Router::new()
|
|
|
|
|
.route("/{id}/download", get(FolderHandler::download_folder_zip))
|
|
|
|
|
.with_state(app_state.clone());
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Create folder operations that use trash (requires full AppState)
|
2025-03-26 18:33:22 +01:00
|
|
|
let folders_ops_router = Router::new()
|
2026-02-08 13:40:23 +01:00
|
|
|
.route("/{id}", delete(FolderHandler::delete_folder_with_trash));
|
2025-03-26 18:33:22 +01:00
|
|
|
|
|
|
|
|
// Merge the routers
|
2025-04-02 01:22:05 +02:00
|
|
|
let folders_router = folders_basic_router.merge(folders_ops_router).merge(folder_zip_router);
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
// Create file routes for basic operations and trash-enabled delete
|
|
|
|
|
let basic_file_router = Router::new()
|
2026-02-08 13:40:23 +01:00
|
|
|
.route("/", get(FileHandler::list_files_query))
|
|
|
|
|
.route("/upload", post(FileHandler::upload_file_with_thumbnails))
|
2025-03-17 21:28:08 +01:00
|
|
|
.route("/{id}", get(FileHandler::download_file))
|
2026-02-03 17:59:04 +01:00
|
|
|
.route("/{id}/thumbnail/{size}", get(FileHandler::get_thumbnail))
|
|
|
|
|
.with_state(app_state.clone());
|
2025-03-26 18:33:22 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// File operations with trash support
|
2025-03-26 18:33:22 +01:00
|
|
|
let file_operations_router = Router::new()
|
2026-02-08 13:40:23 +01:00
|
|
|
.route("/{id}", delete(FileHandler::delete_file))
|
2026-02-08 22:44:42 +01:00
|
|
|
.route("/{id}/move", put(FileHandler::move_file_simple))
|
|
|
|
|
.route("/{id}/rename", put(FileHandler::rename_file));
|
2025-03-26 18:33:22 +01:00
|
|
|
|
|
|
|
|
// Merge the routers
|
|
|
|
|
let files_router = basic_file_router.merge(file_operations_router);
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2025-03-19 00:44:27 +01:00
|
|
|
// Crear rutas para operaciones por lotes
|
|
|
|
|
let batch_router = Router::new()
|
|
|
|
|
// Operaciones de archivos
|
|
|
|
|
.route("/files/move", post(batch_handler::move_files_batch))
|
|
|
|
|
.route("/files/copy", post(batch_handler::copy_files_batch))
|
|
|
|
|
.route("/files/delete", post(batch_handler::delete_files_batch))
|
|
|
|
|
.route("/files/get", post(batch_handler::get_files_batch))
|
|
|
|
|
// Operaciones de carpetas
|
|
|
|
|
.route("/folders/delete", post(batch_handler::delete_folders_batch))
|
|
|
|
|
.route("/folders/create", post(batch_handler::create_folders_batch))
|
|
|
|
|
.route("/folders/get", post(batch_handler::get_folders_batch))
|
|
|
|
|
.with_state(batch_handler_state);
|
|
|
|
|
|
2025-03-27 01:13:34 +01:00
|
|
|
// Create search routes if the service is available
|
|
|
|
|
let search_router = if search_service.is_some() {
|
|
|
|
|
use crate::interfaces::api::handlers::search_handler::SearchHandler;
|
|
|
|
|
|
|
|
|
|
Router::new()
|
|
|
|
|
// Simple search with query parameters
|
|
|
|
|
.route("/", get(SearchHandler::search_files_get))
|
|
|
|
|
// Advanced search with full criteria object
|
|
|
|
|
.route("/advanced", post(SearchHandler::search_files_post))
|
|
|
|
|
// Clear search cache
|
|
|
|
|
.route("/cache", delete(SearchHandler::clear_search_cache))
|
|
|
|
|
.with_state(app_state.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Router::new()
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-28 12:38:48 +01:00
|
|
|
// Implementaciones directas de handlers para compartir, sin depender de ShareHandler
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Create routes for shared resources management (requires auth)
|
2025-03-28 12:38:48 +01:00
|
|
|
let share_router = if let Some(share_service) = share_service.clone() {
|
|
|
|
|
use crate::interfaces::api::handlers::share_handler;
|
|
|
|
|
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/", post(share_handler::create_shared_link))
|
|
|
|
|
.route("/", get(share_handler::get_user_shares))
|
|
|
|
|
.route("/{id}", get(share_handler::get_shared_link))
|
|
|
|
|
.route("/{id}", put(share_handler::update_shared_link))
|
|
|
|
|
.route("/{id}", delete(share_handler::delete_shared_link))
|
|
|
|
|
.with_state(share_service.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Router::new()
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
// Create a router without the i18n routes
|
2025-04-02 03:43:44 +02:00
|
|
|
// Create routes for favorites if the service is available
|
|
|
|
|
let favorites_router = if let Some(favorites_service) = favorites_service.clone() {
|
|
|
|
|
use crate::interfaces::api::handlers::favorites_handler;
|
|
|
|
|
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/", get(favorites_handler::get_favorites))
|
|
|
|
|
.route("/{item_type}/{item_id}", post(favorites_handler::add_favorite))
|
|
|
|
|
.route("/{item_type}/{item_id}", delete(favorites_handler::remove_favorite))
|
|
|
|
|
.with_state(favorites_service.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Router::new()
|
|
|
|
|
};
|
2025-04-02 05:08:30 +02:00
|
|
|
|
|
|
|
|
// Create routes for recent items if the service is available
|
|
|
|
|
let recent_router = if let Some(recent_service) = recent_service.clone() {
|
|
|
|
|
use crate::interfaces::api::handlers::recent_handler;
|
|
|
|
|
|
|
|
|
|
Router::new()
|
|
|
|
|
.route("/", get(recent_handler::get_recent_items))
|
|
|
|
|
.route("/{item_type}/{item_id}", post(recent_handler::record_item_access))
|
|
|
|
|
.route("/{item_type}/{item_id}", delete(recent_handler::remove_from_recent))
|
|
|
|
|
.route("/clear", delete(recent_handler::clear_recent_items))
|
|
|
|
|
.with_state(recent_service.clone())
|
|
|
|
|
} else {
|
|
|
|
|
Router::new()
|
|
|
|
|
};
|
2026-02-03 17:59:04 +01:00
|
|
|
|
|
|
|
|
// Create routes for chunked uploads (large files >10MB)
|
|
|
|
|
let chunked_upload_router = Router::new()
|
|
|
|
|
.route("/", post(ChunkedUploadHandler::create_upload))
|
|
|
|
|
.route("/{upload_id}", axum::routing::patch(ChunkedUploadHandler::upload_chunk))
|
|
|
|
|
.route("/{upload_id}", axum::routing::head(ChunkedUploadHandler::get_upload_status))
|
|
|
|
|
.route("/{upload_id}/complete", post(ChunkedUploadHandler::complete_upload))
|
|
|
|
|
.route("/{upload_id}", delete(ChunkedUploadHandler::cancel_upload))
|
|
|
|
|
.with_state(Arc::new(app_state.clone()));
|
|
|
|
|
|
|
|
|
|
// Create routes for deduplication endpoints
|
|
|
|
|
let dedup_router = Router::new()
|
|
|
|
|
.route("/check/{hash}", get(super::handlers::dedup_handler::DedupHandler::check_hash))
|
|
|
|
|
.route("/upload", post(super::handlers::dedup_handler::DedupHandler::upload_with_dedup))
|
|
|
|
|
.route("/stats", get(super::handlers::dedup_handler::DedupHandler::get_stats))
|
|
|
|
|
.route("/blob/{hash}", get(super::handlers::dedup_handler::DedupHandler::get_blob))
|
|
|
|
|
.route("/blob/{hash}", delete(super::handlers::dedup_handler::DedupHandler::remove_reference))
|
|
|
|
|
.route("/recalculate", post(super::handlers::dedup_handler::DedupHandler::recalculate_stats))
|
|
|
|
|
.with_state(app_state.clone());
|
2025-04-02 03:43:44 +02:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
let mut router = Router::new()
|
|
|
|
|
.nest("/folders", folders_router)
|
2025-03-19 00:44:27 +01:00
|
|
|
.nest("/files", files_router)
|
2026-02-03 17:59:04 +01:00
|
|
|
.nest("/uploads", chunked_upload_router)
|
|
|
|
|
.nest("/dedup", dedup_router)
|
2025-03-27 01:13:34 +01:00
|
|
|
.nest("/batch", batch_router)
|
2025-03-28 12:38:48 +01:00
|
|
|
.nest("/search", search_router)
|
|
|
|
|
.nest("/shares", share_router)
|
2025-04-02 03:43:44 +02:00
|
|
|
.nest("/favorites", favorites_router)
|
2025-04-02 05:08:30 +02:00
|
|
|
.nest("/recent", recent_router)
|
2025-03-28 12:38:48 +01:00
|
|
|
;
|
2025-03-24 16:47:42 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
// Re-enable trash routes to make the trash view work
|
2025-03-28 06:15:09 +01:00
|
|
|
if let Some(_trash_service_ref) = trash_service.clone() {
|
2025-03-26 18:33:22 +01:00
|
|
|
tracing::info!("Setting up trash routes for trash view");
|
|
|
|
|
|
|
|
|
|
let trash_router = Router::new()
|
2026-02-08 13:40:23 +01:00
|
|
|
.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))
|
2025-03-26 18:33:22 +01:00
|
|
|
.with_state(app_state.clone());
|
|
|
|
|
|
|
|
|
|
router = router.nest("/trash", trash_router);
|
|
|
|
|
} else {
|
|
|
|
|
tracing::warn!("Trash service not available - trash view will not work");
|
|
|
|
|
}
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Add WebDAV routes
|
|
|
|
|
{
|
2025-04-04 21:31:41 +02:00
|
|
|
use crate::interfaces::api::handlers::webdav_handler;
|
2026-02-08 13:40:23 +01:00
|
|
|
router = router.merge(webdav_handler::webdav_routes());
|
|
|
|
|
}
|
2025-04-13 01:04:04 +02:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Add CalDAV routes
|
|
|
|
|
{
|
2025-04-13 01:04:04 +02:00
|
|
|
use crate::interfaces::api::handlers::caldav_handler;
|
2026-02-08 13:40:23 +01:00
|
|
|
router = router.nest("/caldav", caldav_handler::caldav_routes());
|
|
|
|
|
}
|
2025-03-26 18:33:22 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
router
|
|
|
|
|
.layer(CompressionLayer::new())
|
|
|
|
|
.layer(TraceLayer::new_for_http())
|
|
|
|
|
}
|