Make AddClipAdminMenuVisible migration idempotent

Rewrites DDL to use IF NOT EXISTS / DROP IF EXISTS and DROP COLUMN IF EXISTS
so the migration can be re-run safely against a DB that already has the schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-06-29 09:07:33 +02:00
parent b53c7fb736
commit 1ab26ec2fd
@@ -12,111 +12,89 @@ namespace Backend.Migrations
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ClipAdminMenuVisible",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.Sql("""
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "ClipAdminMenuVisible" boolean NOT NULL DEFAULT FALSE;
migrationBuilder.AddColumn<string>(
name: "ShowactApplicationDisabledMessage",
table: "SiteSettings",
type: "character varying(240)",
maxLength: 240,
nullable: false,
defaultValue: "");
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "ShowactApplicationDisabledMessage" character varying(240) NOT NULL DEFAULT '';
migrationBuilder.AddColumn<bool>(
name: "ShowactApplicationsEnabled",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: false);
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "ShowactApplicationsEnabled" boolean NOT NULL DEFAULT FALSE;
migrationBuilder.AddColumn<string>(
name: "ShowactsContent",
table: "SiteSettings",
type: "text",
nullable: false,
defaultValue: "");
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "ShowactsContent" text NOT NULL DEFAULT '';
migrationBuilder.AddColumn<string>(
name: "ShowactsUrl",
table: "SiteSettings",
type: "character varying(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "ShowactsUrl" character varying(400) NOT NULL DEFAULT '';
migrationBuilder.AddColumn<bool>(
name: "SponsorsVisible",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: true);
ALTER TABLE "SiteSettings"
ADD COLUMN IF NOT EXISTS "SponsorsVisible" boolean NOT NULL DEFAULT TRUE;
""");
migrationBuilder.CreateTable(
name: "ShowactApplications",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
SeasonId = table.Column<int>(type: "integer", nullable: false),
ArtistName = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
ContactEmail = table.Column<string>(type: "character varying(180)", maxLength: 180, nullable: false),
ContactDiscord = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
PlatformUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
PerformanceType = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
TechnicalNotes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
ReferenceUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
ReviewNote = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
ReviewedByTwitchId = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: true),
CreatedFromIp = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
UserAgent = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
ReviewedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ShowactApplications", x => x.Id);
table.ForeignKey(
name: "FK_ShowactApplications_Seasons_SeasonId",
column: x => x.SeasonId,
principalTable: "Seasons",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.Sql("""
CREATE TABLE IF NOT EXISTS "ShowactApplications" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"SeasonId" integer NOT NULL,
"ArtistName" character varying(120) NOT NULL,
"ContactEmail" character varying(180) NOT NULL,
"ContactDiscord" character varying(120) NOT NULL,
"PlatformUrl" character varying(500) NOT NULL,
"PerformanceType" character varying(80) NOT NULL,
"Description" character varying(1000) NOT NULL,
"TechnicalNotes" character varying(1000) NOT NULL,
"ReferenceUrl" character varying(500) NOT NULL,
"Status" character varying(20) NOT NULL,
"ReviewNote" character varying(500) NULL,
"ReviewedByTwitchId" character varying(120) NULL,
"CreatedFromIp" character varying(80) NOT NULL,
"UserAgent" character varying(400) NOT NULL,
"CreatedAt" timestamp with time zone NOT NULL,
"ReviewedAt" timestamp with time zone NULL
);
migrationBuilder.CreateTable(
name: "Sponsors",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
SeasonId = table.Column<int>(type: "integer", nullable: false),
Name = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
WebsiteUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
LogoUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
Description = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
Tier = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
SortOrder = table.Column<int>(type: "integer", nullable: false),
IsVisible = table.Column<bool>(type: "boolean", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Sponsors", x => x.Id);
table.ForeignKey(
name: "FK_Sponsors_Seasons_SeasonId",
column: x => x.SeasonId,
principalTable: "Seasons",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
CREATE TABLE IF NOT EXISTS "Sponsors" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
"SeasonId" integer NOT NULL,
"Name" character varying(120) NOT NULL,
"WebsiteUrl" character varying(500) NOT NULL,
"LogoUrl" character varying(500) NOT NULL,
"Description" character varying(500) NOT NULL,
"Tier" character varying(80) NOT NULL,
"SortOrder" integer NOT NULL,
"IsVisible" boolean NOT NULL,
"CreatedAt" timestamp with time zone NOT NULL,
"UpdatedAt" timestamp with time zone NULL
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'FK_ShowactApplications_Seasons_SeasonId'
) THEN
ALTER TABLE "ShowactApplications"
ADD CONSTRAINT "FK_ShowactApplications_Seasons_SeasonId"
FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id")
ON DELETE CASCADE;
END IF;
END $$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'FK_Sponsors_Seasons_SeasonId'
) THEN
ALTER TABLE "Sponsors"
ADD CONSTRAINT "FK_Sponsors_Seasons_SeasonId"
FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id")
ON DELETE CASCADE;
END IF;
END $$;
""");
migrationBuilder.UpdateData(
table: "SiteSettings",
@@ -125,49 +103,43 @@ namespace Backend.Migrations
columns: new[] { "ClipAdminMenuVisible", "ShowactApplicationDisabledMessage", "ShowactsContent", "ShowactsUrl", "SponsorsVisible", "WorkflowRulesJson" },
values: new object[] { true, "Showact-Bewerbungen sind aktuell geschlossen.", "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", "https://vtuber-star-awards.de/showacts", true, "[{\"key\":\"max_finalists_per_category\",\"label\":\"Finale Kandidat:innen pro Kategorie\",\"enabled\":true,\"limit\":4,\"mode\":\"block\",\"description\":\"Begrenzt, wie viele finale Kandidat:innen in einer Kategorie vorbereitet werden.\"},{\"key\":\"max_candidate_appearances\",\"label\":\"Kandidaturen pro Person\",\"enabled\":true,\"limit\":2,\"mode\":\"warn\",\"description\":\"Warnt oder blockiert, wenn dieselbe Person in zu vielen Kategorien final auftaucht.\"},{\"key\":\"max_winner_placements\",\"label\":\"Gewinnerpl\\u00E4tze pro Person\",\"enabled\":true,\"limit\":1,\"mode\":\"block\",\"description\":\"Verhindert oder warnt, wenn dieselbe Person mehrfach als Gewinner:in gesetzt wird.\"},{\"key\":\"winner_requires_clip\",\"label\":\"Gewinner braucht Clip-Link\",\"enabled\":true,\"limit\":1,\"mode\":\"block\",\"description\":\"Verhindert oder warnt, wenn ein Gewinner ohne gepflegten YouTube-/Twitch-Clip gesetzt wird.\"}]" });
migrationBuilder.CreateIndex(
name: "IX_ShowactApplications_SeasonId_Status",
table: "ShowactApplications",
columns: new[] { "SeasonId", "Status" });
migrationBuilder.Sql("""
CREATE INDEX IF NOT EXISTS "IX_ShowactApplications_SeasonId_Status"
ON "ShowactApplications" ("SeasonId", "Status");
migrationBuilder.CreateIndex(
name: "IX_Sponsors_SeasonId_IsVisible_SortOrder",
table: "Sponsors",
columns: new[] { "SeasonId", "IsVisible", "SortOrder" });
CREATE INDEX IF NOT EXISTS "IX_Sponsors_SeasonId_IsVisible_SortOrder"
ON "Sponsors" ("SeasonId", "IsVisible", "SortOrder");
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ShowactApplications");
migrationBuilder.Sql("""
DROP TABLE IF EXISTS "ShowactApplications";
migrationBuilder.DropTable(
name: "Sponsors");
DROP TABLE IF EXISTS "Sponsors";
""");
migrationBuilder.DropColumn(
name: "ClipAdminMenuVisible",
table: "SiteSettings");
migrationBuilder.Sql("""
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "ClipAdminMenuVisible";
migrationBuilder.DropColumn(
name: "ShowactApplicationDisabledMessage",
table: "SiteSettings");
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "ShowactApplicationDisabledMessage";
migrationBuilder.DropColumn(
name: "ShowactApplicationsEnabled",
table: "SiteSettings");
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "ShowactApplicationsEnabled";
migrationBuilder.DropColumn(
name: "ShowactsContent",
table: "SiteSettings");
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "ShowactsContent";
migrationBuilder.DropColumn(
name: "ShowactsUrl",
table: "SiteSettings");
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "ShowactsUrl";
migrationBuilder.DropColumn(
name: "SponsorsVisible",
table: "SiteSettings");
ALTER TABLE "SiteSettings"
DROP COLUMN IF EXISTS "SponsorsVisible";
""");
migrationBuilder.UpdateData(
table: "SiteSettings",