feat: batch favorites endpoint + frontend dedup fixes
Backend: - Add POST /api/favorites/batch endpoint (single multi-row INSERT) - Add BatchFavoritesResult/BatchFavoritesStats DTOs - Add batch methods to ports, service, PG repository - Transaction-based insert with ON CONFLICT DO NOTHING, chunking at 5000 Frontend: - Rewrite batchFavorites() to single API call (40 requests → 1) - Add _replaceCacheFromResponse() to avoid extra GET round-trip - Centralize formatFileSize, isTextViewable, formatDateTime, formatDateShort - Remove duplicate icon mapping from app.js - Fix inconsistent quota defaults (10GB everywhere)
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum::{Json, extract::{Path, State}, http::StatusCode, response::IntoResponse};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
|
||||
/// Single item in a batch-add-favorites request.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BatchFavoriteItem {
|
||||
pub item_id: String,
|
||||
pub item_type: String,
|
||||
}
|
||||
|
||||
/// Request body for POST /api/favorites/batch
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BatchFavoritesRequest {
|
||||
pub items: Vec<BatchFavoriteItem>,
|
||||
}
|
||||
|
||||
/// Handler for favorite-related API endpoints
|
||||
pub async fn get_favorites(
|
||||
State(favorites_service): State<Arc<dyn FavoritesUseCase>>,
|
||||
@@ -120,3 +129,63 @@ pub async fn remove_favorite(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add multiple items to favourites in a single transaction.
|
||||
/// POST /api/favorites/batch
|
||||
pub async fn batch_add_favorites(
|
||||
State(favorites_service): State<Arc<dyn FavoritesUseCase>>,
|
||||
auth_user: AuthUser,
|
||||
Json(body): Json<BatchFavoritesRequest>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = &auth_user.id;
|
||||
|
||||
if body.items.is_empty() {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({ "error": "items array must not be empty" })),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// Validate item types
|
||||
for item in &body.items {
|
||||
if item.item_type != "file" && item.item_type != "folder" {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!({
|
||||
"error": format!("Item type must be 'file' or 'folder', got '{}'", item.item_type)
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
|
||||
let items: Vec<(String, String)> = body
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|i| (i.item_id, i.item_type))
|
||||
.collect();
|
||||
|
||||
match favorites_service
|
||||
.batch_add_to_favorites(user_id, &items)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
info!(
|
||||
"Batch favourites: {} requested, {} inserted, {} already existed",
|
||||
result.stats.requested, result.stats.inserted, result.stats.already_existed
|
||||
);
|
||||
(StatusCode::OK, Json(serde_json::json!(result))).into_response()
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error in batch add favorites: {}", err);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(serde_json::json!({
|
||||
"error": format!("Failed to batch add favorites: {}", err)
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user