2025-10-17 19:48:53 +02:00
|
|
|
//! API REST générique pour la gestion du cache
|
|
|
|
|
//!
|
|
|
|
|
//! Ce module expose une API REST documentée avec OpenAPI/Swagger pour :
|
|
|
|
|
//! - Lister les items en cache
|
|
|
|
|
//! - Ajouter des items depuis une URL
|
|
|
|
|
//! - Consulter le status des downloads en cours
|
|
|
|
|
//! - Supprimer des items
|
|
|
|
|
//! - Purger et consolider le cache
|
|
|
|
|
|
2025-10-17 22:15:00 +02:00
|
|
|
use crate::{Cache, CacheConfig};
|
2025-10-17 19:48:53 +02:00
|
|
|
use axum::{
|
|
|
|
|
extract::{Path, State},
|
|
|
|
|
http::StatusCode,
|
|
|
|
|
response::IntoResponse,
|
|
|
|
|
Json,
|
|
|
|
|
};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2025-10-28 00:10:51 +01:00
|
|
|
use serde_json::Value;
|
2025-10-17 19:48:53 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "openapi")]
|
|
|
|
|
use utoipa::ToSchema;
|
|
|
|
|
|
|
|
|
|
/// Statut d'un téléchargement
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct DownloadStatus {
|
|
|
|
|
/// Clé primaire de l'item
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "1a2b3c4d5e6f7a8b"))]
|
|
|
|
|
pub pk: String,
|
|
|
|
|
/// Téléchargement en cours
|
|
|
|
|
pub in_progress: bool,
|
|
|
|
|
/// Taille actuelle téléchargée (source)
|
|
|
|
|
pub current_size: Option<u64>,
|
|
|
|
|
/// Taille après transformation
|
|
|
|
|
pub transformed_size: Option<u64>,
|
|
|
|
|
/// Taille totale attendue
|
|
|
|
|
pub expected_size: Option<u64>,
|
|
|
|
|
/// Téléchargement terminé
|
|
|
|
|
pub finished: bool,
|
|
|
|
|
/// Erreur éventuelle
|
|
|
|
|
pub error: Option<String>,
|
2025-10-28 00:10:51 +01:00
|
|
|
/// Informations sur la conversion
|
|
|
|
|
pub conversion: Option<ConversionStatus>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Informations sur la conversion en cours ou réalisée
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct ConversionStatus {
|
|
|
|
|
/// Mode de conversion (ex: "passthrough", "transcode")
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "passthrough"))]
|
|
|
|
|
pub mode: String,
|
|
|
|
|
/// Codec source détecté (si disponible)
|
|
|
|
|
pub input_codec: Option<String>,
|
|
|
|
|
/// Informations complémentaires lisibles (optionnel)
|
|
|
|
|
pub details: Option<String>,
|
2025-10-17 19:48:53 +02:00
|
|
|
}
|
|
|
|
|
|
2025-12-17 10:10:56 +01:00
|
|
|
/// Requête pour ajouter un item au cache.
|
|
|
|
|
///
|
|
|
|
|
/// Au moins une des deux entrées (`url` ou `path`) doit être fournie.
|
2025-10-17 19:48:53 +02:00
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct AddItemRequest {
|
2025-12-17 10:10:56 +01:00
|
|
|
/// URL HTTP/HTTPS/UPnP à télécharger
|
|
|
|
|
#[serde(default)]
|
2025-10-17 19:48:53 +02:00
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "https://example.com/file.dat"))]
|
2025-12-17 10:10:56 +01:00
|
|
|
pub url: Option<String>,
|
|
|
|
|
/// Chemin local (`file://` implicite) à référencer
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "/mnt/music/track.flac"))]
|
|
|
|
|
pub path: Option<String>,
|
2025-10-17 19:48:53 +02:00
|
|
|
/// Collection optionnelle
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "album:the_wall"))]
|
|
|
|
|
pub collection: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Réponse après ajout d'un item
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct AddItemResponse {
|
|
|
|
|
/// Clé primaire (pk) de l'item ajouté
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "1a2b3c4d5e6f7a8b"))]
|
|
|
|
|
pub pk: String,
|
2025-12-17 10:10:56 +01:00
|
|
|
/// URL ou chemin source de l'item
|
2025-10-17 19:48:53 +02:00
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "https://example.com/file.dat"))]
|
|
|
|
|
pub url: String,
|
|
|
|
|
/// Message de succès
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "Item added successfully"))]
|
|
|
|
|
pub message: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Réponse de suppression d'un item
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct DeleteItemResponse {
|
|
|
|
|
/// Message de succès
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "Item deleted successfully"))]
|
|
|
|
|
pub message: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Réponse d'erreur générique
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(feature = "openapi", derive(ToSchema))]
|
|
|
|
|
pub struct ErrorResponse {
|
|
|
|
|
/// Code d'erreur
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "NOT_FOUND"))]
|
|
|
|
|
pub error: String,
|
|
|
|
|
/// Message descriptif
|
|
|
|
|
#[cfg_attr(feature = "openapi", schema(example = "Item not found in cache"))]
|
|
|
|
|
pub message: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Liste tous les items en cache avec leurs statistiques
|
|
|
|
|
///
|
|
|
|
|
/// Retourne la liste complète des entrées du cache triées par nombre d'accès décroissant.
|
2025-12-17 21:41:24 +01:00
|
|
|
pub async fn list_items<C: CacheConfig + 'static>(
|
|
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-10-26 09:18:48 +01:00
|
|
|
match cache.db.get_all(true) {
|
2025-10-17 19:48:53 +02:00
|
|
|
Ok(entries) => (StatusCode::OK, Json(entries)).into_response(),
|
|
|
|
|
Err(e) => (
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "DATABASE_ERROR".to_string(),
|
|
|
|
|
message: format!("Cannot retrieve cache entries: {}", e),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Récupère les informations d'un item spécifique
|
|
|
|
|
///
|
|
|
|
|
/// Retourne les métadonnées d'un item identifié par sa clé (pk).
|
2025-12-17 15:15:10 +01:00
|
|
|
pub async fn get_item_info<C: CacheConfig + 'static>(
|
2025-10-17 19:48:53 +02:00
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
Path(pk): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-10-26 09:18:48 +01:00
|
|
|
match cache.db.get(&pk, true) {
|
2025-10-17 19:48:53 +02:00
|
|
|
Ok(entry) => (StatusCode::OK, Json(entry)).into_response(),
|
|
|
|
|
Err(_) => (
|
|
|
|
|
StatusCode::NOT_FOUND,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "NOT_FOUND".to_string(),
|
|
|
|
|
message: format!("Item with pk '{}' not found in cache", pk),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Récupère le statut du téléchargement d'un item
|
|
|
|
|
///
|
|
|
|
|
/// Retourne le statut actuel du téléchargement (progression, tailles, erreurs).
|
|
|
|
|
/// Si le téléchargement est terminé, retourne les informations du fichier.
|
2025-12-17 15:15:10 +01:00
|
|
|
pub async fn get_download_status<C: CacheConfig + 'static>(
|
2025-10-17 19:48:53 +02:00
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
Path(pk): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-10-28 00:10:51 +01:00
|
|
|
let entry = match cache.db.get(&pk, false) {
|
|
|
|
|
Ok(entry) => entry,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
return (
|
|
|
|
|
StatusCode::NOT_FOUND,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "NOT_FOUND".to_string(),
|
|
|
|
|
message: format!("Item with pk '{}' not found in cache", pk),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let download = cache.get_download(&pk).await;
|
|
|
|
|
let file_path = cache.get_file_path(&pk);
|
|
|
|
|
let file_size = if file_path.exists() {
|
|
|
|
|
std::fs::metadata(&file_path).ok().map(|m| m.len())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let in_progress = download.is_some();
|
|
|
|
|
let current_size = if let Some(download) = download.as_ref() {
|
|
|
|
|
Some(download.current_size().await)
|
|
|
|
|
} else {
|
|
|
|
|
file_size
|
|
|
|
|
};
|
2025-10-17 19:48:53 +02:00
|
|
|
|
2025-10-28 00:10:51 +01:00
|
|
|
let transformed_size = if let Some(download) = download.as_ref() {
|
|
|
|
|
Some(download.transformed_size().await)
|
|
|
|
|
} else {
|
|
|
|
|
file_size
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let expected_size = if let Some(download) = download.as_ref() {
|
|
|
|
|
download.expected_size().await
|
|
|
|
|
} else {
|
|
|
|
|
file_size
|
|
|
|
|
};
|
2025-10-17 19:48:53 +02:00
|
|
|
|
2025-10-28 00:10:51 +01:00
|
|
|
let finished = if let Some(download) = download.as_ref() {
|
|
|
|
|
download.finished().await
|
|
|
|
|
} else {
|
|
|
|
|
file_path.exists()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let error = if let Some(download) = download.as_ref() {
|
2025-10-17 19:48:53 +02:00
|
|
|
download.error().await
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-28 00:10:51 +01:00
|
|
|
let mut conversion = if let Some(download) = download.as_ref() {
|
|
|
|
|
download
|
|
|
|
|
.transform_metadata()
|
|
|
|
|
.await
|
|
|
|
|
.map(ConversionStatus::from)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if conversion.is_none() {
|
|
|
|
|
if let Some(meta) = entry.metadata.as_ref() {
|
|
|
|
|
conversion = conversion_from_json(meta);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 19:48:53 +02:00
|
|
|
let status = DownloadStatus {
|
|
|
|
|
pk,
|
|
|
|
|
in_progress,
|
|
|
|
|
current_size,
|
|
|
|
|
transformed_size,
|
|
|
|
|
expected_size,
|
|
|
|
|
finished,
|
|
|
|
|
error,
|
2025-10-28 00:10:51 +01:00
|
|
|
conversion,
|
2025-10-17 19:48:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
(StatusCode::OK, Json(status)).into_response()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 00:10:51 +01:00
|
|
|
impl From<crate::download::TransformMetadata> for ConversionStatus {
|
|
|
|
|
fn from(value: crate::download::TransformMetadata) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
mode: value.mode.unwrap_or_else(|| "unknown".to_string()),
|
|
|
|
|
input_codec: value.input_codec,
|
|
|
|
|
details: value.details,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn conversion_from_json(value: &Value) -> Option<ConversionStatus> {
|
|
|
|
|
value
|
|
|
|
|
.get("conversion")
|
|
|
|
|
.and_then(|conv| serde_json::from_value(conv.clone()).ok())
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 10:10:56 +01:00
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
enum AddSource<'a> {
|
|
|
|
|
Url(&'a str),
|
|
|
|
|
Local(&'a str),
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 19:48:53 +02:00
|
|
|
/// Ajoute un item au cache depuis une URL
|
|
|
|
|
///
|
|
|
|
|
/// Télécharge l'item depuis l'URL fournie et l'ajoute au cache.
|
|
|
|
|
/// Si l'item existe déjà, il est mis à jour.
|
2025-12-17 15:15:10 +01:00
|
|
|
pub async fn add_item<C: CacheConfig + 'static>(
|
2025-10-17 19:48:53 +02:00
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
Json(req): Json<AddItemRequest>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-12-17 10:10:56 +01:00
|
|
|
let mode = match (req.url.as_deref(), req.path.as_deref()) {
|
|
|
|
|
(Some(url), None) if !url.is_empty() => AddSource::Url(url),
|
|
|
|
|
(None, Some(path)) if !path.is_empty() => AddSource::Local(path),
|
|
|
|
|
(Some(_), Some(_)) => {
|
|
|
|
|
return (
|
|
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "INVALID_REQUEST".to_string(),
|
|
|
|
|
message: "Provide either 'url' or 'path', not both".to_string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
return (
|
|
|
|
|
StatusCode::BAD_REQUEST,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "INVALID_REQUEST".to_string(),
|
|
|
|
|
message: "Either 'url' or 'path' must be provided".to_string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-10-17 19:48:53 +02:00
|
|
|
|
2025-12-17 10:10:56 +01:00
|
|
|
let collection = req.collection.as_deref();
|
|
|
|
|
let add_result = match mode {
|
|
|
|
|
AddSource::Url(url) => cache.add_from_url(url, collection).await,
|
|
|
|
|
AddSource::Local(path) => cache.add_from_file(path, collection).await,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match add_result {
|
|
|
|
|
Ok(pk) => {
|
|
|
|
|
let origin =
|
|
|
|
|
cache
|
|
|
|
|
.db
|
|
|
|
|
.get_origin_url(&pk)
|
|
|
|
|
.ok()
|
|
|
|
|
.flatten()
|
|
|
|
|
.unwrap_or_else(|| match mode {
|
|
|
|
|
AddSource::Url(url) => url.to_string(),
|
|
|
|
|
AddSource::Local(path) => format!("file://{}", path),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
(
|
|
|
|
|
StatusCode::CREATED,
|
|
|
|
|
Json(AddItemResponse {
|
|
|
|
|
pk,
|
|
|
|
|
url: origin,
|
|
|
|
|
message: "Item added successfully".to_string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
|
|
|
|
}
|
2025-10-17 19:48:53 +02:00
|
|
|
Err(e) => (
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "PROCESSING_ERROR".to_string(),
|
|
|
|
|
message: format!("Cannot add item: {}", e),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Supprime un item du cache
|
|
|
|
|
///
|
|
|
|
|
/// Supprime l'item et toutes ses variantes du disque et de la base de données.
|
2025-12-17 15:15:10 +01:00
|
|
|
pub async fn delete_item<C: CacheConfig + 'static>(
|
2025-10-17 19:48:53 +02:00
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
Path(pk): Path<String>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
// Vérifier que l'item existe
|
2025-10-26 09:18:48 +01:00
|
|
|
if cache.db.get(&pk, false).is_err() {
|
2025-10-17 19:48:53 +02:00
|
|
|
return (
|
|
|
|
|
StatusCode::NOT_FOUND,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "NOT_FOUND".to_string(),
|
|
|
|
|
message: format!("Item with pk '{}' not found in cache", pk),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Supprimer tous les fichiers avec ce pk (toutes variantes)
|
|
|
|
|
let cache_dir = cache.cache_dir();
|
|
|
|
|
if let Ok(mut entries) = tokio::fs::read_dir(cache_dir).await {
|
|
|
|
|
while let Ok(Some(entry)) = entries.next_entry().await {
|
|
|
|
|
if let Some(filename) = entry.file_name().to_str() {
|
|
|
|
|
// Format: {pk}.{param}.{ext}
|
|
|
|
|
if filename.starts_with(&pk) && filename.starts_with(&format!("{}.", pk)) {
|
|
|
|
|
let _ = tokio::fs::remove_file(entry.path()).await;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Supprimer de la base de données
|
|
|
|
|
match cache.db.delete(&pk) {
|
|
|
|
|
Ok(_) => (
|
|
|
|
|
StatusCode::OK,
|
|
|
|
|
Json(DeleteItemResponse {
|
|
|
|
|
message: format!("Item '{}' deleted successfully", pk),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
Err(e) => (
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "DATABASE_ERROR".to_string(),
|
|
|
|
|
message: format!("Cannot delete from database: {}", e),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Purge complètement le cache
|
|
|
|
|
///
|
|
|
|
|
/// Supprime tous les items et vide la base de données. Opération irréversible.
|
2025-12-17 21:41:24 +01:00
|
|
|
pub async fn purge_cache<C: CacheConfig + 'static>(
|
|
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
) -> impl IntoResponse {
|
2025-10-17 19:48:53 +02:00
|
|
|
match cache.purge().await {
|
|
|
|
|
Ok(_) => (
|
|
|
|
|
StatusCode::OK,
|
|
|
|
|
Json(DeleteItemResponse {
|
|
|
|
|
message: "Cache purged successfully".to_string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
Err(e) => (
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "PURGE_ERROR".to_string(),
|
|
|
|
|
message: format!("Cannot purge cache: {}", e),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Consolide le cache
|
|
|
|
|
///
|
|
|
|
|
/// Re-télécharge les items manquants et supprime les fichiers orphelins.
|
|
|
|
|
/// Utile pour réparer un cache corrompu.
|
2025-12-17 15:15:10 +01:00
|
|
|
pub async fn consolidate_cache<C: CacheConfig + 'static>(
|
2025-10-17 19:48:53 +02:00
|
|
|
State(cache): State<Arc<Cache<C>>>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
match cache.consolidate().await {
|
|
|
|
|
Ok(_) => (
|
|
|
|
|
StatusCode::OK,
|
|
|
|
|
Json(DeleteItemResponse {
|
|
|
|
|
message: "Cache consolidated successfully".to_string(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
Err(e) => (
|
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
|
Json(ErrorResponse {
|
|
|
|
|
error: "CONSOLIDATE_ERROR".to_string(),
|
|
|
|
|
message: format!("Cannot consolidate cache: {}", e),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|