feat: suppression images + gestion tags (ajout/suppression)
Backend:
- ImagoClient::delete_image() — DELETE /api/v1/images/{id}
- ImagoClient::update_tags() — PUT /api/v1/images/{id}/tags
- Route DELETE /api/images/{id}
- Route PUT /api/images/{id}/tags
Frontend:
- Bouton 🗑️ sur chaque carte (visible au hover)
- Boîte de confirmation avant suppression
- Tags cliquables avec × pour suppression
- Bouton + tag pour ajouter un tag (Enter pour valider)
- Bouton Supprimer + tags éditables dans la modale
- Suppression + modif tags aussi dans la modale
⚠️ Nécessite redéploiement Imago pour le endpoint PUT /tags
This commit is contained in:
+65
-2
@@ -1,7 +1,7 @@
|
||||
//! Routes API REST du dashboard — proxy vers Imago v2
|
||||
|
||||
use axum::{
|
||||
Router, routing::{get, post},
|
||||
Router, routing::{get, post, put, delete},
|
||||
extract::{Multipart, Path, Query, State},
|
||||
http::HeaderMap,
|
||||
response::IntoResponse,
|
||||
@@ -97,7 +97,8 @@ pub fn api_routes(state: AppState) -> Router {
|
||||
.route("/api/upload", post(upload_image))
|
||||
.route("/api/images", get(list_images))
|
||||
.route("/api/images/search", get(search_images))
|
||||
.route("/api/images/{id}", get(get_image))
|
||||
.route("/api/images/{id}", get(get_image).delete(delete_image))
|
||||
.route("/api/images/{id}/tags", put(update_tags))
|
||||
.route("/api/images/{id}/thumbnail", get(get_thumbnail))
|
||||
.route("/api/stats", get(stats))
|
||||
.with_state(state)
|
||||
@@ -447,6 +448,68 @@ async fn get_thumbnail(
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_image(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let img_id: i64 = match id.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => return error_response(400, "ID invalide"),
|
||||
};
|
||||
|
||||
let client = match get_client(&state, &headers) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return error_response(500, &format!("Erreur client : {}", e)),
|
||||
};
|
||||
|
||||
match client.delete_image(img_id).await {
|
||||
Ok(()) => (
|
||||
axum::http::StatusCode::OK,
|
||||
Json(serde_json::json!({"deleted": true, "id": img_id})),
|
||||
),
|
||||
Err(e) => {
|
||||
let err_msg = format!("{:#}", e);
|
||||
let status = if err_msg.contains("403") || err_msg.contains("401") { 403 } else { 500 };
|
||||
error_response(status, &format!("Échec suppression : {}", err_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Requête de mise à jour des tags
|
||||
#[derive(Deserialize)]
|
||||
pub struct TagsUpdate {
|
||||
pub tags: Vec<String>,
|
||||
}
|
||||
|
||||
async fn update_tags(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
Json(body): Json<TagsUpdate>,
|
||||
) -> impl IntoResponse {
|
||||
let img_id: i64 = match id.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => return error_response(400, "ID invalide"),
|
||||
};
|
||||
|
||||
let client = match get_client(&state, &headers) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return error_response(500, &format!("Erreur client : {}", e)),
|
||||
};
|
||||
|
||||
match client.update_tags(img_id, body.tags.clone()).await {
|
||||
Ok(()) => (
|
||||
axum::http::StatusCode::OK,
|
||||
Json(serde_json::json!({"updated": true, "id": img_id, "tags": body.tags})),
|
||||
),
|
||||
Err(e) => {
|
||||
let err_msg = format!("{:#}", e);
|
||||
error_response(500, &format!("Échec mise à jour tags : {}", err_msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn stats(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
|
||||
Reference in New Issue
Block a user