Refactor app architecture and clean local artifacts

This commit is contained in:
AzuTear
2026-06-24 23:43:14 +02:00
parent 17134b3b82
commit fef1d36fe8
274 changed files with 37724 additions and 6065 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddClipReviewWorkflow : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "ClipSubmissions"
ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL;
ALTER TABLE IF EXISTS "ClipSubmissions"
ADD COLUMN IF NOT EXISTS "ReviewedByTwitchId" character varying(120) NULL;
ALTER TABLE IF EXISTS "ClipSubmissions"
ADD COLUMN IF NOT EXISTS "ReviewedAt" timestamp with time zone NULL;
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "ClipSubmissions"
DROP COLUMN IF EXISTS "ReviewNote";
ALTER TABLE IF EXISTS "ClipSubmissions"
DROP COLUMN IF EXISTS "ReviewedByTwitchId";
ALTER TABLE IF EXISTS "ClipSubmissions"
DROP COLUMN IF EXISTS "ReviewedAt";
""");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,95 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddNominationReviewWorkflow : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Nominations_SeasonId",
table: "Nominations");
migrationBuilder.AddColumn<string>(
name: "ReviewNote",
table: "Nominations",
type: "character varying(500)",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<DateTimeOffset>(
name: "ReviewedAt",
table: "Nominations",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ReviewedByTwitchId",
table: "Nominations",
type: "character varying(120)",
maxLength: 120,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Status",
table: "Nominations",
type: "character varying(20)",
maxLength: 20,
nullable: false,
defaultValue: "");
migrationBuilder.UpdateData(
table: "Nominations",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "ReviewNote", "ReviewedAt", "ReviewedByTwitchId", "Status" },
values: new object[] { null, null, null, "pending" });
migrationBuilder.UpdateData(
table: "Nominations",
keyColumn: "Id",
keyValue: 2,
columns: new[] { "ReviewNote", "ReviewedAt", "ReviewedByTwitchId", "Status" },
values: new object[] { null, null, null, "pending" });
migrationBuilder.CreateIndex(
name: "IX_Nominations_SeasonId_Status",
table: "Nominations",
columns: new[] { "SeasonId", "Status" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Nominations_SeasonId_Status",
table: "Nominations");
migrationBuilder.DropColumn(
name: "ReviewNote",
table: "Nominations");
migrationBuilder.DropColumn(
name: "ReviewedAt",
table: "Nominations");
migrationBuilder.DropColumn(
name: "ReviewedByTwitchId",
table: "Nominations");
migrationBuilder.DropColumn(
name: "Status",
table: "Nominations");
migrationBuilder.CreateIndex(
name: "IX_Nominations_SeasonId",
table: "Nominations",
column: "SeasonId");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,93 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddAwardResultCategoryLock : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Results_SeasonId",
table: "Results");
migrationBuilder.AddColumn<int>(
name: "CategoryId",
table: "Results",
type: "integer",
nullable: true);
migrationBuilder.Sql(
"""
UPDATE "Results" AS r
SET "CategoryId" = c."Id"
FROM "Categories" AS c
WHERE r."SeasonId" = c."SeasonId"
AND lower(trim(r."CategoryName")) = lower(trim(c."Name"));
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM "Results" WHERE "CategoryId" IS NULL) THEN
RAISE EXCEPTION 'Could not backfill CategoryId for one or more rows in Results.';
END IF;
END $$;
""");
migrationBuilder.AlterColumn<int>(
name: "CategoryId",
table: "Results",
type: "integer",
nullable: false,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.CreateIndex(
name: "IX_Results_CategoryId",
table: "Results",
column: "CategoryId");
migrationBuilder.CreateIndex(
name: "IX_Results_SeasonId_CategoryId",
table: "Results",
columns: new[] { "SeasonId", "CategoryId" },
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_Results_Categories_CategoryId",
table: "Results",
column: "CategoryId",
principalTable: "Categories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Results_Categories_CategoryId",
table: "Results");
migrationBuilder.DropIndex(
name: "IX_Results_CategoryId",
table: "Results");
migrationBuilder.DropIndex(
name: "IX_Results_SeasonId_CategoryId",
table: "Results");
migrationBuilder.DropColumn(
name: "CategoryId",
table: "Results");
migrationBuilder.CreateIndex(
name: "IX_Results_SeasonId",
table: "Results",
column: "SeasonId");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddSeasonShowStreamUrl : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ShowStreamUrl",
table: "Seasons",
type: "character varying(400)",
maxLength: 400,
nullable: false,
defaultValue: "");
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 1,
column: "ShowStreamUrl",
value: "https://twitch.tv/jayuhime");
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 2,
column: "ShowStreamUrl",
value: "https://twitch.tv/jayuhime");
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 3,
column: "ShowStreamUrl",
value: "https://youtube.com/c/Jayuhime");
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 4,
column: "ShowStreamUrl",
value: "https://twitch.tv/jayuhime");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ShowStreamUrl",
table: "Seasons");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddSiteSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SiteSettings",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
HostDisplayName = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
HostTagline = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
NewsletterUrl = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
PrivacyEmail = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
ImprintUrl = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
ContactUrl = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
SponsorsUrl = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
SocialLinksJson = table.Column<string>(type: "text", nullable: false),
FaqJson = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SiteSettings", x => x.Id);
});
migrationBuilder.InsertData(
table: "SiteSettings",
columns: new[] { "Id", "ContactUrl", "FaqJson", "HostDisplayName", "HostTagline", "ImprintUrl", "NewsletterUrl", "PrivacyEmail", "SocialLinksJson", "SponsorsUrl" },
values: new object[] { 1, "https://vtuber-star-awards.de/kontakt", "[{\"question\":\"Wer darf mitmachen?\",\"answer\":\"Jede:r mit einem Twitch-Account. Einmal einloggen gen\\u00FCgt \\u2013 kein extra Konto, kein Papierkram.\"},{\"question\":\"Wie werden die Gewinner bestimmt?\",\"answer\":\"Komplett durch eure Stimmen. Die Community entscheidet, wer auf die B\\u00FChne darf.\"},{\"question\":\"Kann ich meine Wahl noch \\u00E4ndern?\",\"answer\":\"Ja. Bis zum Ende der Voting-Phase kannst du Nominierungen und Stimmen anpassen.\"},{\"question\":\"Wann und wo findet die Show statt?\",\"answer\":\"Die gro\\u00DFe Live-Show wird von Jayuhime gehostet und live auf den verkn\\u00FCpften Plattformen \\u00FCbertragen.\"}]", "Jayuhime", "VTuber & Award Host", "https://vtuber-star-awards.de/impressum", "https://vtuber-star-awards.de/newsletter", "datenschutz@vtuber-star-awards.de", "[{\"label\":\"Twitch\",\"platform\":\"twitch\",\"url\":\"https://twitch.tv/jayuhime\"},{\"label\":\"YouTube\",\"platform\":\"youtube\",\"url\":\"https://youtube.com/c/Jayuhime\"},{\"label\":\"X\",\"platform\":\"x\",\"url\":\"https://x.com/jayuhime\"},{\"label\":\"Instagram\",\"platform\":\"instagram\",\"url\":\"https://instagram.com/jayuhime\"},{\"label\":\"Discord\",\"platform\":\"discord\",\"url\":\"https://discord.gg/jayuhime\"}]", "https://vtuber-star-awards.de/partner" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SiteSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,58 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddPrivacyPolicyContentMetadata : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "PrivacyPolicyContent",
table: "SiteSettings",
type: "text",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<DateTimeOffset>(
name: "PrivacyPolicyUpdatedAt",
table: "SiteSettings",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "PrivacyPolicyUpdatedBy",
table: "SiteSettings",
type: "character varying(120)",
maxLength: 120,
nullable: true);
migrationBuilder.UpdateData(
table: "SiteSettings",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "PrivacyPolicyContent", "PrivacyPolicyUpdatedAt", "PrivacyPolicyUpdatedBy" },
values: new object[] { "Verantwortliche:r\nVTuber Star Awards, vertreten durch Jayuhime. Kontakt: datenschutz@vtuber-star-awards.de\n\nWelche Daten wir verarbeiten\nBei Teilnahme (Voting, Nominierung, Clip-Einreichung) verarbeiten wir ausschließlich deine Twitch-User-ID sowie den Zeitstempel deiner Aktion.\nFür Show-Erinnerungen speichern wir optional deine E-Mail-Adresse.\n\nRechtsgrundlage\nVerarbeitung auf Basis von Art. 6 Abs. 1 lit. b DSGVO. Für optionale Erinnerungen gilt Art. 6 Abs. 1 lit. a DSGVO.\n\nZweck der Verarbeitung\nDurchführung des VTuber Star Awards, Sicherstellung fairer Abstimmung, Spam-Prävention sowie Admin-Review von Einreichungen.\n\nLöschfristen\nAlle Teilnahmedaten werden spätestens 6 Monate nach der Award-Show automatisch gelöscht. E-Mail-Adressen werden nach der Show entfernt.\n\nDeine Rechte\nDu hast das Recht auf Auskunft, Löschung, Berichtigung, Widerspruch und Beschwerde bei der zuständigen Aufsichtsbehörde.\n\nWeitergabe an Dritte\nKeine Weitergabe zu Werbezwecken. Technische Dienstleister verarbeiten Daten ausschließlich im Rahmen der Auftragsverarbeitung.", new DateTimeOffset(new DateTime(2026, 6, 23, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "seed" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PrivacyPolicyContent",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "PrivacyPolicyUpdatedAt",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "PrivacyPolicyUpdatedBy",
table: "SiteSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddOperationalSiteSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DemoLoginDisplayName",
table: "SiteSettings",
type: "character varying(120)",
maxLength: 120,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "DemoLoginEmail",
table: "SiteSettings",
type: "character varying(180)",
maxLength: 180,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<bool>(
name: "DemoLoginEnabled",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "DemoLoginManagedByDatabase",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "DemoLoginPasswordHash",
table: "SiteSettings",
type: "character varying(120)",
maxLength: 120,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "DemoLoginPasswordSalt",
table: "SiteSettings",
type: "character varying(80)",
maxLength: 80,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "DemoLoginTwitchUserId",
table: "SiteSettings",
type: "character varying(120)",
maxLength: 120,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "MaintenanceMessage",
table: "SiteSettings",
type: "character varying(600)",
maxLength: 600,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<bool>(
name: "MaintenanceModeEnabled",
table: "SiteSettings",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<string>(
name: "MaintenanceTitle",
table: "SiteSettings",
type: "character varying(120)",
maxLength: 120,
nullable: false,
defaultValue: "");
migrationBuilder.UpdateData(
table: "SiteSettings",
keyColumn: "Id",
keyValue: 1,
columns: new[] { "DemoLoginDisplayName", "DemoLoginEmail", "DemoLoginEnabled", "DemoLoginManagedByDatabase", "DemoLoginPasswordHash", "DemoLoginPasswordSalt", "DemoLoginTwitchUserId", "MaintenanceMessage", "MaintenanceModeEnabled", "MaintenanceTitle" },
values: new object[] { "Jayuhime Admin", "", false, false, "", "", "jayuhime_admin", "Die Award-Galaxie wird gerade liebevoll poliert. Schau gleich wieder vorbei.", false, "Sternenpause" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DemoLoginDisplayName",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginEmail",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginEnabled",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginManagedByDatabase",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginPasswordHash",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginPasswordSalt",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "DemoLoginTwitchUserId",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "MaintenanceMessage",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "MaintenanceModeEnabled",
table: "SiteSettings");
migrationBuilder.DropColumn(
name: "MaintenanceTitle",
table: "SiteSettings");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddSeasonShowStartsAt : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE "Seasons"
ADD COLUMN IF NOT EXISTS "ShowStartsAt" time without time zone NOT NULL DEFAULT TIME '20:00:00';
""");
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 1,
column: "ShowStartsAt",
value: new TimeOnly(20, 0, 0));
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 2,
column: "ShowStartsAt",
value: new TimeOnly(20, 0, 0));
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 3,
column: "ShowStartsAt",
value: new TimeOnly(20, 0, 0));
migrationBuilder.UpdateData(
table: "Seasons",
keyColumn: "Id",
keyValue: 4,
column: "ShowStartsAt",
value: new TimeOnly(20, 0, 0));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ShowStartsAt",
table: "Seasons");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
public partial class AddClipCandidateLink : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "ClipSubmissions"
ADD COLUMN IF NOT EXISTS "CandidateId" integer NULL;
DO $$
BEGIN
IF to_regclass('"ClipSubmissions"') IS NOT NULL THEN
CREATE INDEX IF NOT EXISTS "IX_ClipSubmissions_CandidateId"
ON "ClipSubmissions" ("CandidateId");
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 IF;
END $$;
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "ClipSubmissions"
DROP CONSTRAINT IF EXISTS "FK_ClipSubmissions_Candidates_CandidateId";
DROP INDEX IF EXISTS "IX_ClipSubmissions_CandidateId";
ALTER TABLE IF EXISTS "ClipSubmissions"
DROP COLUMN IF EXISTS "CandidateId";
""");
}
}
}
@@ -0,0 +1,34 @@
using Backend.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
[DbContext(typeof(AwardsDbContext))]
[Migration("20260624150500_AddRiskFlagReviewNote")]
public partial class AddRiskFlagReviewNote : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "RiskFlags"
ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL;
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "RiskFlags"
DROP COLUMN IF EXISTS "ReviewNote";
""");
}
}
}
@@ -0,0 +1,39 @@
using Backend.Data;
using Backend.Services;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Backend.Migrations
{
/// <inheritdoc />
[DbContext(typeof(AwardsDbContext))]
[Migration("20260624162000_AddRiskRulesJson")]
public partial class AddRiskRulesJson : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
$"""
ALTER TABLE IF EXISTS "SiteSettings"
ADD COLUMN IF NOT EXISTS "RiskRulesJson" text NOT NULL DEFAULT '{RiskRuleSettings.Serialize(RiskRuleSettings.Defaults).Replace("'", "''")}';
UPDATE "SiteSettings"
SET "RiskRulesJson" = '{RiskRuleSettings.Serialize(RiskRuleSettings.Defaults).Replace("'", "''")}'
WHERE "RiskRulesJson" IS NULL OR btrim("RiskRulesJson") = '' OR "RiskRulesJson" = '[]';
""");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
ALTER TABLE IF EXISTS "SiteSettings"
DROP COLUMN IF EXISTS "RiskRulesJson";
""");
}
}
}
@@ -22,6 +22,51 @@ namespace Backend.Migrations
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ActionType")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("AdminTwitchUserId")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("EntityId")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("EntityType")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("MetadataJson")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Summary")
.IsRequired()
.HasMaxLength(240)
.HasColumnType("character varying(240)");
b.HasKey("Id");
b.ToTable("AdminAuditEntries");
});
modelBuilder.Entity("Backend.Domain.AwardResult", b =>
{
b.Property<int>("Id")
@@ -33,6 +78,9 @@ namespace Backend.Migrations
b.Property<int>("CandidateId")
.HasColumnType("integer");
b.Property<int>("CategoryId")
.HasColumnType("integer");
b.Property<string>("CategoryName")
.IsRequired()
.HasMaxLength(120)
@@ -45,7 +93,10 @@ namespace Backend.Migrations
b.HasIndex("CandidateId");
b.HasIndex("SeasonId");
b.HasIndex("CategoryId");
b.HasIndex("SeasonId", "CategoryId")
.IsUnique();
b.ToTable("Results");
@@ -54,6 +105,7 @@ namespace Backend.Migrations
{
Id = 1,
CandidateId = 8,
CategoryId = 5,
CategoryName = "VTuber des Jahres",
SeasonId = 2
},
@@ -61,6 +113,7 @@ namespace Backend.Migrations
{
Id = 2,
CandidateId = 9,
CategoryId = 6,
CategoryName = "Bestes Live Event",
SeasonId = 2
},
@@ -68,6 +121,7 @@ namespace Backend.Migrations
{
Id = 3,
CandidateId = 10,
CategoryId = 7,
CategoryName = "Clip des Jahres",
SeasonId = 2
},
@@ -75,6 +129,7 @@ namespace Backend.Migrations
{
Id = 4,
CandidateId = 11,
CategoryId = 8,
CategoryName = "VTuber des Jahres",
SeasonId = 3
},
@@ -82,6 +137,7 @@ namespace Backend.Migrations
{
Id = 5,
CandidateId = 12,
CategoryId = 9,
CategoryName = "Clip des Jahres",
SeasonId = 3
},
@@ -89,6 +145,7 @@ namespace Backend.Migrations
{
Id = 6,
CandidateId = 13,
CategoryId = 10,
CategoryName = "VTuber des Jahres",
SeasonId = 4
});
@@ -407,6 +464,81 @@ namespace Backend.Migrations
});
});
modelBuilder.Entity("Backend.Domain.ClipSubmission", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int?>("CandidateId")
.HasColumnType("integer");
b.Property<int?>("CategoryId")
.HasColumnType("integer");
b.Property<string>("ClipUrl")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedFromIp")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("Creator")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("Platform")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("ReviewNote")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewedByTwitchId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<int>("SeasonId")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("SubmittedByTwitchId")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.HasKey("Id");
b.HasIndex("CandidateId");
b.HasIndex("SeasonId", "Status");
b.ToTable("ClipSubmissions");
});
modelBuilder.Entity("Backend.Domain.Nomination", b =>
{
b.Property<int>("Id")
@@ -428,9 +560,25 @@ namespace Backend.Migrations
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewNote")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewedByTwitchId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<int>("SeasonId")
.HasColumnType("integer");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("SubmittedByTwitchId")
.IsRequired()
.HasMaxLength(120)
@@ -442,7 +590,7 @@ namespace Backend.Migrations
b.HasIndex("CategoryId");
b.HasIndex("SeasonId");
b.HasIndex("SeasonId", "Status");
b.ToTable("Nominations");
@@ -454,6 +602,7 @@ namespace Backend.Migrations
CategoryId = 1,
CreatedAt = new DateTimeOffset(new DateTime(2026, 6, 10, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)),
SeasonId = 1,
Status = "pending",
SubmittedByTwitchId = "twitch_hoshi"
},
new
@@ -463,10 +612,86 @@ namespace Backend.Migrations
CategoryId = 2,
CreatedAt = new DateTimeOffset(new DateTime(2026, 6, 10, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)),
SeasonId = 1,
Status = "pending",
SubmittedByTwitchId = "twitch_kurainu"
});
});
modelBuilder.Entity("Backend.Domain.RiskFlag", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedFromIp")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("MetadataJson")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ReviewNote")
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewedByTwitchId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<int?>("SeasonId")
.HasColumnType("integer");
b.Property<string>("Severity")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Source")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<string>("Summary")
.IsRequired()
.HasMaxLength(240)
.HasColumnType("character varying(240)");
b.Property<string>("TwitchUserId")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("UserAgent")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("RiskFlags");
});
modelBuilder.Entity("Backend.Domain.Season", b =>
{
b.Property<int>("Id")
@@ -506,6 +731,14 @@ namespace Backend.Migrations
b.Property<DateOnly>("ShowDate")
.HasColumnType("date");
b.Property<TimeOnly>("ShowStartsAt")
.HasColumnType("time without time zone");
b.Property<string>("ShowStreamUrl")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.Property<DateOnly>("VotingEndsAt")
.HasColumnType("date");
@@ -535,6 +768,8 @@ namespace Backend.Migrations
ReviewEndsAt = new DateOnly(2026, 7, 10),
ReviewStartsAt = new DateOnly(2026, 7, 1),
ShowDate = new DateOnly(2026, 7, 20),
ShowStartsAt = new TimeOnly(20, 0, 0),
ShowStreamUrl = "https://twitch.tv/jayuhime",
VotingEndsAt = new DateOnly(2026, 6, 30),
VotingStartsAt = new DateOnly(2026, 6, 1),
Year = 2026
@@ -551,6 +786,8 @@ namespace Backend.Migrations
ReviewEndsAt = new DateOnly(2025, 7, 10),
ReviewStartsAt = new DateOnly(2025, 7, 1),
ShowDate = new DateOnly(2025, 7, 20),
ShowStartsAt = new TimeOnly(20, 0, 0),
ShowStreamUrl = "https://twitch.tv/jayuhime",
VotingEndsAt = new DateOnly(2025, 6, 30),
VotingStartsAt = new DateOnly(2025, 6, 1),
Year = 2025
@@ -567,6 +804,8 @@ namespace Backend.Migrations
ReviewEndsAt = new DateOnly(2024, 7, 10),
ReviewStartsAt = new DateOnly(2024, 7, 1),
ShowDate = new DateOnly(2024, 7, 20),
ShowStartsAt = new TimeOnly(20, 0, 0),
ShowStreamUrl = "https://youtube.com/c/Jayuhime",
VotingEndsAt = new DateOnly(2024, 6, 30),
VotingStartsAt = new DateOnly(2024, 6, 1),
Year = 2024
@@ -583,12 +822,211 @@ namespace Backend.Migrations
ReviewEndsAt = new DateOnly(2023, 7, 10),
ReviewStartsAt = new DateOnly(2023, 7, 1),
ShowDate = new DateOnly(2023, 7, 20),
ShowStartsAt = new TimeOnly(20, 0, 0),
ShowStreamUrl = "https://twitch.tv/jayuhime",
VotingEndsAt = new DateOnly(2023, 6, 30),
VotingStartsAt = new DateOnly(2023, 6, 1),
Year = 2023
});
});
modelBuilder.Entity("Backend.Domain.SiteSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ContactUrl")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.Property<string>("DemoLoginDisplayName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("DemoLoginEmail")
.IsRequired()
.HasMaxLength(180)
.HasColumnType("character varying(180)");
b.Property<bool>("DemoLoginEnabled")
.HasColumnType("boolean");
b.Property<bool>("DemoLoginManagedByDatabase")
.HasColumnType("boolean");
b.Property<string>("DemoLoginPasswordHash")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("DemoLoginPasswordSalt")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("DemoLoginTwitchUserId")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("FaqJson")
.IsRequired()
.HasColumnType("text");
b.Property<string>("HostDisplayName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("HostTagline")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)");
b.Property<string>("ImprintUrl")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.Property<string>("MaintenanceMessage")
.IsRequired()
.HasMaxLength(600)
.HasColumnType("character varying(600)");
b.Property<bool>("MaintenanceModeEnabled")
.HasColumnType("boolean");
b.Property<string>("MaintenanceTitle")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("NewsletterUrl")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.Property<string>("PrivacyEmail")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)");
b.Property<string>("PrivacyPolicyContent")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset?>("PrivacyPolicyUpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("PrivacyPolicyUpdatedBy")
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("RiskRulesJson")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SocialLinksJson")
.IsRequired()
.HasColumnType("text");
b.Property<string>("SponsorsUrl")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.HasKey("Id");
b.ToTable("SiteSettings");
b.HasData(
new
{
Id = 1,
ContactUrl = "https://vtuber-star-awards.de/kontakt",
DemoLoginDisplayName = "Jayuhime Admin",
DemoLoginEmail = "",
DemoLoginEnabled = false,
DemoLoginManagedByDatabase = false,
DemoLoginPasswordHash = "",
DemoLoginPasswordSalt = "",
DemoLoginTwitchUserId = "jayuhime_admin",
FaqJson = "[{\"question\":\"Wer darf nominiert werden?\",\"answer\":\"Jede:r aktive deutschsprachige VTuber kann nominiert werden \\u2014 unabh\\u00E4ngig von Follower-Zahl oder Plattform. Die Community schl\\u00E4gt in der Nominierungsphase ihre Favorit:innen vor.\"},{\"question\":\"Wie funktioniert das Voting?\",\"answer\":\"Du meldest dich ausschlie\\u00DFlich mit deinem Twitch-Account an \\u2014 nur so kannst du teilnehmen. Das h\\u00E4lt das Voting fair und bot-frei. Danach vergibst du pro Kategorie eine Stimme, \\u00E4nderbar bis zum Ende der Phase.\"},{\"question\":\"Was kostet die Teilnahme?\",\"answer\":\"Gar nichts. Nominieren, voten und die Show schauen ist komplett kostenlos \\u2014 der VTuber Star Award ist ein Community-Event von Fans f\\u00FCr Fans.\"},{\"question\":\"Wann und wo findet die Award-Show statt?\",\"answer\":\"Die gro\\u00DFe Live-Show wird von Jayuhime gehostet und auf Twitch \\u0026 YouTube gestreamt. Den genauen Termin findest du im Countdown oben \\u2014 sei live dabei, wenn die Stars gek\\u00FCrt werden!\"},{\"question\":\"Ich wurde nominiert \\u2014 was nun?\",\"answer\":\"Gl\\u00FCckwunsch! Du erh\\u00E4ltst eine Benachrichtigung mit allen Infos. Teile deine Nominierung gern mit deiner Community und ruf zum Voten auf \\u2014 jede Stimme z\\u00E4hlt.\"}]",
HostDisplayName = "Jayuhime",
HostTagline = "VTuber & Award Host",
ImprintUrl = "https://vtuber-star-awards.de/impressum",
MaintenanceMessage = "Die Award-Galaxie wird gerade liebevoll poliert. Schau gleich wieder vorbei.",
MaintenanceModeEnabled = false,
MaintenanceTitle = "Sternenpause",
NewsletterUrl = "https://vtuber-star-awards.de/newsletter",
PrivacyEmail = "datenschutz@vtuber-star-awards.de",
PrivacyPolicyContent = "Verantwortliche:r\nVTuber Star Awards, vertreten durch Jayuhime. Kontakt: datenschutz@vtuber-star-awards.de\n\nWelche Daten wir verarbeiten\nBei Teilnahme (Voting, Nominierung, Clip-Einreichung) verarbeiten wir ausschließlich deine Twitch-User-ID sowie den Zeitstempel deiner Aktion.\nFür Show-Erinnerungen speichern wir optional deine E-Mail-Adresse.\n\nRechtsgrundlage\nVerarbeitung auf Basis von Art. 6 Abs. 1 lit. b DSGVO. Für optionale Erinnerungen gilt Art. 6 Abs. 1 lit. a DSGVO.\n\nZweck der Verarbeitung\nDurchführung des VTuber Star Awards, Sicherstellung fairer Abstimmung, Spam-Prävention sowie Admin-Review von Einreichungen.\n\nLöschfristen\nAlle Teilnahmedaten werden spätestens 6 Monate nach der Award-Show automatisch gelöscht. E-Mail-Adressen werden nach der Show entfernt.\n\nDeine Rechte\nDu hast das Recht auf Auskunft, Löschung, Berichtigung, Widerspruch und Beschwerde bei der zuständigen Aufsichtsbehörde.\n\nWeitergabe an Dritte\nKeine Weitergabe zu Werbezwecken. Technische Dienstleister verarbeiten Daten ausschließlich im Rahmen der Auftragsverarbeitung.",
PrivacyPolicyUpdatedAt = new DateTimeOffset(new DateTime(2026, 6, 23, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)),
PrivacyPolicyUpdatedBy = "seed",
RiskRulesJson = "[]",
SocialLinksJson = "[{\"label\":\"Twitch\",\"platform\":\"twitch\",\"url\":\"https://twitch.tv/jayuhime\",\"icon\":\"twitch\",\"showOnHost\":true,\"showOnCommunity\":true},{\"label\":\"YouTube\",\"platform\":\"youtube\",\"url\":\"https://youtube.com/c/Jayuhime\",\"icon\":\"youtube\",\"showOnHost\":true,\"showOnCommunity\":true},{\"label\":\"X\",\"platform\":\"x\",\"url\":\"https://x.com/jayuhime\",\"icon\":\"x\",\"showOnHost\":true,\"showOnCommunity\":true},{\"label\":\"Instagram\",\"platform\":\"instagram\",\"url\":\"https://instagram.com/jayuhime\",\"icon\":\"instagram\",\"showOnHost\":true,\"showOnCommunity\":true},{\"label\":\"Discord\",\"platform\":\"discord\",\"url\":\"https://discord.gg/jayuhime\",\"icon\":\"discord\",\"showOnHost\":true,\"showOnCommunity\":true}]",
SponsorsUrl = "https://vtuber-star-awards.de/partner"
});
});
modelBuilder.Entity("Backend.Domain.UserSession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedFromIp")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("character varying(80)");
b.Property<string>("DisplayName")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<bool>("IsActive")
.HasColumnType("boolean");
b.Property<DateTimeOffset>("LastSeenAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Role")
.IsRequired()
.HasMaxLength(40)
.HasColumnType("character varying(40)");
b.Property<string>("SessionToken")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("TwitchUserId")
.IsRequired()
.HasMaxLength(120)
.HasColumnType("character varying(120)");
b.Property<string>("UserAgent")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("character varying(400)");
b.HasKey("Id");
b.HasIndex("SessionToken")
.IsUnique();
b.ToTable("UserSessions");
});
modelBuilder.Entity("Backend.Domain.VoteBallot", b =>
{
b.Property<int>("Id")
@@ -704,6 +1142,12 @@ namespace Backend.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Backend.Domain.Category", "Category")
.WithMany()
.HasForeignKey("CategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Backend.Domain.Season", "Season")
.WithMany("Results")
.HasForeignKey("SeasonId")
@@ -712,6 +1156,8 @@ namespace Backend.Migrations
b.Navigation("Candidate");
b.Navigation("Category");
b.Navigation("Season");
});
@@ -745,6 +1191,16 @@ namespace Backend.Migrations
b.Navigation("Season");
});
modelBuilder.Entity("Backend.Domain.ClipSubmission", b =>
{
b.HasOne("Backend.Domain.Candidate", "Candidate")
.WithMany()
.HasForeignKey("CandidateId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("Candidate");
});
modelBuilder.Entity("Backend.Domain.Nomination", b =>
{
b.HasOne("Backend.Domain.Candidate", "Candidate")
@@ -770,6 +1226,15 @@ namespace Backend.Migrations
b.Navigation("Season");
});
modelBuilder.Entity("Backend.Domain.RiskFlag", b =>
{
b.HasOne("Backend.Domain.Season", "Season")
.WithMany()
.HasForeignKey("SeasonId");
b.Navigation("Season");
});
modelBuilder.Entity("Backend.Domain.VoteBallot", b =>
{
b.HasOne("Backend.Domain.Season", "Season")