using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MaBibli.Api.Data.Migrations
{
///
/// Sort le statut de lecture et l'auteur de la table Livres.
///
///
/// Ce que deviennent les données existantes.
///
/// -
/// Livres.Statut était commun à tout le foyer. Il est rattaché à
/// AjoutePar, la seule personne que la base associe au livre. Les statuts des
/// livres sans AjoutePar (saisis sans identité) sont perdus : les attribuer à
/// quelqu'un serait une invention, et les garder pour tout le monde serait exactement le
/// défaut que cette migration corrige. Les trois valeurs sont reprises telles quelles, « À
/// lire » compris : c'est ce que l'ancienne interface affichait, et le taire changerait ce
/// que l'utilisateur voit.
///
/// -
/// Livres.Auteur devient une ligne de Auteurs par valeur distincte, plus un lien
/// en position 0. Le regroupement se fait ici sur une simple mise en minuscules — SQLite ne
/// sait pas retirer les accents. La normalisation complète, et la fusion des variantes qu'elle
/// révèle, sont faites en C# au démarrage suivant (voir ServiceRenormalisation).
///
///
/// L'ordre des opérations compte : tout est recopié avant que les anciennes colonnes
/// ne soient supprimées.
///
public partial class StatutPersonnelEtTableAuteurs : Migration
{
///
protected override void Up(MigrationBuilder migrationBuilder)
{
// Mise à l'abri du contenu des deux colonnes condamnées. Supprimer une colonne sous
// SQLite reconstruit la table : on le fait pendant que Livres n'a pas encore
// d'enfants à préserver, et on recopie ensuite depuis cette table de transit.
migrationBuilder.Sql(
"""
CREATE TABLE _RepriseLivres AS
SELECT Id, Auteur, Statut FROM Livres;
""");
migrationBuilder.DropColumn(
name: "Auteur",
table: "Livres");
migrationBuilder.DropColumn(
name: "Statut",
table: "Livres");
migrationBuilder.AddColumn(
name: "TitreNormalise",
table: "Livres",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "Auteurs",
columns: table => new
{
Id = table.Column(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Nom = table.Column(type: "TEXT", nullable: false),
NomNormalise = table.Column(type: "TEXT", nullable: false),
CleRegroupement = table.Column(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Auteurs", x => x.Id);
});
migrationBuilder.CreateTable(
name: "RapprochementsRefuses",
columns: table => new
{
Id = table.Column(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
AuteurAId = table.Column(type: "INTEGER", nullable: false),
AuteurBId = table.Column(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RapprochementsRefuses", x => x.Id);
});
migrationBuilder.CreateTable(
name: "StatutsLecture",
columns: table => new
{
Id = table.Column(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
LivreId = table.Column(type: "INTEGER", nullable: false),
Utilisateur = table.Column(type: "TEXT", nullable: false),
Statut = table.Column(type: "INTEGER", nullable: false),
DateMaj = table.Column(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StatutsLecture", x => x.Id);
table.ForeignKey(
name: "FK_StatutsLecture_Livres_LivreId",
column: x => x.LivreId,
principalTable: "Livres",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "LivreAuteurs",
columns: table => new
{
LivreId = table.Column(type: "INTEGER", nullable: false),
AuteurId = table.Column(type: "INTEGER", nullable: false),
Position = table.Column(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LivreAuteurs", x => new { x.LivreId, x.AuteurId });
table.ForeignKey(
name: "FK_LivreAuteurs_Auteurs_AuteurId",
column: x => x.AuteurId,
principalTable: "Auteurs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_LivreAuteurs_Livres_LivreId",
column: x => x.LivreId,
principalTable: "Livres",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Livres_TitreNormalise",
table: "Livres",
column: "TitreNormalise");
migrationBuilder.CreateIndex(
name: "IX_Auteurs_CleRegroupement",
table: "Auteurs",
column: "CleRegroupement",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Auteurs_NomNormalise",
table: "Auteurs",
column: "NomNormalise");
migrationBuilder.CreateIndex(
name: "IX_LivreAuteurs_AuteurId",
table: "LivreAuteurs",
column: "AuteurId");
migrationBuilder.CreateIndex(
name: "IX_RapprochementsRefuses_AuteurAId_AuteurBId",
table: "RapprochementsRefuses",
columns: new[] { "AuteurAId", "AuteurBId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_StatutsLecture_LivreId_Utilisateur",
table: "StatutsLecture",
columns: new[] { "LivreId", "Utilisateur" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_StatutsLecture_Utilisateur",
table: "StatutsLecture",
column: "Utilisateur");
// ── Reprise des données depuis la table de transit ──────────────────────────────
// Le titre normalisé n'est qu'approché ici (lower() de SQLite ne touche pas aux
// accents) ; le passage C# du démarrage le recalculera correctement.
migrationBuilder.Sql("UPDATE Livres SET TitreNormalise = lower(trim(Titre));");
// Une fiche auteur par valeur distincte, à la casse près. Le nom d'affichage retenu
// est le premier dans l'ordre alphabétique : arbitraire, mais déterministe, et
// l'utilisateur peut le corriger depuis n'importe quel livre.
migrationBuilder.Sql(
"""
INSERT INTO Auteurs (Nom, NomNormalise, CleRegroupement)
SELECT min(trim(Auteur)), lower(trim(Auteur)), lower(trim(Auteur))
FROM _RepriseLivres
WHERE Auteur IS NOT NULL AND trim(Auteur) <> ''
GROUP BY lower(trim(Auteur));
""");
migrationBuilder.Sql(
"""
INSERT INTO LivreAuteurs (LivreId, AuteurId, Position)
SELECT r.Id, a.Id, 0
FROM _RepriseLivres r
JOIN Auteurs a ON a.CleRegroupement = lower(trim(r.Auteur))
JOIN Livres l ON l.Id = r.Id
WHERE r.Auteur IS NOT NULL AND trim(r.Auteur) <> '';
""");
// Le statut commun devient celui de la personne qui a saisi le livre. Sans AjoutePar,
// il n'y a personne à qui l'attribuer : la ligne n'est pas créée.
migrationBuilder.Sql(
"""
INSERT INTO StatutsLecture (LivreId, Utilisateur, Statut, DateMaj)
SELECT l.Id, trim(l.AjoutePar), r.Statut, l.DateAjout
FROM Livres l
JOIN _RepriseLivres r ON r.Id = l.Id
WHERE l.AjoutePar IS NOT NULL AND trim(l.AjoutePar) <> '';
""");
migrationBuilder.Sql("DROP TABLE _RepriseLivres;");
}
///
///
/// Retour en arrière. Une partie de l'information est perdue par nature : l'ancien
/// modèle n'a qu'une case pour un auteur et une pour un statut. On y remet le premier
/// auteur et le statut de AjoutePar ; les co-auteurs et les statuts des autres
/// membres du foyer disparaissent.
///
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn(
name: "Auteur",
table: "Livres",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn(
name: "Statut",
table: "Livres",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(
"""
UPDATE Livres SET Auteur = (
SELECT a.Nom FROM LivreAuteurs la
JOIN Auteurs a ON a.Id = la.AuteurId
WHERE la.LivreId = Livres.Id
ORDER BY la.Position LIMIT 1);
""");
migrationBuilder.Sql(
"""
UPDATE Livres SET Statut = coalesce((
SELECT s.Statut FROM StatutsLecture s
WHERE s.LivreId = Livres.Id AND s.Utilisateur = trim(Livres.AjoutePar)
LIMIT 1), 0);
""");
migrationBuilder.DropTable(
name: "LivreAuteurs");
migrationBuilder.DropTable(
name: "RapprochementsRefuses");
migrationBuilder.DropTable(
name: "StatutsLecture");
migrationBuilder.DropTable(
name: "Auteurs");
migrationBuilder.DropIndex(
name: "IX_Livres_TitreNormalise",
table: "Livres");
migrationBuilder.DropColumn(
name: "TitreNormalise",
table: "Livres");
}
}
}