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:
@@ -12,111 +12,89 @@ namespace Backend.Migrations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.AddColumn<bool>(
|
migrationBuilder.Sql("""
|
||||||
name: "ClipAdminMenuVisible",
|
ALTER TABLE "SiteSettings"
|
||||||
table: "SiteSettings",
|
ADD COLUMN IF NOT EXISTS "ClipAdminMenuVisible" boolean NOT NULL DEFAULT FALSE;
|
||||||
type: "boolean",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: false);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactApplicationDisabledMessage",
|
ADD COLUMN IF NOT EXISTS "ShowactApplicationDisabledMessage" character varying(240) NOT NULL DEFAULT '';
|
||||||
table: "SiteSettings",
|
|
||||||
type: "character varying(240)",
|
|
||||||
maxLength: 240,
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<bool>(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactApplicationsEnabled",
|
ADD COLUMN IF NOT EXISTS "ShowactApplicationsEnabled" boolean NOT NULL DEFAULT FALSE;
|
||||||
table: "SiteSettings",
|
|
||||||
type: "boolean",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: false);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactsContent",
|
ADD COLUMN IF NOT EXISTS "ShowactsContent" text NOT NULL DEFAULT '';
|
||||||
table: "SiteSettings",
|
|
||||||
type: "text",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactsUrl",
|
ADD COLUMN IF NOT EXISTS "ShowactsUrl" character varying(400) NOT NULL DEFAULT '';
|
||||||
table: "SiteSettings",
|
|
||||||
type: "character varying(400)",
|
|
||||||
maxLength: 400,
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: "");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<bool>(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "SponsorsVisible",
|
ADD COLUMN IF NOT EXISTS "SponsorsVisible" boolean NOT NULL DEFAULT TRUE;
|
||||||
table: "SiteSettings",
|
""");
|
||||||
type: "boolean",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.Sql("""
|
||||||
name: "ShowactApplications",
|
CREATE TABLE IF NOT EXISTS "ShowactApplications" (
|
||||||
columns: table => new
|
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
{
|
"SeasonId" integer NOT NULL,
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
"ArtistName" character varying(120) NOT NULL,
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
"ContactEmail" character varying(180) NOT NULL,
|
||||||
SeasonId = table.Column<int>(type: "integer", nullable: false),
|
"ContactDiscord" character varying(120) NOT NULL,
|
||||||
ArtistName = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
"PlatformUrl" character varying(500) NOT NULL,
|
||||||
ContactEmail = table.Column<string>(type: "character varying(180)", maxLength: 180, nullable: false),
|
"PerformanceType" character varying(80) NOT NULL,
|
||||||
ContactDiscord = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
"Description" character varying(1000) NOT NULL,
|
||||||
PlatformUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
"TechnicalNotes" character varying(1000) NOT NULL,
|
||||||
PerformanceType = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
"ReferenceUrl" character varying(500) NOT NULL,
|
||||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
"Status" character varying(20) NOT NULL,
|
||||||
TechnicalNotes = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: false),
|
"ReviewNote" character varying(500) NULL,
|
||||||
ReferenceUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
"ReviewedByTwitchId" character varying(120) NULL,
|
||||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
"CreatedFromIp" character varying(80) NOT NULL,
|
||||||
ReviewNote = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
"UserAgent" character varying(400) NOT NULL,
|
||||||
ReviewedByTwitchId = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: true),
|
"CreatedAt" timestamp with time zone NOT NULL,
|
||||||
CreatedFromIp = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
"ReviewedAt" timestamp with time zone NULL
|
||||||
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.CreateTable(
|
CREATE TABLE IF NOT EXISTS "Sponsors" (
|
||||||
name: "Sponsors",
|
"Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||||
columns: table => new
|
"SeasonId" integer NOT NULL,
|
||||||
{
|
"Name" character varying(120) NOT NULL,
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
"WebsiteUrl" character varying(500) NOT NULL,
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
"LogoUrl" character varying(500) NOT NULL,
|
||||||
SeasonId = table.Column<int>(type: "integer", nullable: false),
|
"Description" character varying(500) NOT NULL,
|
||||||
Name = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
"Tier" character varying(80) NOT NULL,
|
||||||
WebsiteUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
"SortOrder" integer NOT NULL,
|
||||||
LogoUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
"IsVisible" boolean NOT NULL,
|
||||||
Description = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: false),
|
"CreatedAt" timestamp with time zone NOT NULL,
|
||||||
Tier = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
"UpdatedAt" timestamp with time zone NULL
|
||||||
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),
|
DO $$
|
||||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
|
BEGIN
|
||||||
},
|
IF NOT EXISTS (
|
||||||
constraints: table =>
|
SELECT 1
|
||||||
{
|
FROM pg_constraint
|
||||||
table.PrimaryKey("PK_Sponsors", x => x.Id);
|
WHERE conname = 'FK_ShowactApplications_Seasons_SeasonId'
|
||||||
table.ForeignKey(
|
) THEN
|
||||||
name: "FK_Sponsors_Seasons_SeasonId",
|
ALTER TABLE "ShowactApplications"
|
||||||
column: x => x.SeasonId,
|
ADD CONSTRAINT "FK_ShowactApplications_Seasons_SeasonId"
|
||||||
principalTable: "Seasons",
|
FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id")
|
||||||
principalColumn: "Id",
|
ON DELETE CASCADE;
|
||||||
onDelete: ReferentialAction.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(
|
migrationBuilder.UpdateData(
|
||||||
table: "SiteSettings",
|
table: "SiteSettings",
|
||||||
@@ -125,49 +103,43 @@ namespace Backend.Migrations
|
|||||||
columns: new[] { "ClipAdminMenuVisible", "ShowactApplicationDisabledMessage", "ShowactsContent", "ShowactsUrl", "SponsorsVisible", "WorkflowRulesJson" },
|
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.\"}]" });
|
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(
|
migrationBuilder.Sql("""
|
||||||
name: "IX_ShowactApplications_SeasonId_Status",
|
CREATE INDEX IF NOT EXISTS "IX_ShowactApplications_SeasonId_Status"
|
||||||
table: "ShowactApplications",
|
ON "ShowactApplications" ("SeasonId", "Status");
|
||||||
columns: new[] { "SeasonId", "Status" });
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
CREATE INDEX IF NOT EXISTS "IX_Sponsors_SeasonId_IsVisible_SortOrder"
|
||||||
name: "IX_Sponsors_SeasonId_IsVisible_SortOrder",
|
ON "Sponsors" ("SeasonId", "IsVisible", "SortOrder");
|
||||||
table: "Sponsors",
|
""");
|
||||||
columns: new[] { "SeasonId", "IsVisible", "SortOrder" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.Sql("""
|
||||||
name: "ShowactApplications");
|
DROP TABLE IF EXISTS "ShowactApplications";
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
DROP TABLE IF EXISTS "Sponsors";
|
||||||
name: "Sponsors");
|
""");
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
migrationBuilder.Sql("""
|
||||||
name: "ClipAdminMenuVisible",
|
ALTER TABLE "SiteSettings"
|
||||||
table: "SiteSettings");
|
DROP COLUMN IF EXISTS "ClipAdminMenuVisible";
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactApplicationDisabledMessage",
|
DROP COLUMN IF EXISTS "ShowactApplicationDisabledMessage";
|
||||||
table: "SiteSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactApplicationsEnabled",
|
DROP COLUMN IF EXISTS "ShowactApplicationsEnabled";
|
||||||
table: "SiteSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactsContent",
|
DROP COLUMN IF EXISTS "ShowactsContent";
|
||||||
table: "SiteSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "ShowactsUrl",
|
DROP COLUMN IF EXISTS "ShowactsUrl";
|
||||||
table: "SiteSettings");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
ALTER TABLE "SiteSettings"
|
||||||
name: "SponsorsVisible",
|
DROP COLUMN IF EXISTS "SponsorsVisible";
|
||||||
table: "SiteSettings");
|
""");
|
||||||
|
|
||||||
migrationBuilder.UpdateData(
|
migrationBuilder.UpdateData(
|
||||||
table: "SiteSettings",
|
table: "SiteSettings",
|
||||||
|
|||||||
Reference in New Issue
Block a user