Ajouter la liste d'envies personnelle, son export et la bibliographie par auteur

La liste d'envies vit dans une table séparée (LivreSouhaite) plutôt que dans un
statut de plus sur Livre : un livre souhaité n'est pas possédé, et le loger dans
Livres l'aurait fait entrer dans le catalogue, les compteurs et les prêts, au
prix d'un « et qui n'est pas souhaité » à répéter dans chaque lecture. La portée
est personnelle, comme le statut de lecture — mais ici Utilisateur est une vraie
frontière : toute lecture filtre dessus.

Bibliographie : SRU BnF interrogé par bib.author, vérifié le 2026-08-18. Deux
filtres mesurés sur des réponses réelles sont indispensables — le type de
document (l'index mêle livres audio, jeux et spectacles) et surtout l'auteur
réel de la notice, « all » rapprochant les mots sur l'ensemble des auteurs :
« Émile Zola » remonte sinon toute l'œuvre de sa fille Denise Le Blond-Zola.
Les rééditions sont regroupées par clé d'œuvre (183 notices Werber -> 51
œuvres). Le rapprochement avec l'étagère se fait par titre, pas par ISBN, qui
désigne une édition et non une œuvre ; ses limites sont dites à l'écran.

Export en deux formats, tous deux du texte sans dépendance : .txt groupé par
auteur pour la librairie, .csv à séparateur point-virgule et BOM pour le
tableur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-18 13:59:06 +02:00
co-authored by Claude Opus 5
parent 3a5a3a7763
commit 8b8fe3be6e
27 changed files with 3939 additions and 0 deletions
@@ -231,6 +231,63 @@ public sealed class ServiceLivresApi(HttpClient http)
};
}
// ─────────────────────────────────────────────────────────────────────────
// Liste d'envies et bibliographie par auteur.
// Bloc ajouté en fin de classe pour rester bien délimité.
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// La liste d'envies <b>de l'utilisateur courant</b>.
/// </summary>
/// <remarks>
/// Comme pour le statut de lecture, le client ne transmet aucune identité : le serveur sait
/// qui appelle. Il n'existe volontairement pas de moyen de demander la liste d'un autre.
/// </remarks>
public async Task<IReadOnlyList<SouhaitDto>> ListerSouhaitsAsync(CancellationToken ct = default) =>
await http.GetFromJsonAsync<List<SouhaitDto>>("api/souhaits", Json, ct) ?? [];
public async Task<ResultatApi<SouhaitDto>> AjouterSouhaitAsync(
EnregistrementSouhait saisie, CancellationToken ct = default)
{
var reponse = await http.PostAsJsonAsync("api/souhaits", saisie, Json, ct);
return await InterpreterAsync<SouhaitDto>(reponse, ct);
}
public async Task<string?> SupprimerSouhaitAsync(int id, CancellationToken ct = default)
{
var reponse = await http.DeleteAsync($"api/souhaits/{id}", ct);
return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct);
}
/// <summary>
/// Adresse de téléchargement d'un export.
/// </summary>
/// <remarks>
/// Le fichier n'est pas récupéré par ce service : un simple lien suffit, le serveur pose un
/// <c>Content-Disposition: attachment</c> et le navigateur enregistre. Passer par
/// <see cref="HttpClient"/> obligerait à fabriquer une URL d'objet en JavaScript pour un
/// résultat identique.
/// <para>
/// ⚠️ Le lien <b>doit</b> porter l'attribut <c>download</c> : sans lui, le routeur Blazor
/// intercepte le clic comme une navigation interne et affiche sa page « introuvable » au
/// lieu de laisser le navigateur télécharger.
/// </para>
/// </remarks>
public static string UrlExportSouhaits(FormatExportSouhaits format) =>
format == FormatExportSouhaits.Csv ? "api/souhaits/export.csv" : "api/souhaits/export.txt";
/// <summary>
/// Bibliographie d'un auteur d'après la BnF. <c>null</c> si l'auteur n'existe pas.
/// </summary>
public async Task<BibliographieDto?> ObtenirBibliographieAsync(
int auteurId, CancellationToken ct = default)
{
var reponse = await http.GetAsync($"api/auteurs/{auteurId}/bibliographie", ct);
return reponse.StatusCode == HttpStatusCode.NotFound
? null
: await LireAsync<BibliographieDto>(reponse, ct);
}
private sealed record ErreurApi(string? Erreur);
}