update la webapp
This commit is contained in:
101
pmoaudiocache/src/api.rs
Normal file
101
pmoaudiocache/src/api.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
//! API REST handlers spécifiques au cache audio
|
||||
|
||||
use crate::metadata_ext::AudioTrackMetadataExt;
|
||||
use crate::Cache;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
Json,
|
||||
};
|
||||
use pmometadata::TrackMetadata;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Réponse contenant l'URL de la cover avec fallback
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CoverUrlResponse {
|
||||
/// PK de la piste
|
||||
#[schema(example = "1a2b3c4d5e6f7a8b")]
|
||||
pub pk: String,
|
||||
/// URL de la cover (cover_pk, cover_url, ou data URL par défaut)
|
||||
#[schema(example = "https://example.com/cover.jpg")]
|
||||
pub cover_url: String,
|
||||
/// Source de l'URL: "cover_pk", "cover_url", ou "default"
|
||||
#[schema(example = "cover_pk")]
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
/// Récupère l'URL de la cover d'une piste avec logique de fallback
|
||||
///
|
||||
/// Cette route retourne l'URL de la cover en appliquant la logique de priorité suivante :
|
||||
/// 1. Si `cover_pk` est défini dans les métadonnées, retourne la clé du cache de covers
|
||||
/// 2. Sinon, si `cover_url` est défini, retourne l'URL externe
|
||||
/// 3. Sinon, retourne une image SVG par défaut (data URL)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `pk` - Clé primaire de la piste audio
|
||||
///
|
||||
/// # Responses
|
||||
///
|
||||
/// * `200 OK` - Retourne l'URL de la cover avec la source
|
||||
/// * `404 NOT_FOUND` - Piste non trouvée
|
||||
/// * `500 INTERNAL_SERVER_ERROR` - Erreur lors de la lecture des métadonnées
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/{pk}/cover-url",
|
||||
tag = "audio",
|
||||
params(
|
||||
("pk" = String, Path, description = "Clé primaire de la piste")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "URL de la cover récupérée avec succès", body = CoverUrlResponse),
|
||||
(status = 404, description = "Piste non trouvée", body = pmocache::api::ErrorResponse),
|
||||
(status = 500, description = "Erreur interne", body = pmocache::api::ErrorResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn get_cover_url(
|
||||
State(cache): State<Arc<Cache>>,
|
||||
Path(pk): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
// Vérifier que la piste existe
|
||||
if cache.db.get(&pk, false).is_err() {
|
||||
return (
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(pmocache::api::ErrorResponse {
|
||||
error: "NOT_FOUND".to_string(),
|
||||
message: format!("Track with pk '{}' not found in cache", pk),
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
|
||||
// Récupérer les métadonnées
|
||||
let metadata = cache.track_metadata(&pk);
|
||||
let metadata_guard = metadata.read().await;
|
||||
|
||||
// Déterminer la source et l'URL
|
||||
let (cover_url, source) = match metadata_guard.get_cover_pk().await {
|
||||
Ok(Some(cover_pk)) if !cover_pk.is_empty() => (cover_pk, "cover_pk".to_string()),
|
||||
_ => match metadata_guard.get_cover_url().await {
|
||||
Ok(Some(url)) if !url.is_empty() => (url, "cover_url".to_string()),
|
||||
_ => (
|
||||
pmometadata::get_default_cover_url(),
|
||||
"default".to_string(),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(CoverUrlResponse {
|
||||
pk,
|
||||
cover_url,
|
||||
source,
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
@@ -81,6 +81,9 @@ pub mod metadata_ext;
|
||||
pub mod streaming;
|
||||
pub mod track_metadata;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub mod api;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub mod openapi;
|
||||
|
||||
@@ -218,7 +221,19 @@ impl AudioCacheExt for pmoserver::Server {
|
||||
|
||||
// API REST générique (pmocache)
|
||||
// Routes: GET/POST/DELETE /api/audio, etc.
|
||||
let api_router = create_api_router(cache.clone());
|
||||
let mut api_router = create_api_router(cache.clone());
|
||||
|
||||
// Ajouter les endpoints audio spécifiques
|
||||
// Route: GET /api/audio/{pk}/cover-url
|
||||
api_router = api_router.merge(
|
||||
axum::Router::new()
|
||||
.route(
|
||||
"/{pk}/cover-url",
|
||||
axum::routing::get(crate::api::get_cover_url),
|
||||
)
|
||||
.with_state(cache.clone())
|
||||
);
|
||||
|
||||
let openapi = crate::ApiDoc::openapi();
|
||||
self.add_openapi(api_router, openapi, "audio").await;
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ use utoipa::OpenApi;
|
||||
/// L'API réutilise les handlers génériques de pmocache.
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::api::get_cover_url,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
pmocache::CacheEntry,
|
||||
@@ -15,6 +18,7 @@ use utoipa::OpenApi;
|
||||
pmocache::api::DeleteItemResponse,
|
||||
pmocache::api::ErrorResponse,
|
||||
pmocache::api::DownloadStatus,
|
||||
crate::api::CoverUrlResponse,
|
||||
)
|
||||
),
|
||||
tags(
|
||||
@@ -56,6 +60,9 @@ Supprime une piste
|
||||
### GET /api/audio/{pk}/status
|
||||
Récupère le statut du téléchargement et de la conversion
|
||||
|
||||
### GET /api/audio/{pk}/cover-url
|
||||
Récupère l'URL de la cover avec fallback automatique (cover_pk → cover_url → image par défaut)
|
||||
|
||||
### DELETE /api/audio
|
||||
Purge complètement le cache
|
||||
|
||||
@@ -84,6 +91,8 @@ Les métadonnées suivantes sont extraites et stockées :
|
||||
- Numéro de piste/disque, total de pistes/disques
|
||||
- Durée, taux d'échantillonnage, bitrate
|
||||
- Nombre de canaux
|
||||
- Cover : `cover_pk` (clé dans le cache de covers) et `cover_url` (URL externe)
|
||||
- Fallback automatique vers une image SVG par défaut si aucune cover n'est disponible
|
||||
|
||||
## Collections
|
||||
|
||||
|
||||
Reference in New Issue
Block a user