Files
Oxicloud/src/interfaces/api/handlers/favorites_handler.rs
T
Diocrafts 06ed0455ce perf: migrate all user/session/auth IDs from VARCHAR(36) to native UUID
- Schema: all ~15 VARCHAR(36) columns → UUID with DEFAULT gen_random_uuid()
- Domain entities: User, Session, DeviceCode, AppPassword, Share → id: Uuid
- DTOs: CurrentUser.id → Uuid (API boundary DTOs keep String for JSON)
- Auth middleware: parse JWT claims.sub (String) → Uuid at boundary
- All repository traits, port traits, service impls updated end-to-end
- Handlers: pass Uuid by value (Copy, 16 bytes) instead of String refs
- Settings chain: updated_by column → Uuid (was text, caused setup crash)
- Removed ~650 lines of String↔Uuid conversion boilerplate
- Eliminates per-request heap allocations for ID cloning
- 16-byte binary comparison vs 36-byte string comparison in all queries
- Native UUID indexing in PostgreSQL (btree on 16 bytes vs 36-char text)

85 files changed, 1090 insertions(+), 1739 deletions(-)
2026-03-07 14:59:32 +01:00

198 lines
5.8 KiB
Rust
Executable File

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::application::services::favorites_service::FavoritesService;
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<FavoritesService>>,
auth_user: AuthUser,
) -> impl IntoResponse {
let user_id = auth_user.id;
match favorites_service.get_favorites(user_id).await {
Ok(favorites) => {
info!("Retrieved {} favorites for user", favorites.len());
(StatusCode::OK, Json(serde_json::json!(favorites))).into_response()
}
Err(err) => {
error!("Error retrieving favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to retrieve favorites"
})),
)
.into_response()
}
}
}
/// Add an item to user's favorites
pub async fn add_favorite(
State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = auth_user.id;
// Validate item_type
if item_type != "file" && item_type != "folder" {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": "Item type must be 'file' or 'folder'"
})),
);
}
match favorites_service
.add_to_favorites(user_id, &item_id, &item_type)
.await
{
Ok(_) => {
info!("Added {} '{}' to favorites", item_type, item_id);
(
StatusCode::CREATED,
Json(serde_json::json!({
"message": "Item added to favorites"
})),
)
}
Err(err) => {
error!("Error adding to favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to add to favorites"
})),
)
}
}
}
/// Remove an item from user's favorites
pub async fn remove_favorite(
State(favorites_service): State<Arc<FavoritesService>>,
auth_user: AuthUser,
Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
let user_id = auth_user.id;
match favorites_service
.remove_from_favorites(user_id, &item_id, &item_type)
.await
{
Ok(removed) => {
if removed {
info!("Removed {} '{}' from favorites", item_type, item_id);
(
StatusCode::OK,
Json(serde_json::json!({
"message": "Item removed from favorites"
})),
)
} else {
info!("Item {} '{}' was not in favorites", item_type, item_id);
(
StatusCode::NOT_FOUND,
Json(serde_json::json!({
"message": "Item was not in favorites"
})),
)
}
}
Err(err) => {
error!("Error removing from favorites: {}", err);
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({
"error": "Failed to remove from favorites"
})),
)
}
}
}
/// Add multiple items to favourites in a single transaction.
/// POST /api/favorites/batch
pub async fn batch_add_favorites(
State(favorites_service): State<Arc<FavoritesService>>,
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": "Failed to batch add favorites"
})),
)
.into_response()
}
}
}