diff --git a/MaBibli.Api/Data/MaBibliDbContext.cs b/MaBibli.Api/Data/MaBibliDbContext.cs index 442c735..4ce55d6 100644 --- a/MaBibli.Api/Data/MaBibliDbContext.cs +++ b/MaBibli.Api/Data/MaBibliDbContext.cs @@ -46,6 +46,12 @@ public class MaBibliDbContext(DbContextOptions options) : DbCo public DbSet NumerosRevue => Set(); + /// + /// Articles à la une d'un numéro. Table à part de : un titre d'article + /// est unique à sa parution et ne se réutilise jamais, là où un thème est un vocabulaire. + /// + public DbSet ArticlesUne => Set(); + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -259,6 +265,24 @@ public class MaBibliDbContext(DbContextOptions options) : DbCo numero.HasIndex(n => new { n.RevueId, n.NumeroNormalise }).IsUnique(); }); + modelBuilder.Entity(article => + { + article.Property(a => a.Titre).IsRequired(); + article.Property(a => a.TitreNormalise).IsRequired(); + + // Cascade, comme les numéros sous leur revue : un article à la une n'existe que par + // la parution qui le porte. Rien de comparable à ElementSerie, dont la place doit + // survivre au livre. + article.HasOne(a => a.NumeroRevue) + .WithMany(n => n.Articles) + .HasForeignKey(a => a.NumeroRevueId) + .OnDelete(DeleteBehavior.Cascade); + + // Le même article ne s'annonce pas deux fois à la une du même numéro. L'unicité est + // bornée au numéro : deux parutions peuvent parfaitement titrer pareil. + article.HasIndex(a => new { a.NumeroRevueId, a.TitreNormalise }).IsUnique(); + }); + modelBuilder.Entity(pret => { pret.Property(p => p.Emprunteur).IsRequired(); diff --git a/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.Designer.cs b/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.Designer.cs new file mode 100644 index 0000000..feed027 --- /dev/null +++ b/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.Designer.cs @@ -0,0 +1,641 @@ +// +using System; +using MaBibli.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace MaBibli.Api.Data.Migrations +{ + [DbContext(typeof(MaBibliDbContext))] + [Migration("20260821084601_ImagesEtUnesDesNumeros")] + partial class ImagesEtUnesDesNumeros + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.11"); + + modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NumeroRevueId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NumeroRevueId", "TitreNormalise") + .IsUnique(); + + b.ToTable("ArticlesUne"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CleRegroupement") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NomNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CleRegroupement") + .IsUnique(); + + b.HasIndex("NomNormalise"); + + b.ToTable("Auteurs"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.BibliographieMasquee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuteurId") + .HasColumnType("INTEGER"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Utilisateur", "AuteurId"); + + b.HasIndex(new[] { "AuteurId", "Utilisateur", "TitreNormalise" }, "IX_BibliographiesMasquees_Auteur_Utilisateur_Titre") + .IsUnique(); + + b.ToTable("BibliographiesMasquees"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("SerieId") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LivreId"); + + b.HasIndex(new[] { "SerieId" }, "IX_ElementsSerie_SerieId"); + + b.HasIndex(new[] { "SerieId", "LivreId" }, "IX_ElementsSerie_SerieId_LivreId") + .IsUnique() + .HasFilter("\"LivreId\" IS NOT NULL"); + + b.ToTable("ElementsSerie"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("Isbn") + .HasColumnType("TEXT"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TypeDocument") + .HasColumnType("INTEGER"); + + b.Property("UrlNotice") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Isbn"); + + b.HasIndex("TitreNormalise"); + + b.ToTable("Livres"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => + { + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("AuteurId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.HasKey("LivreId", "AuteurId"); + + b.HasIndex("AuteurId"); + + b.ToTable("LivreAuteurs"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreSouhaite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Annee") + .HasColumnType("TEXT"); + + b.Property("Auteur") + .HasColumnType("TEXT"); + + b.Property("AuteurNormalise") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Isbn") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Rang") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Utilisateur" }, "IX_LivresSouhaites_Utilisateur"); + + b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "AuteurNormalise" }, "IX_LivresSouhaites_Utilisateur_Oeuvre") + .IsUnique(); + + b.ToTable("LivresSouhaites"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => + { + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.HasKey("LivreId", "ThemeId"); + + b.HasIndex("ThemeId"); + + b.ToTable("LivreThemes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("DateParution") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NumeroNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevueId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevueId", "NumeroNormalise") + .IsUnique(); + + b.ToTable("NumerosRevue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DatePret") + .HasColumnType("TEXT"); + + b.Property("DateRetour") + .HasColumnType("TEXT"); + + b.Property("Emprunteur") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId"); + + b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId_EnCours") + .IsUnique() + .HasFilter("\"DateRetour\" IS NULL"); + + b.ToTable("Prets"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.RapprochementRefuse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuteurAId") + .HasColumnType("INTEGER"); + + b.Property("AuteurBId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AuteurAId", "AuteurBId") + .IsUnique(); + + b.ToTable("RapprochementsRefuses"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Issn") + .HasColumnType("TEXT"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TitreNormalise") + .IsUnique(); + + b.HasIndex(new[] { "Issn" }, "IX_Revues_Issn") + .IsUnique() + .HasFilter("\"Issn\" IS NOT NULL"); + + b.ToTable("Revues"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("SerieParenteId") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SerieParenteId"); + + b.HasIndex("TitreNormalise") + .IsUnique(); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateMaj") + .HasColumnType("TEXT"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("Statut") + .HasColumnType("INTEGER"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Utilisateur"); + + b.HasIndex("LivreId", "Utilisateur") + .IsUnique(); + + b.ToTable("StatutsLecture"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Nom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NomNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NomNormalise") + .IsUnique(); + + b.ToTable("Themes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b => + { + b.HasOne("MaBibli.Shared.Entites.NumeroRevue", "NumeroRevue") + .WithMany("Articles") + .HasForeignKey("NumeroRevueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NumeroRevue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany() + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("MaBibli.Shared.Entites.Serie", "Serie") + .WithMany("Elements") + .HasForeignKey("SerieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + + b.Navigation("Serie"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => + { + b.HasOne("MaBibli.Shared.Entites.Auteur", "Auteur") + .WithMany("Livres") + .HasForeignKey("AuteurId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Auteurs") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Auteur"); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Themes") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MaBibli.Shared.Entites.Theme", "Theme") + .WithMany("Livres") + .HasForeignKey("ThemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Livre"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.HasOne("MaBibli.Shared.Entites.Revue", "Revue") + .WithMany("Numeros") + .HasForeignKey("RevueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Revue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Prets") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.HasOne("MaBibli.Shared.Entites.Serie", "SerieParente") + .WithMany("SousSeries") + .HasForeignKey("SerieParenteId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SerieParente"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Statuts") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => + { + b.Navigation("Livres"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => + { + b.Navigation("Auteurs"); + + b.Navigation("Prets"); + + b.Navigation("Statuts"); + + b.Navigation("Themes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.Navigation("Articles"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => + { + b.Navigation("Numeros"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.Navigation("Elements"); + + b.Navigation("SousSeries"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => + { + b.Navigation("Livres"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.cs b/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.cs new file mode 100644 index 0000000..2b732cb --- /dev/null +++ b/MaBibli.Api/Data/Migrations/20260821084601_ImagesEtUnesDesNumeros.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MaBibli.Api.Data.Migrations +{ + /// + public partial class ImagesEtUnesDesNumeros : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CoverUrl", + table: "NumerosRevue", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "ArticlesUne", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + NumeroRevueId = table.Column(type: "INTEGER", nullable: false), + Titre = table.Column(type: "TEXT", nullable: false), + TitreNormalise = table.Column(type: "TEXT", nullable: false), + Position = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ArticlesUne", x => x.Id); + table.ForeignKey( + name: "FK_ArticlesUne_NumerosRevue_NumeroRevueId", + column: x => x.NumeroRevueId, + principalTable: "NumerosRevue", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ArticlesUne_NumeroRevueId_TitreNormalise", + table: "ArticlesUne", + columns: new[] { "NumeroRevueId", "TitreNormalise" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ArticlesUne"); + + migrationBuilder.DropColumn( + name: "CoverUrl", + table: "NumerosRevue"); + } + } +} diff --git a/MaBibli.Api/Data/Migrations/MaBibliDbContextModelSnapshot.cs b/MaBibli.Api/Data/Migrations/MaBibliDbContextModelSnapshot.cs index 4d46347..4b9695d 100644 --- a/MaBibli.Api/Data/Migrations/MaBibliDbContextModelSnapshot.cs +++ b/MaBibli.Api/Data/Migrations/MaBibliDbContextModelSnapshot.cs @@ -1,591 +1,638 @@ -// -using System; -using MaBibli.Api.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace MaBibli.Api.Data.Migrations -{ - [DbContext(typeof(MaBibliDbContext))] - partial class MaBibliDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "10.0.11"); - - modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("CleRegroupement") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Nom") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("NomNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("CleRegroupement") - .IsUnique(); - - b.HasIndex("NomNormalise"); - - b.ToTable("Auteurs"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.BibliographieMasquee", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AuteurId") - .HasColumnType("INTEGER"); - - b.Property("TitreNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Utilisateur") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Utilisateur", "AuteurId"); - - b.HasIndex(new[] { "AuteurId", "Utilisateur", "TitreNormalise" }, "IX_BibliographiesMasquees_Auteur_Utilisateur_Titre") - .IsUnique(); - - b.ToTable("BibliographiesMasquees"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("LivreId") - .HasColumnType("INTEGER"); - - b.Property("Position") - .HasColumnType("INTEGER"); - - b.Property("SerieId") - .HasColumnType("INTEGER"); - - b.Property("Titre") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("LivreId"); - - b.HasIndex(new[] { "SerieId" }, "IX_ElementsSerie_SerieId"); - - b.HasIndex(new[] { "SerieId", "LivreId" }, "IX_ElementsSerie_SerieId_LivreId") - .IsUnique() - .HasFilter("\"LivreId\" IS NOT NULL"); - - b.ToTable("ElementsSerie"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AjoutePar") - .HasColumnType("TEXT"); - - b.Property("CoverUrl") - .HasColumnType("TEXT"); - - b.Property("DateAjout") - .HasColumnType("TEXT"); - - b.Property("Editeur") - .HasColumnType("TEXT"); - - b.Property("Format") - .HasColumnType("INTEGER"); - - b.Property("Isbn") - .HasColumnType("TEXT"); - - b.Property("Titre") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TitreNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TypeDocument") - .HasColumnType("INTEGER"); - - b.Property("UrlNotice") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Isbn"); - - b.HasIndex("TitreNormalise"); - - b.ToTable("Livres"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => - { - b.Property("LivreId") - .HasColumnType("INTEGER"); - - b.Property("AuteurId") - .HasColumnType("INTEGER"); - - b.Property("Position") - .HasColumnType("INTEGER"); - - b.Property("Role") - .HasColumnType("INTEGER"); - - b.HasKey("LivreId", "AuteurId"); - - b.HasIndex("AuteurId"); - - b.ToTable("LivreAuteurs"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.LivreSouhaite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Annee") - .HasColumnType("TEXT"); - - b.Property("Auteur") - .HasColumnType("TEXT"); - - b.Property("AuteurNormalise") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(""); - - b.Property("CoverUrl") - .HasColumnType("TEXT"); - - b.Property("DateAjout") - .HasColumnType("TEXT"); - - b.Property("Editeur") - .HasColumnType("TEXT"); - - b.Property("Isbn") - .HasColumnType("TEXT"); - - b.Property("Note") - .HasColumnType("TEXT"); - - b.Property("Rang") - .HasColumnType("INTEGER"); - - b.Property("Titre") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TitreNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("Utilisateur") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex(new[] { "Utilisateur" }, "IX_LivresSouhaites_Utilisateur"); - - b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "AuteurNormalise" }, "IX_LivresSouhaites_Utilisateur_Oeuvre") - .IsUnique(); - - b.ToTable("LivresSouhaites"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => - { - b.Property("LivreId") - .HasColumnType("INTEGER"); - - b.Property("ThemeId") - .HasColumnType("INTEGER"); - - b.HasKey("LivreId", "ThemeId"); - - b.HasIndex("ThemeId"); - - b.ToTable("LivreThemes"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateAjout") - .HasColumnType("TEXT"); - - b.Property("DateParution") - .HasColumnType("TEXT"); - - b.Property("Note") - .HasColumnType("TEXT"); - - b.Property("Numero") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("NumeroNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("RevueId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("RevueId", "NumeroNormalise") - .IsUnique(); - - b.ToTable("NumerosRevue"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DatePret") - .HasColumnType("TEXT"); - - b.Property("DateRetour") - .HasColumnType("TEXT"); - - b.Property("Emprunteur") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("LivreId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId"); - - b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId_EnCours") - .IsUnique() - .HasFilter("\"DateRetour\" IS NULL"); - - b.ToTable("Prets"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.RapprochementRefuse", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AuteurAId") - .HasColumnType("INTEGER"); - - b.Property("AuteurBId") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("AuteurAId", "AuteurBId") - .IsUnique(); - - b.ToTable("RapprochementsRefuses"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AjoutePar") - .HasColumnType("TEXT"); - - b.Property("DateAjout") - .HasColumnType("TEXT"); - - b.Property("Editeur") - .HasColumnType("TEXT"); - - b.Property("Issn") - .HasColumnType("TEXT"); - - b.Property("Titre") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TitreNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("TitreNormalise") - .IsUnique(); - - b.HasIndex(new[] { "Issn" }, "IX_Revues_Issn") - .IsUnique() - .HasFilter("\"Issn\" IS NOT NULL"); - - b.ToTable("Revues"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("AjoutePar") - .HasColumnType("TEXT"); - - b.Property("DateAjout") - .HasColumnType("TEXT"); - - b.Property("Position") - .HasColumnType("INTEGER"); - - b.Property("SerieParenteId") - .HasColumnType("INTEGER"); - - b.Property("Titre") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("TitreNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("SerieParenteId"); - - b.HasIndex("TitreNormalise") - .IsUnique(); - - b.ToTable("Series"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("DateMaj") - .HasColumnType("TEXT"); - - b.Property("LivreId") - .HasColumnType("INTEGER"); - - b.Property("Statut") - .HasColumnType("INTEGER"); - - b.Property("Utilisateur") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("Utilisateur"); - - b.HasIndex("LivreId", "Utilisateur") - .IsUnique(); - - b.ToTable("StatutsLecture"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Nom") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("NomNormalise") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NomNormalise") - .IsUnique(); - - b.ToTable("Themes"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => - { - b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") - .WithMany() - .HasForeignKey("LivreId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("MaBibli.Shared.Entites.Serie", "Serie") - .WithMany("Elements") - .HasForeignKey("SerieId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Livre"); - - b.Navigation("Serie"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => - { - b.HasOne("MaBibli.Shared.Entites.Auteur", "Auteur") - .WithMany("Livres") - .HasForeignKey("AuteurId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") - .WithMany("Auteurs") - .HasForeignKey("LivreId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Auteur"); - - b.Navigation("Livre"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => - { - b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") - .WithMany("Themes") - .HasForeignKey("LivreId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("MaBibli.Shared.Entites.Theme", "Theme") - .WithMany("Livres") - .HasForeignKey("ThemeId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Livre"); - - b.Navigation("Theme"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => - { - b.HasOne("MaBibli.Shared.Entites.Revue", "Revue") - .WithMany("Numeros") - .HasForeignKey("RevueId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Revue"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => - { - b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") - .WithMany("Prets") - .HasForeignKey("LivreId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Livre"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => - { - b.HasOne("MaBibli.Shared.Entites.Serie", "SerieParente") - .WithMany("SousSeries") - .HasForeignKey("SerieParenteId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("SerieParente"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => - { - b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") - .WithMany("Statuts") - .HasForeignKey("LivreId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Livre"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => - { - b.Navigation("Livres"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => - { - b.Navigation("Auteurs"); - - b.Navigation("Prets"); - - b.Navigation("Statuts"); - - b.Navigation("Themes"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => - { - b.Navigation("Numeros"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => - { - b.Navigation("Elements"); - - b.Navigation("SousSeries"); - }); - - modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => - { - b.Navigation("Livres"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using MaBibli.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace MaBibli.Api.Data.Migrations +{ + [DbContext(typeof(MaBibliDbContext))] + partial class MaBibliDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.11"); + + modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NumeroRevueId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NumeroRevueId", "TitreNormalise") + .IsUnique(); + + b.ToTable("ArticlesUne"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CleRegroupement") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Nom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NomNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CleRegroupement") + .IsUnique(); + + b.HasIndex("NomNormalise"); + + b.ToTable("Auteurs"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.BibliographieMasquee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuteurId") + .HasColumnType("INTEGER"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Utilisateur", "AuteurId"); + + b.HasIndex(new[] { "AuteurId", "Utilisateur", "TitreNormalise" }, "IX_BibliographiesMasquees_Auteur_Utilisateur_Titre") + .IsUnique(); + + b.ToTable("BibliographiesMasquees"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("SerieId") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LivreId"); + + b.HasIndex(new[] { "SerieId" }, "IX_ElementsSerie_SerieId"); + + b.HasIndex(new[] { "SerieId", "LivreId" }, "IX_ElementsSerie_SerieId_LivreId") + .IsUnique() + .HasFilter("\"LivreId\" IS NOT NULL"); + + b.ToTable("ElementsSerie"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("Isbn") + .HasColumnType("TEXT"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TypeDocument") + .HasColumnType("INTEGER"); + + b.Property("UrlNotice") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Isbn"); + + b.HasIndex("TitreNormalise"); + + b.ToTable("Livres"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => + { + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("AuteurId") + .HasColumnType("INTEGER"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.HasKey("LivreId", "AuteurId"); + + b.HasIndex("AuteurId"); + + b.ToTable("LivreAuteurs"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreSouhaite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Annee") + .HasColumnType("TEXT"); + + b.Property("Auteur") + .HasColumnType("TEXT"); + + b.Property("AuteurNormalise") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Isbn") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Rang") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Utilisateur" }, "IX_LivresSouhaites_Utilisateur"); + + b.HasIndex(new[] { "Utilisateur", "TitreNormalise", "AuteurNormalise" }, "IX_LivresSouhaites_Utilisateur_Oeuvre") + .IsUnique(); + + b.ToTable("LivresSouhaites"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => + { + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.HasKey("LivreId", "ThemeId"); + + b.HasIndex("ThemeId"); + + b.ToTable("LivreThemes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("DateParution") + .HasColumnType("TEXT"); + + b.Property("Note") + .HasColumnType("TEXT"); + + b.Property("Numero") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NumeroNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RevueId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevueId", "NumeroNormalise") + .IsUnique(); + + b.ToTable("NumerosRevue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DatePret") + .HasColumnType("TEXT"); + + b.Property("DateRetour") + .HasColumnType("TEXT"); + + b.Property("Emprunteur") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId"); + + b.HasIndex(new[] { "LivreId" }, "IX_Prets_LivreId_EnCours") + .IsUnique() + .HasFilter("\"DateRetour\" IS NULL"); + + b.ToTable("Prets"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.RapprochementRefuse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuteurAId") + .HasColumnType("INTEGER"); + + b.Property("AuteurBId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AuteurAId", "AuteurBId") + .IsUnique(); + + b.ToTable("RapprochementsRefuses"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Editeur") + .HasColumnType("TEXT"); + + b.Property("Issn") + .HasColumnType("TEXT"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("TitreNormalise") + .IsUnique(); + + b.HasIndex(new[] { "Issn" }, "IX_Revues_Issn") + .IsUnique() + .HasFilter("\"Issn\" IS NOT NULL"); + + b.ToTable("Revues"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AjoutePar") + .HasColumnType("TEXT"); + + b.Property("DateAjout") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("SerieParenteId") + .HasColumnType("INTEGER"); + + b.Property("Titre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TitreNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SerieParenteId"); + + b.HasIndex("TitreNormalise") + .IsUnique(); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateMaj") + .HasColumnType("TEXT"); + + b.Property("LivreId") + .HasColumnType("INTEGER"); + + b.Property("Statut") + .HasColumnType("INTEGER"); + + b.Property("Utilisateur") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Utilisateur"); + + b.HasIndex("LivreId", "Utilisateur") + .IsUnique(); + + b.ToTable("StatutsLecture"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Nom") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NomNormalise") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NomNormalise") + .IsUnique(); + + b.ToTable("Themes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ArticleUne", b => + { + b.HasOne("MaBibli.Shared.Entites.NumeroRevue", "NumeroRevue") + .WithMany("Articles") + .HasForeignKey("NumeroRevueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NumeroRevue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.ElementSerie", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany() + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("MaBibli.Shared.Entites.Serie", "Serie") + .WithMany("Elements") + .HasForeignKey("SerieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + + b.Navigation("Serie"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreAuteur", b => + { + b.HasOne("MaBibli.Shared.Entites.Auteur", "Auteur") + .WithMany("Livres") + .HasForeignKey("AuteurId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Auteurs") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Auteur"); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.LivreTheme", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Themes") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MaBibli.Shared.Entites.Theme", "Theme") + .WithMany("Livres") + .HasForeignKey("ThemeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Livre"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.HasOne("MaBibli.Shared.Entites.Revue", "Revue") + .WithMany("Numeros") + .HasForeignKey("RevueId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Revue"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Pret", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Prets") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.HasOne("MaBibli.Shared.Entites.Serie", "SerieParente") + .WithMany("SousSeries") + .HasForeignKey("SerieParenteId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("SerieParente"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.StatutLecture", b => + { + b.HasOne("MaBibli.Shared.Entites.Livre", "Livre") + .WithMany("Statuts") + .HasForeignKey("LivreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Livre"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Auteur", b => + { + b.Navigation("Livres"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Livre", b => + { + b.Navigation("Auteurs"); + + b.Navigation("Prets"); + + b.Navigation("Statuts"); + + b.Navigation("Themes"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.NumeroRevue", b => + { + b.Navigation("Articles"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Revue", b => + { + b.Navigation("Numeros"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Serie", b => + { + b.Navigation("Elements"); + + b.Navigation("SousSeries"); + }); + + modelBuilder.Entity("MaBibli.Shared.Entites.Theme", b => + { + b.Navigation("Livres"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MaBibli.Api/Data/ServiceRenormalisation.cs b/MaBibli.Api/Data/ServiceRenormalisation.cs index 7ebee62..0930941 100644 --- a/MaBibli.Api/Data/ServiceRenormalisation.cs +++ b/MaBibli.Api/Data/ServiceRenormalisation.cs @@ -1,4 +1,5 @@ using MaBibli.Shared.Entites; +using MaBibli.Shared.Isbn; using MaBibli.Shared.Textes; using Microsoft.EntityFrameworkCore; @@ -89,6 +90,19 @@ public sealed class ServiceRenormalisation(MaBibliDbContext db, ILogger n.RecalculerFormes(), n => $"le numéro {n.Numero}"); + // ⚠️ L'ISSN ne peut PAS passer par Renormaliser : sa clé est nullable — la plupart des + // revues n'en ont pas — et un jeu de clés confondrait tous ces NULL en une seule. + // Son unicité est d'ailleurs un index PARTIEL, précisément pour cette raison. + corriges += CanoniserLesIssn(revues); + + var articles = await db.ArticlesUne.OrderBy(a => a.Id).ToListAsync(ct); + corriges += Renormaliser( + articles, + a => Cle(a.NumeroRevueId.ToString(), a.TitreNormalise), + a => Cle(a.NumeroRevueId.ToString(), NormalisationTexte.Normaliser(a.Titre)), + a => a.RecalculerFormes(), + a => $"l'article « {a.Titre} »"); + var themes = await db.Themes.OrderBy(t => t.Id).ToListAsync(ct); corriges += Renormaliser( themes, @@ -155,6 +169,53 @@ public sealed class ServiceRenormalisation(MaBibliDbContext db, ILogger + /// Range les ISSN existants sous leur forme à tiret, sans jamais créer de collision. + /// + /// + /// Rattrape les revues saisies à la main avant que la canonisation n'existe : « 24666718 » + /// devient « 2466-6718 », donc rapprochable du code-barres et de bib.issn. + /// + /// ⚠️ Une revue dont l'ISSN canonique est déjà celui d'une autre garde le sien : la + /// fusion de deux fiches n'est pas une opération de démarrage, et l'unicité partielle + /// ferait échouer le lancement du serveur. + /// + /// + private int CanoniserLesIssn(IReadOnlyList revues) + { + var porteuses = revues.Where(r => r.Issn is not null).ToList(); + var occupes = new HashSet(porteuses.Select(r => r.Issn!), StringComparer.Ordinal); + var corriges = 0; + + foreach (var revue in porteuses) + { + var actuel = revue.Issn!; + var voulu = FormatageIssn.Canonique(actuel); + + if (voulu is null || voulu == actuel) + { + continue; + } + + occupes.Remove(actuel); + + if (!occupes.Add(voulu)) + { + occupes.Add(actuel); + logger.LogWarning( + "Renormalisation : la revue « {Titre} » garde l'ISSN {Issn}, sa forme à tiret est déjà prise.", + revue.Titre, + actuel); + continue; + } + + revue.Issn = voulu; + corriges++; + } + + return corriges; + } + /// Clé composite comparable, les composants ne pouvant pas contenir de NUL. private static string Cle(params string?[] composants) => string.Join('\0', composants); diff --git a/MaBibli.Api/Endpoints/RevuesEndpoints.cs b/MaBibli.Api/Endpoints/RevuesEndpoints.cs index 9fa5a89..59365d2 100644 --- a/MaBibli.Api/Endpoints/RevuesEndpoints.cs +++ b/MaBibli.Api/Endpoints/RevuesEndpoints.cs @@ -70,6 +70,18 @@ public static class RevuesEndpoints .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound); + groupe.MapPut("/numeros/{numeroId:int}", async ( + int numeroId, AjoutNumeroRevue saisie, IServiceRevues service, CancellationToken ct) => + Repondre(await service.ModifierNumeroAsync(numeroId, saisie, ct))) + .WithName("ModifierNumeroRevue") + .WithSummary("Modifie un numéro : numéro, parution, note, couverture et articles à la une.") + .WithDescription( + "La couverture d'un numéro et sa une ne viennent d'aucune source — l'ISSN " + + "désigne la revue, pas la parution — et se saisissent donc après coup.") + .Produces() + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status404NotFound); + groupe.MapDelete("/numeros/{numeroId:int}", async ( int numeroId, IServiceRevues service, CancellationToken ct) => await service.RetirerNumeroAsync(numeroId, ct) diff --git a/MaBibli.Api/Services/Couvertures/ServiceCouvertures.cs b/MaBibli.Api/Services/Couvertures/ServiceCouvertures.cs index 3b9c1b9..bbb11d0 100644 --- a/MaBibli.Api/Services/Couvertures/ServiceCouvertures.cs +++ b/MaBibli.Api/Services/Couvertures/ServiceCouvertures.cs @@ -35,7 +35,8 @@ public interface IServiceCouvertures /// /// /// -/// L'URL doit déjà exister en base comme couverture d'un livre ou d'une envie. Le proxy +/// L'URL doit déjà exister en base comme couverture d'un livre, d'une envie ou d'un +/// numéro de revue. Le proxy /// ne va donc jamais chercher une URL choisie au moment de l'appel : seulement une valeur que /// l'application détient déjà. /// @@ -115,9 +116,16 @@ public sealed class ServiceCouvertures(HttpClient http, MaBibliDbContext db) : I /// L'URL est-elle déjà enregistrée comme couverture ? Comparaison exacte : c'est la /// valeur stockée qui autorise, pas une ressemblance. /// + /// + /// ⚠️ Les numéros de revue en font partie depuis qu'ils portent une couverture. Les + /// oublier ici aurait produit exactement le défaut que ce service existe pour corriger : + /// une image qui s'affiche en ligne et jamais hors-ligne, l'appel de mise en cache étant + /// refusé par le relais. + /// private async Task EstConnueAsync(string url, CancellationToken ct) => await db.Livres.AnyAsync(l => l.CoverUrl == url, ct) - || await db.LivresSouhaites.AnyAsync(s => s.CoverUrl == url, ct); + || await db.LivresSouhaites.AnyAsync(s => s.CoverUrl == url, ct) + || await db.NumerosRevue.AnyAsync(n => n.CoverUrl == url, ct); /// /// Lit au plus octets, et rend null si le corps dépasse. diff --git a/MaBibli.Api/Services/Revues/ServiceRevues.cs b/MaBibli.Api/Services/Revues/ServiceRevues.cs index 54da00d..6c7fb3a 100644 --- a/MaBibli.Api/Services/Revues/ServiceRevues.cs +++ b/MaBibli.Api/Services/Revues/ServiceRevues.cs @@ -1,6 +1,7 @@ using MaBibli.Api.Data; using MaBibli.Shared.Dtos; using MaBibli.Shared.Entites; +using MaBibli.Shared.Isbn; using MaBibli.Shared.Textes; using Microsoft.EntityFrameworkCore; @@ -36,6 +37,18 @@ public interface IServiceRevues Task AjouterNumeroAsync( int revueId, AjoutNumeroRevue saisie, CancellationToken ct = default); + /// + /// Modifie un numéro déjà recensé : son numéro, sa parution, sa note, sa couverture et ses + /// articles à la une. + /// + /// + /// Sans ce point d'entrée, la couverture et les articles ne se seraient saisis qu'à la + /// création — c'est-à-dire jamais, puisqu'on ajoute un numéro le jour où on l'achète et + /// qu'on en recopie la une plus tard. + /// + Task ModifierNumeroAsync( + int numeroId, AjoutNumeroRevue saisie, CancellationToken ct = default); + Task RetirerNumeroAsync(int numeroId, CancellationToken ct = default); } @@ -57,7 +70,7 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues { var revues = await db.Revues .AsNoTracking() - .Include(r => r.Numeros) + .Include(r => r.Numeros).ThenInclude(n => n.Articles) .OrderBy(r => r.TitreNormalise) .ToListAsync(ct); @@ -73,7 +86,10 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues return ResultatRevue.Invalide("Le titre de la revue est obligatoire."); } - var issn = Vide(saisie.Issn); + // ⚠️ Canonisé AVANT la recherche, pas seulement à l'écriture : « 24666718 » et + // « 2466-6718 » désignent la même revue, et sans cela le scan suivant en créerait une + // seconde à côté de celle saisie à la main. + var issn = FormatageIssn.Canonique(saisie.Issn); var normalise = NormalisationTexte.Normaliser(titre); // ⚠️ « Créer OU retrouver » et non « créer » : le flux du code-barres 977 rescanne @@ -81,7 +97,7 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues // à comprendre qu'il doit aller chercher la fiche lui-même — alors que le geste est // toujours le même : « voici un numéro de plus ». var existante = await db.Revues - .Include(r => r.Numeros) + .Include(r => r.Numeros).ThenInclude(n => n.Articles) .FirstOrDefaultAsync( r => (issn != null && r.Issn == issn) || r.TitreNormalise == normalise, ct); @@ -117,7 +133,7 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues public async Task ModifierAsync( int id, EnregistrementRevue saisie, CancellationToken ct = default) { - var revue = await db.Revues.Include(r => r.Numeros).FirstOrDefaultAsync(r => r.Id == id, ct); + var revue = await db.Revues.Include(r => r.Numeros).ThenInclude(n => n.Articles).FirstOrDefaultAsync(r => r.Id == id, ct); if (revue is null) { return ResultatRevue.Introuvable; @@ -135,7 +151,7 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues return ResultatRevue.Invalide($"La revue « {titre} » existe déjà."); } - var issn = Vide(saisie.Issn); + var issn = FormatageIssn.Canonique(saisie.Issn); if (issn is not null && await db.Revues.AnyAsync(r => r.Id != id && r.Issn == issn, ct)) { return ResultatRevue.Invalide($"L'ISSN {issn} est déjà celui d'une autre revue."); @@ -168,7 +184,7 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues public async Task AjouterNumeroAsync( int revueId, AjoutNumeroRevue saisie, CancellationToken ct = default) { - var revue = await db.Revues.Include(r => r.Numeros).FirstOrDefaultAsync(r => r.Id == revueId, ct); + var revue = await db.Revues.Include(r => r.Numeros).ThenInclude(n => n.Articles).FirstOrDefaultAsync(r => r.Id == revueId, ct); if (revue is null) { return ResultatRevue.Introuvable; @@ -199,19 +215,104 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues Numero = numero, DateParution = saisie.DateParution, Note = Vide(saisie.Note), + CoverUrl = Vide(saisie.CoverUrl), DateAjout = DateTime.UtcNow, }; ligne.RecalculerFormes(); + RemplacerArticles(ligne, saisie.Articles); db.NumerosRevue.Add(ligne); await db.SaveChangesAsync(ct); - var relue = await db.Revues.AsNoTracking().Include(r => r.Numeros) + var relue = await db.Revues.AsNoTracking().Include(r => r.Numeros).ThenInclude(n => n.Articles) .FirstAsync(r => r.Id == revueId, ct); return ResultatRevue.Ok(Projeter(relue)); } + public async Task ModifierNumeroAsync( + int numeroId, AjoutNumeroRevue saisie, CancellationToken ct = default) + { + var ligne = await db.NumerosRevue + .Include(n => n.Articles) + .FirstOrDefaultAsync(n => n.Id == numeroId, ct); + + if (ligne is null) + { + return ResultatRevue.Introuvable; + } + + var numero = saisie.Numero?.Trim(); + if (string.IsNullOrWhiteSpace(numero)) + { + return ResultatRevue.Invalide("Le numéro est obligatoire."); + } + + var normalise = NormalisationTexte.Normaliser(numero); + if (await db.NumerosRevue.AnyAsync( + n => n.Id != numeroId && n.RevueId == ligne.RevueId && n.NumeroNormalise == normalise, ct)) + { + return ResultatRevue.Invalide($"Le numéro {numero} est déjà dans votre collection."); + } + + if (saisie.DateParution is { } parution && parution.Date > DateTime.UtcNow.Date.AddDays(1)) + { + return ResultatRevue.Invalide("La date de parution ne peut pas être dans le futur."); + } + + ligne.Numero = numero; + ligne.DateParution = saisie.DateParution; + ligne.Note = Vide(saisie.Note); + ligne.CoverUrl = Vide(saisie.CoverUrl); + ligne.RecalculerFormes(); + RemplacerArticles(ligne, saisie.Articles); + + await db.SaveChangesAsync(ct); + + var relue = await db.Revues.AsNoTracking().Include(r => r.Numeros).ThenInclude(n => n.Articles) + .FirstAsync(r => r.Id == ligne.RevueId, ct); + + return ResultatRevue.Ok(Projeter(relue)); + } + + /// + /// Remplace les articles à la une du numéro par ceux de la saisie. + /// + /// + /// ⚠️ Remplacement et non fusion : la ligne de saisie est la liste. Un titre effacé + /// du champ doit disparaître, comme pour les thèmes d'un livre. + /// + /// Le dédoublonnage a déjà eu lieu à la saisie (ListeSaisie), mais il est refait ici + /// sur la forme normalisée : c'est l'unicité (NumeroRevueId, TitreNormalise) qui est + /// en jeu, et un appel direct à l'API n'est pas passé par le champ texte. + /// + /// + private void RemplacerArticles(NumeroRevue numero, IReadOnlyList? titres) + { + if (numero.Articles.Count > 0) + { + db.ArticlesUne.RemoveRange(numero.Articles); + numero.Articles.Clear(); + } + + var vus = new HashSet(StringComparer.Ordinal); + var position = 0; + + foreach (var titre in titres ?? []) + { + var article = new ArticleUne { NumeroRevue = numero, Titre = titre ?? string.Empty }; + article.RecalculerFormes(); + + if (article.TitreNormalise.Length == 0 || !vus.Add(article.TitreNormalise)) + { + continue; + } + + article.Position = position++; + numero.Articles.Add(article); + } + } + public async Task RetirerNumeroAsync(int numeroId, CancellationToken ct = default) { var numero = await db.NumerosRevue.FirstOrDefaultAsync(n => n.Id == numeroId, ct); @@ -249,6 +350,12 @@ public sealed class ServiceRevues(MaBibliDbContext db) : IServiceRevues Numero = n.Numero, DateParution = n.DateParution, Note = n.Note, + CoverUrl = n.CoverUrl, + Articles = n.Articles + .OrderBy(a => a.Position) + .ThenBy(a => a.Id) + .Select(a => a.Titre) + .ToList(), }) .ToList(), }; diff --git a/MaBibli.Client/Composants/ChampsNumeroRevue.razor b/MaBibli.Client/Composants/ChampsNumeroRevue.razor new file mode 100644 index 0000000..f8a4071 --- /dev/null +++ b/MaBibli.Client/Composants/ChampsNumeroRevue.razor @@ -0,0 +1,82 @@ +@* + Les champs d'un numéro de revue, partagés par l'ajout et la modification. + + Un seul exemplaire, et pas deux blocs jumeaux : le formulaire d'ajout et celui de + modification doivent proposer exactement les mêmes champs, sinon une couverture ou une une + saisies à la création ne seraient plus modifiables ensuite — ce qui est précisément le cas + d'usage (on ajoute un numéro le jour où on l'achète, on en recopie la une plus tard). +*@ +
+ + + + + +
+ +@* + ⚠️ Séparateur : le POINT-VIRGULE, et pas la virgule des thèmes de livres. Un titre d'article + en contient souvent (« Ukraine, deux ans après »), et la virgule le couperait en deux. +*@ + + +@* + ⚠️ Une URL, pas une photo. Aucune source ne donne la couverture d'un NUMÉRO : l'ISSN désigne + la revue, pas la parution. Et le projet ne stocke aucun fichier — « ebooks : fiches + uniquement » vaut ici aussi. +*@ + + +@code { + [Parameter, EditorRequired] public AjoutNumeroRevue Saisie { get; set; } = default!; + + /// + /// Date affichée par le champ, en local. + /// + /// + /// ⚠️ Le <input type="date"> produit une date sans fuseau. Elle est déclarée + /// locale ici et convertie en UTC, comme toutes les dates du projet : sans cela la parution + /// se décale d'un jour pour la moitié du globe. La conversion se fait dans le composant + /// pour que les deux écrans qui s'en servent ne puissent pas l'oublier chacun de leur côté. + /// + private DateTime? Parution + { + get => Saisie.DateParution?.ToLocalTime().Date; + set => Saisie.DateParution = value is { } date + ? DateTime.SpecifyKind(date, DateTimeKind.Local).ToUniversalTime() + : null; + } + + [Parameter] public EventCallback OnEntree { get; set; } + + private async Task SurTouche(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + await OnEntree.InvokeAsync(e); + } + } +} diff --git a/MaBibli.Client/Pages/AjoutRevue.razor b/MaBibli.Client/Pages/AjoutRevue.razor index f1b9599..ec9d73b 100644 --- a/MaBibli.Client/Pages/AjoutRevue.razor +++ b/MaBibli.Client/Pages/AjoutRevue.razor @@ -39,7 +39,7 @@