Phase 3 : CRUD des livres, statuts de lecture et identite SSOwat

API
- CRUD complet sur /api/livres : liste filtrable (format, statut) et
  cherchable (titre, auteur), consultation, creation, modification,
  changement de statut isole, suppression.
- Aucune lecture ne filtre sur AjoutePar : la bibliotheque est commune au
  foyer. Le critere n'existe meme pas dans CritereLivres, pour qu'on ne
  puisse pas s'en servir par inadvertance. Deux tests verrouillent
  l'invariant, dont un par reflexion sur le type des criteres.
- AjoutePar est renseigne par le serveur a la creation, jamais par le
  client, et n'est pas reecrit a l'edition (pas plus que DateAjout).
- Identite lue dans les en-tetes SSOwat (YNH_USER, YNH_USER_EMAIL,
  YNH_USER_FULLNAME), avec repli sur un utilisateur simule configure en
  developpement uniquement. Sans en-tete ni simulation, l'identite reste
  inconnue plutot qu'inventee. Expose par GET /api/moi.
- Migration appliquee au demarrage : pas d'etape manuelle a l'installation.

Interface Blazor, pensee mobile d'abord
- Catalogue unique (physiques et numeriques melanges, distingues par le
  format) avec recherche, filtres et changement de statut directement dans
  la liste.
- Ajout par ISBN : quand la cascade rend plusieurs notices, un ecran de
  choix les presente en mettant en avant editeur et annee, seuls elements
  qui les departagent. Le formulaire reste ensuite entierement modifiable.
- Ajout manuel, edition, suppression avec confirmation explicite.
- Couvertures : l'URL OpenLibrary n'est jamais validee par un appel
  prealable (502 intermittents mesures en phase 2) ; un attribut onerror
  masque l'image cassee et laisse apparaitre un substitut.

Decisions prises la ou CLAUDE.md etait muet
- Tri alphabetique par titre.
- Un ISBN saisi doit etre valide, mais reste facultatif.
- Ecran de choix affiche seulement a partir de deux candidats.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-17 22:55:07 +02:00
co-authored by Claude Opus 5
parent aed03e6b6f
commit fd6438b912
29 changed files with 2430 additions and 203 deletions
@@ -0,0 +1,34 @@
using MaBibli.Shared.Entites;
namespace MaBibli.Shared.Dtos;
/// <summary>
/// Charge utile de création et de modification d'un livre.
/// </summary>
/// <remarks>
/// <see cref="Livre.AjoutePar"/> n'y figure pas volontairement : il est déterminé par le serveur
/// à partir de l'utilisateur authentifié, jamais par le client.
/// </remarks>
public record EnregistrementLivre
{
/// <summary>Facultatif : certains livres n'ont pas d'ISBN. Normalisé par le serveur.</summary>
public string? Isbn { get; set; }
public string Titre { get; set; } = string.Empty;
public string? Auteur { get; set; }
public string? Editeur { get; set; }
public Format Format { get; set; }
public Statut Statut { get; set; }
public string? CoverUrl { get; set; }
}
/// <summary>Charge utile du changement de statut de lecture — l'action la plus fréquente.</summary>
public record ChangementStatut
{
public Statut Statut { get; set; }
}
+34
View File
@@ -0,0 +1,34 @@
using MaBibli.Shared.Entites;
namespace MaBibli.Shared.Dtos;
/// <summary>
/// Un livre tel qu'exposé par l'API. Volontairement sans la collection <c>Prets</c> :
/// les prêts sont la phase 4.
/// </summary>
public record LivreDto
{
public required int Id { get; init; }
public string? Isbn { get; init; }
public required string Titre { get; init; }
public string? Auteur { get; init; }
public string? Editeur { get; init; }
public required Format Format { get; init; }
public required Statut Statut { get; init; }
public string? CoverUrl { get; init; }
public required DateTime DateAjout { get; init; }
/// <summary>
/// Qui a saisi le livre. <b>Information d'affichage, pas une frontière</b> :
/// la bibliothèque est commune et aucune lecture ne filtre sur ce champ.
/// </summary>
public string? AjoutePar { get; init; }
}
+30
View File
@@ -0,0 +1,30 @@
namespace MaBibli.Shared.Dtos;
/// <summary>
/// Identité de la personne connectée, telle que fournie par les en-têtes SSOwat de YunoHost.
/// </summary>
/// <remarks>
/// En développement il n'y a pas de SSOwat : un utilisateur simulé est configuré
/// (<c>Identite:UtilisateurSimule</c>), conformément à CLAUDE.md qui demande de simuler
/// plutôt que de désactiver l'authentification.
/// </remarks>
public record UtilisateurCourant
{
/// <summary><c>YNH_USER</c>. <c>null</c> si l'identité n'a pas pu être déterminée.</summary>
public string? Identifiant { get; init; }
/// <summary><c>YNH_USER_EMAIL</c>.</summary>
public string? Email { get; init; }
/// <summary><c>YNH_USER_FULLNAME</c> — évite une requête LDAP pour l'affichage.</summary>
public string? NomComplet { get; init; }
/// <summary>Vrai si l'identité provient de la configuration de développement, pas de SSOwat.</summary>
public bool Simule { get; init; }
/// <summary>Libellé à afficher, avec repli sur l'identifiant.</summary>
public string Affichage =>
NomComplet is { Length: > 0 } ? NomComplet : Identifiant ?? "inconnu";
public static readonly UtilisateurCourant Anonyme = new();
}