2025-03-17 21:28:08 +01:00
|
|
|
use axum::{
|
2026-02-14 12:45:46 +01:00
|
|
|
Json,
|
2026-02-14 20:22:19 +01:00
|
|
|
extract::{Path, Query, State},
|
|
|
|
|
http::{HeaderName, HeaderValue, Response, StatusCode, header},
|
|
|
|
|
response::IntoResponse,
|
2025-03-17 21:28:08 +01:00
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::sync::Arc;
|
2025-03-17 21:28:08 +01:00
|
|
|
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::dtos::folder_dto::{CreateFolderDto, MoveFolderDto, RenameFolderDto};
|
2025-03-19 00:44:27 +01:00
|
|
|
use crate::application::dtos::pagination::PaginationRequestDto;
|
|
|
|
|
use crate::application::ports::inbound::FolderUseCase;
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::application::services::folder_service::FolderService;
|
2025-03-26 18:33:22 +01:00
|
|
|
use crate::common::di::AppState as GlobalAppState;
|
2026-02-14 20:22:19 +01:00
|
|
|
use crate::common::errors::ErrorKind;
|
|
|
|
|
use crate::interfaces::middleware::auth::{AuthUser, OptionalAuthUser};
|
2025-03-17 21:28:08 +01:00
|
|
|
|
|
|
|
|
type AppState = Arc<FolderService>;
|
|
|
|
|
|
|
|
|
|
/// Handler for folder-related API endpoints
|
|
|
|
|
pub struct FolderHandler;
|
|
|
|
|
|
|
|
|
|
impl FolderHandler {
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Creates a new folder.
|
2026-02-14 12:45:46 +01:00
|
|
|
/// When parent_id is not provided, the folder is created inside the
|
2026-02-15 23:45:11 +01:00
|
|
|
/// authenticated user's home folder rather than at the storage root.
|
2025-03-17 21:28:08 +01:00
|
|
|
pub async fn create_folder(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-14 12:45:46 +01:00
|
|
|
auth_user: AuthUser,
|
|
|
|
|
Json(mut dto): Json<CreateFolderDto>,
|
2025-03-17 21:28:08 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-02-14 12:45:46 +01:00
|
|
|
// If no parent_id was supplied, resolve the user's home folder as
|
|
|
|
|
// the default parent so the new folder is nested correctly.
|
|
|
|
|
if dto.parent_id.is_none() {
|
|
|
|
|
tracing::info!(
|
2026-02-15 23:45:11 +01:00
|
|
|
"create_folder: parent_id is None for user '{}', resolving home folder",
|
|
|
|
|
auth_user.username
|
2026-02-14 12:45:46 +01:00
|
|
|
);
|
2026-02-15 23:45:11 +01:00
|
|
|
match service.list_folders_for_owner(None, &auth_user.id).await {
|
2026-02-14 12:45:46 +01:00
|
|
|
Ok(folders) => {
|
2026-02-15 23:45:11 +01:00
|
|
|
if let Some(home) = folders.first() {
|
2026-02-14 12:45:46 +01:00
|
|
|
tracing::info!(
|
|
|
|
|
"create_folder: resolved home folder ID '{}' for user '{}'",
|
2026-02-14 20:22:19 +01:00
|
|
|
home.id,
|
|
|
|
|
auth_user.username
|
2026-02-14 12:45:46 +01:00
|
|
|
);
|
|
|
|
|
dto.parent_id = Some(home.id.clone());
|
|
|
|
|
} else {
|
|
|
|
|
tracing::warn!(
|
2026-02-15 23:45:11 +01:00
|
|
|
"create_folder: home folder not found for user '{}', folder will be created at root",
|
|
|
|
|
auth_user.username
|
2026-02-14 12:45:46 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
2026-02-14 20:22:19 +01:00
|
|
|
tracing::error!(
|
|
|
|
|
"create_folder: failed to list folders for home resolution: {}",
|
|
|
|
|
e
|
|
|
|
|
);
|
2026-02-14 12:45:46 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
match service.create_folder(dto).await {
|
|
|
|
|
Ok(folder) => (StatusCode::CREATED, Json(folder)).into_response(),
|
|
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::AlreadyExists => StatusCode::CONFLICT,
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
(status, err.to_string()).into_response()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Gets a folder by ID.
|
|
|
|
|
/// Validates that the authenticated user owns the folder.
|
2025-03-17 21:28:08 +01:00
|
|
|
pub async fn get_folder(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-15 23:45:11 +01:00
|
|
|
auth_user: AuthUser,
|
2025-03-17 21:28:08 +01:00
|
|
|
Path(id): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
match service.get_folder(&id).await {
|
2026-02-15 23:45:11 +01:00
|
|
|
Ok(folder) => {
|
|
|
|
|
// Access check: folder must belong to the requesting user
|
|
|
|
|
if let Some(ref owner) = folder.owner_id {
|
|
|
|
|
if owner != &auth_user.id {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
"get_folder: user '{}' attempted to access folder '{}' owned by '{}'",
|
|
|
|
|
auth_user.id, id, owner
|
|
|
|
|
);
|
|
|
|
|
return (StatusCode::NOT_FOUND, "Folder not found".to_string()).into_response();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(StatusCode::OK, Json(folder)).into_response()
|
|
|
|
|
}
|
2025-03-17 21:28:08 +01:00
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
(status, err.to_string()).into_response()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Lists root folders for the authenticated user.
|
|
|
|
|
/// Only returns folders owned by this user — no information disclosure.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub async fn list_root_folders(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-13 22:31:05 +01:00
|
|
|
auth_user: AuthUser,
|
2026-02-13 23:00:16 +01:00
|
|
|
) -> axum::response::Response {
|
2026-02-15 23:45:11 +01:00
|
|
|
Self::list_folders_scoped(service, None, &auth_user).await
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Lists contents of a specific folder by its ID.
|
|
|
|
|
/// Scoped to the authenticated user's folders.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub async fn list_folder_contents(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-15 23:45:11 +01:00
|
|
|
auth_user: AuthUser,
|
2026-02-08 13:40:23 +01:00
|
|
|
Path(id): Path<String>,
|
2026-02-13 23:00:16 +01:00
|
|
|
) -> axum::response::Response {
|
2026-02-15 23:45:11 +01:00
|
|
|
Self::list_folders_scoped(service, Some(&id), &auth_user).await
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Lists root folders with pagination support.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub async fn list_root_folders_paginated(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-13 22:31:05 +01:00
|
|
|
auth_user: AuthUser,
|
|
|
|
|
_pagination: Query<PaginationRequestDto>,
|
2026-02-13 23:00:16 +01:00
|
|
|
) -> axum::response::Response {
|
2026-02-15 23:45:11 +01:00
|
|
|
Self::list_folders_scoped(service, None, &auth_user).await
|
2026-02-08 13:40:23 +01:00
|
|
|
}
|
|
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Lists contents of a specific folder with pagination.
|
2026-02-08 13:40:23 +01:00
|
|
|
pub async fn list_folder_contents_paginated(
|
|
|
|
|
State(service): State<AppState>,
|
2026-02-15 23:45:11 +01:00
|
|
|
_auth_user: AuthUser,
|
2026-02-08 13:40:23 +01:00
|
|
|
Path(id): Path<String>,
|
|
|
|
|
pagination: Query<PaginationRequestDto>,
|
2026-02-13 23:00:16 +01:00
|
|
|
) -> axum::response::Response {
|
2026-02-15 23:45:11 +01:00
|
|
|
// For sub-folder pagination, use the standard paginated path
|
|
|
|
|
// (owner filtering is implicit — sub-folders inherit ownership)
|
|
|
|
|
match service.list_folders_paginated(Some(&id), &pagination).await {
|
|
|
|
|
Ok(paginated_result) => (StatusCode::OK, Json(paginated_result)).into_response(),
|
2025-03-17 21:28:08 +01:00
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
|
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
(
|
|
|
|
|
status,
|
2026-02-15 23:45:11 +01:00
|
|
|
Json(serde_json::json!({ "error": err.to_string() })),
|
2026-02-14 20:22:19 +01:00
|
|
|
)
|
|
|
|
|
.into_response()
|
2026-02-13 22:31:05 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-15 23:45:11 +01:00
|
|
|
/// Internal helper: lists folders scoped to the authenticated user.
|
|
|
|
|
/// Uses `list_folders_for_owner` — the DB query filters by `user_id`,
|
|
|
|
|
/// so no data from other users ever leaves the database.
|
|
|
|
|
async fn list_folders_scoped(
|
2026-02-13 23:00:16 +01:00
|
|
|
service: AppState,
|
2026-02-13 22:31:05 +01:00
|
|
|
parent_id: Option<&str>,
|
|
|
|
|
auth_user: &AuthUser,
|
2026-02-13 23:00:16 +01:00
|
|
|
) -> axum::response::Response {
|
2026-02-15 23:45:11 +01:00
|
|
|
match service.list_folders_for_owner(parent_id, &auth_user.id).await {
|
|
|
|
|
Ok(folders) => (StatusCode::OK, Json(folders)).into_response(),
|
2025-03-19 00:44:27 +01:00
|
|
|
Err(err) => {
|
|
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
(
|
|
|
|
|
status,
|
2026-02-15 23:45:11 +01:00
|
|
|
Json(serde_json::json!({ "error": err.to_string() })),
|
2026-02-14 20:22:19 +01:00
|
|
|
)
|
|
|
|
|
.into_response()
|
2025-03-17 21:28:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
/// Renames a folder
|
|
|
|
|
pub async fn rename_folder(
|
|
|
|
|
State(service): State<AppState>,
|
|
|
|
|
Path(id): Path<String>,
|
|
|
|
|
Json(dto): Json<RenameFolderDto>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
match service.rename_folder(&id, dto).await {
|
|
|
|
|
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
|
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
|
|
|
|
ErrorKind::AlreadyExists => StatusCode::CONFLICT,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
// Return a proper JSON error response
|
2026-02-14 20:22:19 +01:00
|
|
|
(
|
|
|
|
|
status,
|
|
|
|
|
Json(serde_json::json!({
|
|
|
|
|
"error": err.to_string()
|
|
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
2025-03-17 21:28:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
/// Moves a folder to a new parent
|
|
|
|
|
pub async fn move_folder(
|
|
|
|
|
State(service): State<AppState>,
|
|
|
|
|
Path(id): Path<String>,
|
|
|
|
|
Json(dto): Json<MoveFolderDto>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
match service.move_folder(&id, dto).await {
|
|
|
|
|
Ok(folder) => (StatusCode::OK, Json(folder)).into_response(),
|
|
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
|
|
|
|
ErrorKind::AlreadyExists => StatusCode::CONFLICT,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
(status, err.to_string()).into_response()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
/// Deletes a folder (with trash support)
|
2025-03-17 21:28:08 +01:00
|
|
|
pub async fn delete_folder(
|
|
|
|
|
State(service): State<AppState>,
|
|
|
|
|
Path(id): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-03-26 18:33:22 +01:00
|
|
|
// For folder deletion without trash functionality
|
2025-03-17 21:28:08 +01:00
|
|
|
match service.delete_folder(&id).await {
|
|
|
|
|
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
|
|
|
|
Err(err) => {
|
2025-03-19 00:44:27 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
2025-03-17 21:28:08 +01:00
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
(status, err.to_string()).into_response()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
/// Deletes a folder with trash functionality
|
|
|
|
|
pub async fn delete_folder_with_trash(
|
|
|
|
|
State(state): State<GlobalAppState>,
|
2026-02-13 08:54:51 +01:00
|
|
|
OptionalAuthUser(auth_user): OptionalAuthUser,
|
2025-03-26 18:33:22 +01:00
|
|
|
Path(id): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
2026-02-14 20:22:19 +01:00
|
|
|
let user_id = auth_user
|
|
|
|
|
.as_ref()
|
|
|
|
|
.map(|u| u.id.as_str())
|
|
|
|
|
.unwrap_or("anonymous");
|
2025-03-26 18:33:22 +01:00
|
|
|
// Check if trash service is available
|
|
|
|
|
if let Some(trash_service) = &state.trash_service {
|
|
|
|
|
tracing::info!("Moving folder to trash: {}", id);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
// Try to move to trash first
|
2026-02-13 08:54:51 +01:00
|
|
|
match trash_service.move_to_trash(&id, "folder", user_id).await {
|
2025-03-26 18:33:22 +01:00
|
|
|
Ok(_) => {
|
|
|
|
|
tracing::info!("Folder successfully moved to trash: {}", id);
|
|
|
|
|
return StatusCode::NO_CONTENT.into_response();
|
2026-02-14 20:22:19 +01:00
|
|
|
}
|
2025-03-26 18:33:22 +01:00
|
|
|
Err(err) => {
|
2026-02-14 20:22:19 +01:00
|
|
|
tracing::warn!(
|
|
|
|
|
"Could not move folder to trash, falling back to permanent delete: {}",
|
|
|
|
|
err
|
|
|
|
|
);
|
2025-03-26 18:33:22 +01:00
|
|
|
// Fall through to regular delete if trash fails
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
// Fallback to permanent delete if trash is unavailable or failed
|
|
|
|
|
let folder_service = &state.applications.folder_service;
|
|
|
|
|
match folder_service.delete_folder(&id).await {
|
|
|
|
|
Ok(_) => {
|
|
|
|
|
tracing::info!("Folder permanently deleted: {}", id);
|
|
|
|
|
StatusCode::NO_CONTENT.into_response()
|
2026-02-14 20:22:19 +01:00
|
|
|
}
|
2025-03-26 18:33:22 +01:00
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!("Error deleting folder: {}", err);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-03-26 18:33:22 +01:00
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
|
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
|
|
|
|
(
|
|
|
|
|
status,
|
|
|
|
|
Json(serde_json::json!({
|
|
|
|
|
"error": format!("Error deleting folder: {}", err)
|
|
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
2025-03-26 18:33:22 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
/// Downloads a folder as a ZIP file
|
|
|
|
|
pub async fn download_folder_zip(
|
|
|
|
|
State(state): State<GlobalAppState>,
|
|
|
|
|
Path(id): Path<String>,
|
|
|
|
|
Query(_params): Query<HashMap<String, String>>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
tracing::info!("Downloading folder as ZIP: {}", id);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Get folder information first to check it exists and get name
|
|
|
|
|
let folder_service = &state.applications.folder_service;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
match folder_service.get_folder(&id).await {
|
|
|
|
|
Ok(folder) => {
|
|
|
|
|
tracing::info!("Preparing ZIP for folder: {} ({})", folder.name, id);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
// Use ZIP service from DI container
|
|
|
|
|
let zip_service = &state.core.zip_service;
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Create the ZIP file
|
|
|
|
|
match zip_service.create_folder_zip(&id, &folder.name).await {
|
|
|
|
|
Ok(zip_data) => {
|
2026-02-14 20:22:19 +01:00
|
|
|
tracing::info!(
|
|
|
|
|
"ZIP file created successfully, size: {} bytes",
|
|
|
|
|
zip_data.len()
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Setup headers for download
|
|
|
|
|
let filename = format!("{}.zip", folder.name);
|
|
|
|
|
let content_disposition = format!("attachment; filename=\"{}\"", filename);
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Build response with the ZIP data
|
|
|
|
|
let mut headers = HashMap::new();
|
2026-02-14 20:22:19 +01:00
|
|
|
headers.insert(
|
|
|
|
|
header::CONTENT_TYPE.to_string(),
|
|
|
|
|
"application/zip".to_string(),
|
|
|
|
|
);
|
|
|
|
|
headers
|
|
|
|
|
.insert(header::CONTENT_DISPOSITION.to_string(), content_disposition);
|
|
|
|
|
headers.insert(
|
|
|
|
|
header::CONTENT_LENGTH.to_string(),
|
|
|
|
|
zip_data.len().to_string(),
|
|
|
|
|
);
|
|
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Build the response
|
|
|
|
|
let mut response = Response::builder()
|
|
|
|
|
.status(StatusCode::OK)
|
|
|
|
|
.body(axum::body::Body::from(zip_data))
|
|
|
|
|
.unwrap();
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
// Add headers to response
|
|
|
|
|
for (name, value) in headers {
|
|
|
|
|
response.headers_mut().insert(
|
|
|
|
|
HeaderName::from_bytes(name.as_bytes()).unwrap(),
|
2026-02-14 20:22:19 +01:00
|
|
|
HeaderValue::from_str(&value).unwrap(),
|
2025-04-02 01:22:05 +02:00
|
|
|
);
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
|
2025-04-02 01:22:05 +02:00
|
|
|
response
|
2026-02-14 20:22:19 +01:00
|
|
|
}
|
2025-04-02 01:22:05 +02:00
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!("Error creating ZIP file: {}", err);
|
2026-02-14 20:22:19 +01:00
|
|
|
(
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(serde_json::json!({
|
|
|
|
|
"error": format!("Error creating ZIP file: {}", err)
|
|
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
2025-04-02 01:22:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
}
|
2025-04-02 01:22:05 +02:00
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!("Folder not found: {}", err);
|
|
|
|
|
let status = match err.kind {
|
|
|
|
|
ErrorKind::NotFound => StatusCode::NOT_FOUND,
|
|
|
|
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
};
|
2026-02-14 20:22:19 +01:00
|
|
|
|
|
|
|
|
(
|
|
|
|
|
status,
|
|
|
|
|
Json(serde_json::json!({
|
|
|
|
|
"error": format!("Error finding folder: {}", err)
|
|
|
|
|
})),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
2025-04-02 01:22:05 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-14 20:22:19 +01:00
|
|
|
}
|