using System.Net; using System.Net.Http.Json; using System.Text.Json; using MaBibli.Shared.Catalogue; using MaBibli.Shared.Dtos; using MaBibli.Shared.Entites; namespace MaBibli.Client.Services; /// /// Accès à l'API depuis le client Blazor. /// /// /// Aucun cache : la phase 3 suppose le réseau disponible. Le cache hors-ligne est la phase 6. /// public sealed class ServiceLivresApi(HttpClient http) { private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web); public async Task> ListerAsync(CritereLivres criteres, CancellationToken ct = default) { var parametres = new List(); if (!string.IsNullOrWhiteSpace(criteres.Recherche)) { parametres.Add($"recherche={Uri.EscapeDataString(criteres.Recherche.Trim())}"); } if (criteres.Format is { } format) { parametres.Add($"format={format}"); } if (criteres.Statut is { } statut) { parametres.Add($"statut={statut}"); } var url = "api/livres" + (parametres.Count > 0 ? "?" + string.Join('&', parametres) : string.Empty); return await http.GetFromJsonAsync>(url, Json, ct) ?? []; } public async Task ObtenirAsync(int id, CancellationToken ct = default) { var reponse = await http.GetAsync($"api/livres/{id}", ct); return reponse.StatusCode == HttpStatusCode.NotFound ? null : await LireAsync(reponse, ct); } public async Task> CreerAsync(EnregistrementLivre saisie, CancellationToken ct = default) { var reponse = await http.PostAsJsonAsync("api/livres", saisie, Json, ct); return await InterpreterAsync(reponse, ct); } public async Task> ModifierAsync(int id, EnregistrementLivre saisie, CancellationToken ct = default) { var reponse = await http.PutAsJsonAsync($"api/livres/{id}", saisie, Json, ct); return await InterpreterAsync(reponse, ct); } public async Task> ChangerStatutAsync(int id, Statut statut, CancellationToken ct = default) { var reponse = await http.PatchAsJsonAsync( $"api/livres/{id}/statut", new ChangementStatut { Statut = statut }, Json, ct); return await InterpreterAsync(reponse, ct); } public async Task SupprimerAsync(int id, CancellationToken ct = default) { var reponse = await http.DeleteAsync($"api/livres/{id}", ct); return reponse.IsSuccessStatusCode ? null : await MessageErreurAsync(reponse, ct); } public async Task ChercherIsbnAsync(string isbn, CancellationToken ct = default) { var reponse = await http.GetAsync($"api/isbn/{Uri.EscapeDataString(isbn)}", ct); return reponse.IsSuccessStatusCode ? await LireAsync(reponse, ct) : null; } public async Task ObtenirUtilisateurAsync(CancellationToken ct = default) { try { return await http.GetFromJsonAsync("api/moi", Json, ct) ?? UtilisateurCourant.Anonyme; } catch (HttpRequestException) { return UtilisateurCourant.Anonyme; } } private static async Task LireAsync(HttpResponseMessage reponse, CancellationToken ct) { reponse.EnsureSuccessStatusCode(); return await reponse.Content.ReadFromJsonAsync(Json, ct); } private static async Task> InterpreterAsync(HttpResponseMessage reponse, CancellationToken ct) { if (reponse.IsSuccessStatusCode) { var valeur = await reponse.Content.ReadFromJsonAsync(Json, ct); return new ResultatApi(valeur, null); } return new ResultatApi(default, await MessageErreurAsync(reponse, ct)); } /// Récupère le message d'erreur de l'API pour l'afficher tel quel à l'utilisateur. private static async Task MessageErreurAsync(HttpResponseMessage reponse, CancellationToken ct) { try { var corps = await reponse.Content.ReadFromJsonAsync(Json, ct); if (!string.IsNullOrWhiteSpace(corps?.Erreur)) { return corps.Erreur; } } catch (Exception) { // Corps illisible ou vide : on retombe sur le code HTTP. } return reponse.StatusCode switch { HttpStatusCode.NotFound => "Ce livre n'existe plus.", _ => $"L'enregistrement a échoué ({(int)reponse.StatusCode}).", }; } private sealed record ErreurApi(string? Erreur); } /// Résultat d'un appel d'écriture : la valeur, ou un message d'erreur affichable. public readonly record struct ResultatApi(T? Valeur, string? Erreur) { public bool EstOk => Erreur is null; }