Add winner archive, host image upload and live demo data
CI - Build & Verify / Build, Typecheck & Hygiene (push) Successful in 1m1s
CI - Build & Verify / Deploy to award.noveria.net (push) Successful in 1m30s

Deliver the demo-ready feature set and seed data so the live site can be
presented end to end:

- Winner archive: ArchivedWinner domain, admin CRUD endpoints/view/manager
  and public archive surface, backed by AddArchivedWinners migration.
- Host presentation: host image upload and artist name on SiteSettings with
  public image endpoint and supporting migrations.
- Clip submissions: idempotent table-ensure migration plus current-season
  demo clips for review workflows.
- Demo seed data: sponsors, share links and 2025 archived winners, with a
  guarded RemoveDemoSeasons cleanup; all seeds guard against real data.
- EnsureRuntimeSchemaParity migration to align runtime schema defensively.
- Admin/home UI refinements; remove unused team role permissions modal and
  dead share-quick-links code.

All seed and schema migrations are idempotent (IF NOT EXISTS / ON CONFLICT)
and skip when real season data is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-06-29 21:47:52 +02:00
parent f323e5a82c
commit 6b3b0360e7
92 changed files with 7412 additions and 583 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddArchivedWinners : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ArchivedWinners",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Year = table.Column<int>(type: "integer", nullable: false),
Category = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
Subcategory = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
WinnerName = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
WinnerUrl = table.Column<string>(type: "character varying(500)", maxLength: 500, 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_ArchivedWinners", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_ArchivedWinners_Year_Category_Subcategory",
table: "ArchivedWinners",
columns: new[] { "Year", "Category", "Subcategory" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ArchivedWinners");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class SeedDemoSponsorsAndShareLinks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
UPDATE "SiteSettings"
SET
"ShareXUrl" = CASE
WHEN COALESCE(NULLIF("ShareXUrl", ''), '') IN ('', 'https://x.com/intent/tweet')
THEN 'https://x.com/intent/tweet?text=Schaut%20euch%20die%20VTuber%20Star%20Awards%20an!&url=https%3A%2F%2Faward.noveria.net'
ELSE "ShareXUrl"
END,
"ShareDiscordUrl" = CASE
WHEN COALESCE(NULLIF("ShareDiscordUrl", ''), '') IN ('', 'https://discord.gg/')
THEN 'https://discord.gg/jayuhime'
ELSE "ShareDiscordUrl"
END
WHERE "Id" = 1;
INSERT INTO "Sponsors" (
"Id", "SeasonId", "Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder", "IsVisible", "CreatedAt", "UpdatedAt"
)
SELECT seed."Id", seed."SeasonId", seed."Name", seed."WebsiteUrl", seed."LogoUrl", seed."Description", seed."Tier", seed."SortOrder", seed."IsVisible", seed."CreatedAt", seed."UpdatedAt"
FROM (
VALUES
(9006, 1001, 'Starfall Merch Lab', 'https://example.invalid/starfall-merch', '/demo/sponsors/chibicanvas-market.svg', 'Merch-Pakete, Sticker-Bundles und kleine Giveaways fuer Community-Aktionen rund um die Show.', 'Merch Partner', 60, TRUE, TIMESTAMPTZ '2026-06-01 10:25:00+00', NULL::timestamptz),
(9007, 1001, 'Moonframe Media', 'https://example.invalid/moonframe-media', '/demo/sponsors/prismloop-audio.svg', 'Highlight-Cuts, Social-Assets und kurze Promo-Snippets fuer Voting- und Finale-Phasen.', 'Media Partner', 70, TRUE, TIMESTAMPTZ '2026-06-01 10:30:00+00', NULL::timestamptz),
(9008, 1001, 'PixelHarbor Tools', 'https://example.invalid/pixelharbor-tools', '/demo/sponsors/cloudbeacon-hosting.svg', 'Kleine Creator-Tools fuer Landingpages, Formulare und Community-Orga im Eventbetrieb.', 'Tooling Partner', 80, TRUE, TIMESTAMPTZ '2026-06-01 10:35:00+00', NULL::timestamptz)
) AS seed("Id", "SeasonId", "Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder", "IsVisible", "CreatedAt", "UpdatedAt")
WHERE EXISTS (SELECT 1 FROM "Seasons" WHERE "Id" = seed."SeasonId")
AND NOT EXISTS (
SELECT 1
FROM "Sponsors" existing
WHERE existing."SeasonId" = seed."SeasonId"
AND existing."Name" = seed."Name"
);
SELECT setval(pg_get_serial_sequence('"Sponsors"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Sponsors"), 1));
"""
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
DELETE FROM "Sponsors"
WHERE "Id" IN (9006, 9007, 9008)
AND "SeasonId" = 1001;
UPDATE "SiteSettings"
SET
"ShareXUrl" = 'https://x.com/intent/tweet',
"ShareDiscordUrl" = 'https://discord.gg/'
WHERE "Id" = 1
AND "ShareXUrl" = 'https://x.com/intent/tweet?text=Schaut%20euch%20die%20VTuber%20Star%20Awards%20an!&url=https%3A%2F%2Faward.noveria.net'
AND "ShareDiscordUrl" = 'https://discord.gg/jayuhime';
"""
);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class SeedExampleSponsorsForCurrentSeason : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
WITH target_season AS (
SELECT "Id"
FROM "Seasons"
WHERE "IsCurrent" = TRUE
ORDER BY "Year" DESC, "Id" DESC
LIMIT 1
)
INSERT INTO "Sponsors" (
"SeasonId", "Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder", "IsVisible", "CreatedAt", "UpdatedAt"
)
SELECT
target_season."Id",
seed."Name",
seed."WebsiteUrl",
seed."LogoUrl",
seed."Description",
seed."Tier",
seed."SortOrder",
TRUE,
TIMESTAMPTZ '2026-06-29 19:22:20+00',
NULL::timestamptz
FROM target_season
CROSS JOIN (
VALUES
('HoshiForge Studio', 'https://example.invalid/hoshiforge', '/demo/sponsors/hoshiforge-studio.svg', 'Branding-, Overlay- und Debuet-Visuals fuer VTuber-Projekte und Community-Events.', 'Presenting Sponsor', 10),
('NekoPixel Energy', 'https://example.invalid/nekopixel', '/demo/sponsors/nekopixel-energy.svg', 'Community-fokussierter Drink-Partner fuer lange Showabende, Watchpartys und Creator-Collabs.', 'Gold Partner', 20),
('PrismLoop Audio', 'https://example.invalid/prismloop', '/demo/sponsors/prismloop-audio.svg', 'Audio-Tools, Intro-Packs und Stream-Sounddesign fuer Live-Shows und Highlight-Clips.', 'Gold Partner', 30),
('CloudBeacon Hosting', 'https://example.invalid/cloudbeacon', '/demo/sponsors/cloudbeacon-hosting.svg', 'Skalierbares Hosting fuer Voting, Landingpages und Event-Traffic rund um Showtage.', 'Tech Partner', 40),
('ChibiCanvas Market', 'https://example.invalid/chibicanvas', '/demo/sponsors/chibicanvas-market.svg', 'Merch-, Sticker- und Artist-Marketplace mit Fokus auf VTuber, Emotes und Fanartikel.', 'Community Partner', 50)
) AS seed("Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder")
WHERE NOT EXISTS (
SELECT 1
FROM "Sponsors" existing
WHERE existing."SeasonId" = target_season."Id"
);
"""
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
DELETE FROM "Sponsors"
WHERE "Name" IN (
'HoshiForge Studio',
'NekoPixel Energy',
'PrismLoop Audio',
'CloudBeacon Hosting',
'ChibiCanvas Market'
);
"""
);
}
}
}
@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations;
[DbContext(typeof(Data.AwardsDbContext))]
[Migration("20260629194000_RemoveDemoSeasons")]
public partial class RemoveDemoSeasons : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
UPDATE "RiskFlags"
SET "SeasonId" = NULL
WHERE "SeasonId" IN (
SELECT "Id"
FROM "Seasons"
WHERE "IsDemo" = TRUE
);
DELETE FROM "Seasons"
WHERE "IsDemo" = TRUE;
"""
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
@@ -0,0 +1,38 @@
using Backend.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
[DbContext(typeof(AwardsDbContext))]
[Migration("20260629203000_AddHostImageUpload")]
/// <inheritdoc />
public partial class AddHostImageUpload : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageData" bytea;
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageContentType" character varying(80);
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageUpdatedAt" timestamp with time zone;
"""
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageUpdatedAt";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageContentType";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageData";
"""
);
}
}
}
@@ -0,0 +1,34 @@
using Backend.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
[DbContext(typeof(AwardsDbContext))]
[Migration("20260629204500_AddHostArtistName")]
/// <inheritdoc />
public partial class AddHostArtistName : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostArtistName" character varying(120) NOT NULL DEFAULT '';
"""
);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostArtistName";
"""
);
}
}
}
@@ -0,0 +1,38 @@
using Backend.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations;
[DbContext(typeof(AwardsDbContext))]
[Migration("20260629210000_EnsureRuntimeSchemaParity")]
public partial class EnsureRuntimeSchemaParity : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageData" bytea;
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageContentType" character varying(80);
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostImageUpdatedAt" timestamp with time zone;
ALTER TABLE "SiteSettings" ADD COLUMN IF NOT EXISTS "HostArtistName" character varying(120) NOT NULL DEFAULT '';
ALTER TABLE "Nominations" ADD COLUMN IF NOT EXISTS "StreamUrl" character varying(300);
"""
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "Nominations" DROP COLUMN IF EXISTS "StreamUrl";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostArtistName";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageUpdatedAt";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageContentType";
ALTER TABLE "SiteSettings" DROP COLUMN IF EXISTS "HostImageData";
"""
);
}
}
@@ -0,0 +1,178 @@
using Backend.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations;
[DbContext(typeof(AwardsDbContext))]
[Migration("20260629213000_EnsureClipSubmissionsTable")]
public partial class EnsureClipSubmissionsTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
CREATE TABLE IF NOT EXISTS "ClipSubmissions" (
"Id" integer GENERATED BY DEFAULT AS IDENTITY,
"SeasonId" integer NOT NULL,
"CategoryId" integer,
"CandidateId" integer,
"SubmittedByTwitchId" character varying(120) NOT NULL,
"ClipUrl" character varying(500) NOT NULL,
"Title" character varying(200) NOT NULL,
"Creator" character varying(120) NOT NULL,
"Platform" character varying(40) NOT NULL,
"Status" character varying(20) NOT NULL,
"ReviewNote" character varying(500),
"ReviewedByTwitchId" character varying(120),
"CreatedFromIp" character varying(80) NOT NULL,
"CreatedAt" timestamp with time zone NOT NULL,
"ReviewedAt" timestamp with time zone,
CONSTRAINT "PK_ClipSubmissions" PRIMARY KEY ("Id")
);
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "SeasonId" integer NOT NULL DEFAULT 0;
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "CategoryId" integer;
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "CandidateId" integer;
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "SubmittedByTwitchId" character varying(120) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "ClipUrl" character varying(500) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "Title" character varying(200) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "Creator" character varying(120) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "Platform" character varying(40) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "Status" character varying(20) NOT NULL DEFAULT 'pending';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500);
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "ReviewedByTwitchId" character varying(120);
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "CreatedFromIp" character varying(80) NOT NULL DEFAULT '';
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "CreatedAt" timestamp with time zone NOT NULL DEFAULT NOW();
ALTER TABLE "ClipSubmissions" ADD COLUMN IF NOT EXISTS "ReviewedAt" timestamp with time zone;
DO $vtsa_clip_constraints$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'FK_ClipSubmissions_Seasons_SeasonId'
) THEN
ALTER TABLE "ClipSubmissions"
ADD CONSTRAINT "FK_ClipSubmissions_Seasons_SeasonId"
FOREIGN KEY ("SeasonId") REFERENCES "Seasons"("Id") ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'FK_ClipSubmissions_Candidates_CandidateId'
) THEN
ALTER TABLE "ClipSubmissions"
ADD CONSTRAINT "FK_ClipSubmissions_Candidates_CandidateId"
FOREIGN KEY ("CandidateId") REFERENCES "Candidates"("Id") ON DELETE SET NULL;
END IF;
END;
$vtsa_clip_constraints$;
CREATE INDEX IF NOT EXISTS "IX_ClipSubmissions_CandidateId"
ON "ClipSubmissions" ("CandidateId");
CREATE INDEX IF NOT EXISTS "IX_ClipSubmissions_SeasonId_Status"
ON "ClipSubmissions" ("SeasonId", "Status");
DO $vtsa_clip_seed$
DECLARE
target_season_id integer;
BEGIN
SELECT "Id"
INTO target_season_id
FROM "Seasons"
WHERE "IsCurrent" = TRUE
ORDER BY "Year" DESC
LIMIT 1;
IF target_season_id IS NULL THEN
RETURN;
END IF;
IF EXISTS (
SELECT 1
FROM "ClipSubmissions"
WHERE "SeasonId" = target_season_id
) THEN
RETURN;
END IF;
INSERT INTO "ClipSubmissions" (
"SeasonId",
"CategoryId",
"CandidateId",
"SubmittedByTwitchId",
"ClipUrl",
"Title",
"Creator",
"Platform",
"Status",
"ReviewNote",
"ReviewedByTwitchId",
"CreatedFromIp",
"CreatedAt",
"ReviewedAt"
)
SELECT
target_season_id,
candidates."CategoryId",
candidates."Id",
seed_rows.submitted_by,
seed_rows.clip_url,
seed_rows.title,
seed_rows.creator,
seed_rows.platform,
seed_rows.status,
seed_rows.review_note,
seed_rows.reviewed_by,
'127.0.0.1',
seed_rows.created_at,
seed_rows.reviewed_at
FROM (
VALUES
('vtsa_demo_current_astra_shining_star_aster', 'viewer_2001', 'https://clips.twitch.tv/demo-nova-finale', 'Astra turns the boss fight', 'viewer_2001', 'Twitch', 'approved', 'Kontext passt.', 'clip_reviewer', TIMESTAMPTZ '2026-06-20 18:12:00+00', TIMESTAMPTZ '2026-06-21 09:00:00+00'),
('vtsa_demo_current_melo_rising_star_aster', 'viewer_2002', 'https://youtu.be/demo-vera-stage', 'Melo sings the finale bridge', 'viewer_2002', 'YouTube', 'approved', NULL, 'clip_reviewer', TIMESTAMPTZ '2026-06-20 20:25:00+00', TIMESTAMPTZ '2026-06-21 09:08:00+00'),
('vtsa_demo_current_lumi_shining_star_aster', 'viewer_2003', 'https://clips.twitch.tv/demo-ember-moment', 'Lumi opens the community event', 'viewer_2003', 'Twitch', 'pending', 'Timing pruefen.', NULL, TIMESTAMPTZ '2026-06-21 11:40:00+00', NULL),
('vtsa_demo_current_velvet_shining_star_aster', 'viewer_2004', 'https://youtu.be/demo-chroma-design', 'Velvet explains the overlay rebuild', 'viewer_2004', 'YouTube', 'approved', NULL, 'clip_reviewer', TIMESTAMPTZ '2026-06-21 15:10:00+00', TIMESTAMPTZ '2026-06-22 08:45:00+00'),
('vtsa_demo_current_lumi_rising_star_aster', 'viewer_2005', 'https://clips.twitch.tv/demo-sora-community', 'Lumi lets chat design the scene', 'viewer_2005', 'Twitch', 'pending', NULL, NULL, TIMESTAMPTZ '2026-06-22 19:55:00+00', NULL)
) AS seed_rows(
channel_slug,
submitted_by,
clip_url,
title,
creator,
platform,
status,
review_note,
reviewed_by,
created_at,
reviewed_at
)
INNER JOIN "Candidates" candidates
ON candidates."SeasonId" = target_season_id
AND lower(candidates."ChannelSlug") = lower(seed_rows.channel_slug);
PERFORM setval(
pg_get_serial_sequence('"ClipSubmissions"', 'Id'),
COALESCE((SELECT MAX("Id") FROM "ClipSubmissions"), 1)
);
END;
$vtsa_clip_seed$;
"""
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
DELETE FROM "ClipSubmissions"
WHERE "SubmittedByTwitchId" IN ('viewer_2001', 'viewer_2002', 'viewer_2003', 'viewer_2004', 'viewer_2005');
"""
);
}
}
@@ -0,0 +1,59 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations;
[DbContext(typeof(Data.AwardsDbContext))]
[Migration("20260629220000_SeedDemoArchivedWinners2025")]
public partial class SeedDemoArchivedWinners2025 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
DO $vtsa_archive_2025$
BEGIN
IF EXISTS (
SELECT 1 FROM "Seasons"
WHERE "Year" = 2025 AND "IsDemo" = FALSE
) THEN
RAISE NOTICE 'Echte 2025-Season vorhanden; Demo-Archivgewinner werden nicht eingespielt.';
RETURN;
END IF;
INSERT INTO "ArchivedWinners" (
"Year", "Category", "Subcategory", "WinnerName", "WinnerUrl", "CreatedAt"
)
VALUES
(2025, 'Gaming', 'Hidden Star', 'Archiv Aster', 'https://twitch.tv/archiv_aster', TIMESTAMPTZ '2025-06-22 10:05:00+00'),
(2025, 'Gaming', 'Rising Star', 'Archiv Beryl', 'https://twitch.tv/archiv_beryl', TIMESTAMPTZ '2025-06-22 10:06:00+00'),
(2025, 'Gaming', 'Shining Star', 'Archiv Coda', 'https://clips.twitch.tv/demo-archiv-coda', TIMESTAMPTZ '2025-06-22 10:07:00+00'),
(2025, 'Music', 'Hidden Star', 'Archiv Drift', 'https://twitch.tv/archiv_drift', TIMESTAMPTZ '2025-06-22 10:08:00+00'),
(2025, 'Music', 'Rising Star', 'Archiv Elara', 'https://youtu.be/demo-archiv-elara', TIMESTAMPTZ '2025-06-22 10:09:00+00'),
(2025, 'Music', 'Shining Star', 'Archiv Finch', 'https://clips.twitch.tv/demo-archiv-finch', TIMESTAMPTZ '2025-06-22 10:10:00+00'),
(2025, 'Community', 'Hidden Star', 'Archiv Lyra', 'https://twitch.tv/archiv_lyra', TIMESTAMPTZ '2025-06-22 10:11:00+00'),
(2025, 'Community', 'Rising Star', 'Archiv Muse', 'https://youtu.be/demo-archiv-muse', TIMESTAMPTZ '2025-06-22 10:12:00+00'),
(2025, 'Community', 'Shining Star', 'Archiv Nia', 'https://clips.twitch.tv/demo-archiv-nia', TIMESTAMPTZ '2025-06-22 10:13:00+00')
ON CONFLICT ("Year", "Category", "Subcategory") DO NOTHING;
PERFORM setval(pg_get_serial_sequence('"ArchivedWinners"', 'Id'), COALESCE((SELECT MAX("Id") FROM "ArchivedWinners"), 1));
END;
$vtsa_archive_2025$;
"""
);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
DELETE FROM "ArchivedWinners"
WHERE "Year" = 2025
AND "Category" IN ('Gaming', 'Music', 'Community')
AND "WinnerName" LIKE 'Archiv %';
"""
);
}
}
@@ -77,6 +77,51 @@ namespace Backend.Migrations
b.ToTable("AdminAuditEntries");
});
modelBuilder.Entity("Backend.Domain.ArchivedWinner", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Category")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Subcategory")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<DateTimeOffset?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("WinnerName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("WinnerUrl")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<int>("Year")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("Year", "Category", "Subcategory")
.IsUnique();
b.ToTable("ArchivedWinners");
});
modelBuilder.Entity("Backend.Domain.AwardResult", b =>
{
b.Property<int>("Id")
@@ -779,11 +824,26 @@ namespace Backend.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<string>("HostArtistName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("HostDisplayName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("HostImageContentType")
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<byte[]>("HostImageData")
.HasColumnType("bytea");
b.Property<DateTimeOffset?>("HostImageUpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("HostTagline")
.IsRequired()
.HasMaxLength(160)