diff --git a/Backend/.env.example b/Backend/.env.example index 8a1d581..6a8e940 100644 --- a/Backend/.env.example +++ b/Backend/.env.example @@ -2,7 +2,6 @@ VTSA_POSTGRES=Host=localhost;Port=5433;Database=vtuber_star_awards_dev;Username= ConnectionStrings__Postgres=Host=localhost;Port=5433;Database=vtuber_star_awards_dev;Username=vtsa_dev;Password=change-me-local-only Frontend__AllowedOrigins__0=http://localhost:5173 Frontend__AllowedOrigins__1=http://127.0.0.1:5173 -VTSA_SEED_MODE=demo VTSA_DEMO_LOGIN_ENABLED=true VTSA_DEMO_ADMIN_LOGIN=jayuhime_admin VTSA_DEMO_ADMIN_EMAIL=admin@example.local diff --git a/Backend/Common/SeasonMappings.cs b/Backend/Common/SeasonMappings.cs index e5a4d42..aa23e92 100644 --- a/Backend/Common/SeasonMappings.cs +++ b/Backend/Common/SeasonMappings.cs @@ -1,11 +1,19 @@ using System.Text.Json; +using System.Text.RegularExpressions; using Backend.Contracts; using Backend.Domain; +using System.Net; namespace Backend.Common; public static class SeasonMappings { + private static readonly Regex HtmlBreakRegex = new(@"<\s*br\s*/?>", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex HtmlListItemOpenRegex = new(@"<\s*li\b[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex HtmlBlockCloseRegex = new(@"", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex HtmlTagRegex = new(@"<[^>]*>", RegexOptions.Compiled); + private static readonly Regex MultiNewlineRegex = new(@"\n{3,}", RegexOptions.Compiled); + public static bool IsSeasonScheduleValid( DateOnly nominationStartsAt, DateOnly nominationEndsAt, @@ -90,6 +98,28 @@ public static class SeasonMappings return string.IsNullOrWhiteSpace(trimmed) ? "https://twitch.tv/jayuhime" : trimmed; } + public static string NormalizePlainTextContent(string? value) + { + var trimmed = value?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(trimmed)) + { + return string.Empty; + } + + var withBreakHints = HtmlBreakRegex.Replace(trimmed, "\n"); + withBreakHints = HtmlListItemOpenRegex.Replace(withBreakHints, "- "); + withBreakHints = HtmlBlockCloseRegex.Replace(withBreakHints, "\n"); + withBreakHints = HtmlTagRegex.Replace(withBreakHints, " "); + withBreakHints = WebUtility.HtmlDecode(withBreakHints).Replace("\r\n", "\n").Replace('\r', '\n'); + + var normalizedLines = withBreakHints + .Split('\n') + .Select(line => line.Trim()) + .ToArray(); + + return MultiNewlineRegex.Replace(string.Join('\n', normalizedLines), "\n\n").Trim(); + } + public static string NormalizePhaseKey(string? currentPhase) { var value = currentPhase?.Trim().ToLowerInvariant() ?? string.Empty; @@ -183,7 +213,7 @@ public static class SeasonMappings [ new FooterLinkDto("imprint", "Impressum", settings.ImprintUrl, settings.ImprintContent), new FooterLinkDto("contact", "Kontakt", settings.ContactUrl, settings.ContactContent), - new FooterLinkDto("sponsors", "Sponsoren & Partner", settings.SponsorsUrl, settings.SponsorsContent), + new FooterLinkDto("sponsors", "Sponsoren & Partner", string.Empty, settings.SponsorsContent), new FooterLinkDto("showacts", "Showacts", settings.ShowactsUrl, settings.ShowactsContent), ]; } diff --git a/Backend/Contracts/AdminDashboardContracts.cs b/Backend/Contracts/AdminDashboardContracts.cs index 899434a..64346e7 100644 --- a/Backend/Contracts/AdminDashboardContracts.cs +++ b/Backend/Contracts/AdminDashboardContracts.cs @@ -4,9 +4,13 @@ public sealed record AdminMetricDto(string Label, int Value, string Note); public sealed record AdminActivityDto(string Label, string Age); -public sealed record AdminTopCategoryDto(string Category, int Votes); +public sealed record AdminTopCategoryDto(string Category, int Value, string Basis); public sealed record AdminDashboardResponse( + int SeasonId, + int Year, + string SeasonName, + bool IsCurrent, IEnumerable Metrics, IEnumerable Activities, IEnumerable TopCategories, diff --git a/Backend/Contracts/AdminSeasonContracts.cs b/Backend/Contracts/AdminSeasonContracts.cs index ce6cef0..bd66909 100644 --- a/Backend/Contracts/AdminSeasonContracts.cs +++ b/Backend/Contracts/AdminSeasonContracts.cs @@ -6,7 +6,10 @@ public sealed record AdminSeasonListItemDto( string Name, string CurrentPhase, bool IsCurrent, - int CategoryCount); + bool IsDemo, + int CategoryCount, + DateTimeOffset? WinnersPublishedAt, + string? WinnersPublishedByTwitchId); public sealed record AdminCategoryItemDto( int Id, @@ -52,11 +55,61 @@ public sealed record AdminAwardResultItemDto( string CandidateChannelSlug, string CandidatePlatform); +public sealed record AdminVotingWorkspaceSummaryDto( + int TotalVotes, + int TotalBallots, + int TotalSubcategories, + int VotedSubcategories, + int ReadySubcategories, + int ProblemSubcategories, + int WinnerSetSubcategories); + +public sealed record AdminVotingCandidateRankDto( + int CandidateId, + string DisplayName, + string ChannelSlug, + string Platform, + int Votes, + int VoteSharePercent, + int NominationTally, + bool HasClip, + string ClipEmbedStatus, + bool HasWinnerConflict, + bool IsCurrentWinner, + bool IsAccepted, + bool IsTopTie); + +public sealed record AdminVotingCategoryWorkspaceItemDto( + int CategoryId, + string GroupName, + string CategoryName, + int SortOrder, + int? ViewerRangeMin, + int? ViewerRangeMax, + int VoteCount, + int BallotCount, + int CandidateCount, + int ReadyCandidateCount, + int NominationCount, + int OpenReviewCount, + bool HasWinner, + bool WinnerReady, + bool HasTopVoteTie, + bool HasMissingClip, + bool HasRuleConflict, + bool HasOpenReviews, + bool HasSoftNominatorWarning, + IEnumerable Leaderboard); + +public sealed record AdminVotingWorkspaceDto( + AdminVotingWorkspaceSummaryDto Summary, + IEnumerable Categories); + public sealed record AdminSeasonDetailResponse( int Id, int Year, string Name, - string ShowStreamUrl, + bool IsDemo, string CurrentPhase, bool IsCurrent, bool IsCommunityOnly, @@ -68,6 +121,8 @@ public sealed record AdminSeasonDetailResponse( DateOnly ReviewEndsAt, DateOnly ShowDate, TimeOnly ShowStartsAt, + DateTimeOffset? WinnersPublishedAt, + string? WinnersPublishedByTwitchId, IEnumerable SubcategoryTemplates, IEnumerable Categories, IEnumerable Candidates, @@ -77,12 +132,12 @@ public sealed record AdminSeasonDetailResponse( string TrackingReviewNotes, bool ShowTrackingReviewNotes, IEnumerable Results, + AdminVotingWorkspaceDto VotingWorkspace, IEnumerable ClipSubmissions); public sealed record CreateSeasonRequest( int Year, string Name, - string ShowStreamUrl, string CurrentPhase, bool IsCurrent, bool IsCommunityOnly, @@ -99,7 +154,6 @@ public sealed record CreateSeasonRequest( public sealed record UpdateSeasonRequest( int Year, string Name, - string ShowStreamUrl, string CurrentPhase, bool IsCurrent, bool IsCommunityOnly, diff --git a/Backend/Contracts/AdminSiteSettingsContracts.cs b/Backend/Contracts/AdminSiteSettingsContracts.cs index 971465e..e6ada26 100644 --- a/Backend/Contracts/AdminSiteSettingsContracts.cs +++ b/Backend/Contracts/AdminSiteSettingsContracts.cs @@ -18,6 +18,22 @@ public sealed record AdminSiteSettingsResponse( string SponsorsContent, string ShowactsUrl, string ShowactsContent, + string StreamBannerEyebrow, + string StreamBannerTitle, + string StreamBannerText, + string StreamBannerLiveButtonLabel, + string StreamBannerLiveButtonUrl, + string StreamBannerLockedButtonLabel, + bool StreamBannerUseCompletedContent, + string StreamBannerCompletedEyebrow, + string StreamBannerCompletedTitle, + string StreamBannerCompletedText, + string StreamBannerCompletedButtonLabel, + string StreamBannerCompletedButtonUrl, + string AwardsSectionTitle, + string AwardsSectionDescription, + string SubcategoriesSectionTitle, + string SubcategoriesSectionDescription, IEnumerable SocialLinks, IEnumerable Faq, string ShowactFormSchemaJson); @@ -38,6 +54,22 @@ public sealed record UpdateSiteSettingsRequest( string SponsorsContent, string ShowactsUrl, string ShowactsContent, + string StreamBannerEyebrow, + string StreamBannerTitle, + string StreamBannerText, + string StreamBannerLiveButtonLabel, + string StreamBannerLiveButtonUrl, + string StreamBannerLockedButtonLabel, + bool StreamBannerUseCompletedContent, + string StreamBannerCompletedEyebrow, + string StreamBannerCompletedTitle, + string StreamBannerCompletedText, + string StreamBannerCompletedButtonLabel, + string StreamBannerCompletedButtonUrl, + string AwardsSectionTitle, + string AwardsSectionDescription, + string SubcategoriesSectionTitle, + string SubcategoriesSectionDescription, PublicSocialLinkDto[] SocialLinks, FaqItemDto[] Faq, string? ShowactFormSchemaJson = null); diff --git a/Backend/Contracts/AdminTeamContracts.cs b/Backend/Contracts/AdminTeamContracts.cs index ab7da89..ac54818 100644 --- a/Backend/Contracts/AdminTeamContracts.cs +++ b/Backend/Contracts/AdminTeamContracts.cs @@ -33,6 +33,7 @@ public sealed record AdminTeamPermissionDto( string Key, string Label, string Description, + string GroupLabel, string MenuPath, bool ReadOnlySupported); diff --git a/Backend/Contracts/PublicOverviewContracts.cs b/Backend/Contracts/PublicOverviewContracts.cs index 362e6ba..479d7d7 100644 --- a/Backend/Contracts/PublicOverviewContracts.cs +++ b/Backend/Contracts/PublicOverviewContracts.cs @@ -16,6 +16,7 @@ public sealed record FeaturedCategoryDto( public sealed record WinnerPreviewDto( int Year, + string CategoryGroup, string Category, string WinnerName, string WinnerSlug, @@ -46,6 +47,20 @@ public sealed record FooterLinkDto( string Url, string Content); +public sealed record PublicStreamBannerContentDto( + string Eyebrow, + string Title, + string Text, + string LiveButtonLabel, + string LiveButtonUrl, + string LockedButtonLabel, + bool UseCompletedContent, + string CompletedEyebrow, + string CompletedTitle, + string CompletedText, + string CompletedButtonLabel, + string CompletedButtonUrl); + public sealed record PublicSiteContentDto( string HostDisplayName, string HostTagline, @@ -54,6 +69,11 @@ public sealed record PublicSiteContentDto( string ShareDiscordUrl, string PrivacyEmail, string PrivacyPolicyContent, + string AwardsSectionTitle, + string AwardsSectionDescription, + string SubcategoriesSectionTitle, + string SubcategoriesSectionDescription, + PublicStreamBannerContentDto StreamBanner, IEnumerable SocialLinks, IEnumerable FooterLinks); @@ -80,7 +100,6 @@ public sealed record OverviewResponse( string Title, DateOnly ShowDate, TimeOnly ShowStartsAt, - string ShowStreamUrl, string CurrentPhase, bool IsCommunityOnly, string LoginProvider, diff --git a/Backend/Contracts/PublicSeasonCategoryContracts.cs b/Backend/Contracts/PublicSeasonCategoryContracts.cs index f0fe76e..bd920ff 100644 --- a/Backend/Contracts/PublicSeasonCategoryContracts.cs +++ b/Backend/Contracts/PublicSeasonCategoryContracts.cs @@ -17,8 +17,6 @@ public sealed record PublicCategoryDetailDto( string GroupName, string Description, int MaxNomineesPerUser, - int? ViewerRangeMin, - int? ViewerRangeMax, IEnumerable Candidates); public sealed record SeasonCategoriesResponse( diff --git a/Backend/Contracts/PublicWinnerArchiveContracts.cs b/Backend/Contracts/PublicWinnerArchiveContracts.cs index 9f121a1..7dd715d 100644 --- a/Backend/Contracts/PublicWinnerArchiveContracts.cs +++ b/Backend/Contracts/PublicWinnerArchiveContracts.cs @@ -1,6 +1,7 @@ namespace Backend.Contracts; public sealed record WinnerArchiveItemDto( + string CategoryGroup, string Category, string WinnerName, string WinnerSlug, diff --git a/Backend/Data/AwardsDbContext.cs b/Backend/Data/AwardsDbContext.cs index 2eaeccc..5772694 100644 --- a/Backend/Data/AwardsDbContext.cs +++ b/Backend/Data/AwardsDbContext.cs @@ -29,8 +29,9 @@ public sealed class AwardsDbContext(DbContextOptions options) : { entity.HasIndex(item => item.Year).IsUnique(); entity.Property(item => item.Name).HasMaxLength(160); - entity.Property(item => item.ShowStreamUrl).HasMaxLength(400); + entity.Property(item => item.IsDemo).HasDefaultValue(false); entity.Property(item => item.CurrentPhase).HasMaxLength(60); + entity.Property(item => item.WinnersPublishedByTwitchId).HasMaxLength(120); entity.Property(item => item.SubcategoryTemplatesJson).HasDefaultValue("[]"); entity.Property(item => item.WorkflowRulesJson).HasDefaultValue("[]"); }); @@ -47,6 +48,15 @@ public sealed class AwardsDbContext(DbContextOptions options) : entity.Property(item => item.SponsorsUrl).HasMaxLength(400); entity.Property(item => item.ShowactsUrl).HasMaxLength(400); entity.Property(item => item.ShowactsContent).HasDefaultValue(string.Empty); + entity.Property(item => item.StreamBannerEyebrow).HasMaxLength(120); + entity.Property(item => item.StreamBannerTitle).HasMaxLength(160); + entity.Property(item => item.StreamBannerLiveButtonLabel).HasMaxLength(120); + entity.Property(item => item.StreamBannerLiveButtonUrl).HasMaxLength(400); + entity.Property(item => item.StreamBannerLockedButtonLabel).HasMaxLength(120); + entity.Property(item => item.StreamBannerCompletedEyebrow).HasMaxLength(120); + entity.Property(item => item.StreamBannerCompletedTitle).HasMaxLength(160); + entity.Property(item => item.StreamBannerCompletedButtonLabel).HasMaxLength(120); + entity.Property(item => item.StreamBannerCompletedButtonUrl).HasMaxLength(400); entity.Property(item => item.DemoLoginEmail).HasMaxLength(180); entity.Property(item => item.DemoLoginPasswordHash).HasMaxLength(120); entity.Property(item => item.DemoLoginPasswordSalt).HasMaxLength(80); @@ -225,6 +235,10 @@ public sealed class AwardsDbContext(DbContextOptions options) : entity.Property(item => item.CreatedFromIp).HasMaxLength(80); entity.HasIndex(item => new { item.SeasonId, item.Status }); entity.HasIndex(item => item.CandidateId); + entity.HasOne() + .WithMany() + .HasForeignKey(item => item.SeasonId) + .OnDelete(DeleteBehavior.Cascade); entity.HasOne(item => item.Candidate) .WithMany() .HasForeignKey(item => item.CandidateId) @@ -258,7 +272,5 @@ public sealed class AwardsDbContext(DbContextOptions options) : entity.Property(item => item.Tier).HasMaxLength(80); entity.HasIndex(item => new { item.SeasonId, item.IsVisible, item.SortOrder }); }); - - SeedData.Apply(modelBuilder); } } diff --git a/Backend/Data/OperationalTablesBootstrapper.cs b/Backend/Data/OperationalTablesBootstrapper.cs deleted file mode 100644 index 03d2e0b..0000000 --- a/Backend/Data/OperationalTablesBootstrapper.cs +++ /dev/null @@ -1,471 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static class OperationalTablesBootstrapper -{ - public static Task EnsureAsync(AwardsDbContext db) => - db.Database.ExecuteSqlRawAsync( - """ - ALTER TABLE "UserSessions" - ADD COLUMN IF NOT EXISTS "CreatedFromIp" character varying(80) NOT NULL DEFAULT ''; - - ALTER TABLE "UserSessions" - ADD COLUMN IF NOT EXISTS "UserAgent" character varying(400) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "RiskRulesJson" text NOT NULL DEFAULT '[]'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "WorkflowRulesJson" text NOT NULL DEFAULT '[]'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TrackingRulesJson" text NOT NULL DEFAULT '[]'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ViewerStatsProviderBaseUrl" character varying(400) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TrackingReviewNotes" text NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "NominationLinkBlacklistJson" text NOT NULL DEFAULT '[]'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipSubmissionsEnabled" boolean NOT NULL DEFAULT false; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipReviewEnabled" boolean NOT NULL DEFAULT true; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipSubmissionDisabledMessage" character varying(240) NOT NULL DEFAULT 'Clip-Einreichungen sind aktuell geschlossen.'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationsEnabled" boolean NOT NULL DEFAULT false; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationStartsAt" date NULL; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationEndsAt" date NULL; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationDisabledMessage" character varying(240) NOT NULL DEFAULT 'Showact-Bewerbungen sind aktuell geschlossen.'; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "SponsorsVisible" boolean NOT NULL DEFAULT true; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "HoursStreamed" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "HoursWatched" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "PeakViewers" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "FollowersGained" integer NULL; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TwitchAuthManagedByDatabase" boolean NOT NULL DEFAULT false; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TwitchClientId" character varying(120) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TwitchClientSecret" character varying(180) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TwitchRedirectUri" character varying(400) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "TwitchScope" character varying(300) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "SessionIdleTimeoutHours" integer NOT NULL DEFAULT 3; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ImprintContent" text NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ContactContent" text NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "SponsorsContent" text NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactsUrl" character varying(400) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactsContent" text NOT NULL DEFAULT ''; - - ALTER TABLE "Seasons" - ADD COLUMN IF NOT EXISTS "SubcategoryTemplatesJson" text NOT NULL DEFAULT '[]'; - - CREATE TABLE IF NOT EXISTS "RiskFlags" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NULL, - "TwitchUserId" character varying(120) NULL, - "Source" character varying(80) NOT NULL, - "Type" character varying(80) NOT NULL, - "Severity" character varying(20) NOT NULL, - "Status" character varying(20) NOT NULL, - "Summary" character varying(240) NOT NULL, - "CreatedFromIp" character varying(80) NOT NULL, - "UserAgent" character varying(400) NOT NULL, - "MetadataJson" text NOT NULL, - "ReviewNote" character varying(500) NULL, - "ReviewedByTwitchId" character varying(120) NULL, - "CreatedAt" timestamp with time zone NOT NULL, - "ReviewedAt" timestamp with time zone NULL - ); - - ALTER TABLE "RiskFlags" - ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL; - - CREATE INDEX IF NOT EXISTS "IX_RiskFlags_Status_CreatedAt" - ON "RiskFlags" ("Status", "CreatedAt" DESC); - - CREATE INDEX IF NOT EXISTS "IX_RiskFlags_SeasonId" - ON "RiskFlags" ("SeasonId"); - - CREATE TABLE IF NOT EXISTS "AdminAuditEntries" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "AdminTwitchUserId" character varying(120) NOT NULL, - "ActionType" character varying(80) NOT NULL, - "EntityType" character varying(80) NOT NULL, - "EntityId" character varying(120) NOT NULL, - "Summary" character varying(240) NOT NULL, - "MetadataJson" text NOT NULL, - "CreatedFromIp" character varying(80) NOT NULL DEFAULT '', - "UserAgent" character varying(400) NOT NULL DEFAULT '', - "CreatedAt" timestamp with time zone NOT NULL - ); - - ALTER TABLE "AdminAuditEntries" - ADD COLUMN IF NOT EXISTS "CreatedFromIp" character varying(80) NOT NULL DEFAULT ''; - - ALTER TABLE "AdminAuditEntries" - ADD COLUMN IF NOT EXISTS "UserAgent" character varying(400) NOT NULL DEFAULT ''; - - CREATE INDEX IF NOT EXISTS "IX_AdminAuditEntries_CreatedAt" - ON "AdminAuditEntries" ("CreatedAt" DESC); - - CREATE TABLE IF NOT EXISTS "ClipSubmissions" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL, - "CategoryId" integer NULL, - "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, - "CreatedFromIp" character varying(80) NOT NULL, - "CreatedAt" timestamp with time zone NOT NULL - ); - - ALTER TABLE "ClipSubmissions" - ADD COLUMN IF NOT EXISTS "CandidateId" integer NULL; - - ALTER TABLE "ClipSubmissions" - ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL; - - ALTER TABLE "ClipSubmissions" - ADD COLUMN IF NOT EXISTS "ReviewedByTwitchId" character varying(120) NULL; - - ALTER TABLE "ClipSubmissions" - ADD COLUMN IF NOT EXISTS "ReviewedAt" timestamp with time zone NULL; - - CREATE INDEX IF NOT EXISTS "IX_ClipSubmissions_SeasonId_Status" - ON "ClipSubmissions" ("SeasonId", "Status"); - - CREATE INDEX IF NOT EXISTS "IX_ClipSubmissions_CandidateId" - ON "ClipSubmissions" ("CandidateId"); - - 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 - ); - - CREATE INDEX IF NOT EXISTS "IX_ShowactApplications_SeasonId_Status" - ON "ShowactApplications" ("SeasonId", "Status"); - - 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 - ); - - CREATE INDEX IF NOT EXISTS "IX_Sponsors_SeasonId_IsVisible_SortOrder" - ON "Sponsors" ("SeasonId", "IsVisible", "SortOrder"); - - 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 $$; - - DO $$ - BEGIN - 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 $$; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "StreamUrl" character varying(300) NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "AcceptanceStatus" character varying(30) NOT NULL DEFAULT 'open'; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "AcceptanceNote" character varying(500) NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "ClipCompilationUrl" character varying(500) NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "ClipCompilationTitle" character varying(200) NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "ClipCompilationPlatform" character varying(40) NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "ClipEmbedStatus" character varying(30) NOT NULL DEFAULT 'unchecked'; - - ALTER TABLE "Categories" - ADD COLUMN IF NOT EXISTS "ViewerRangeMin" integer NULL; - - ALTER TABLE "Categories" - ADD COLUMN IF NOT EXISTS "ViewerRangeMax" integer NULL; - - CREATE TABLE IF NOT EXISTS "StreamerIdentities" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "Platform" character varying(40) NOT NULL, - "Login" character varying(120) NOT NULL, - "NormalizedKey" character varying(180) NOT NULL, - "DisplayName" character varying(120) NOT NULL, - "ProfileUrl" character varying(500) NULL, - "LastResolvedAt" timestamp with time zone NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS "IX_StreamerIdentities_NormalizedKey" - ON "StreamerIdentities" ("NormalizedKey"); - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "StreamerIdentityId" integer NULL; - - ALTER TABLE "Candidates" - ADD COLUMN IF NOT EXISTS "NominationTally" integer NOT NULL DEFAULT 0; - - CREATE INDEX IF NOT EXISTS "IX_Candidates_StreamerIdentityId" - ON "Candidates" ("StreamerIdentityId"); - - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM pg_constraint - WHERE conname = 'FK_Candidates_StreamerIdentities_StreamerIdentityId' - ) THEN - ALTER TABLE "Candidates" - ADD CONSTRAINT "FK_Candidates_StreamerIdentities_StreamerIdentityId" - FOREIGN KEY ("StreamerIdentityId") REFERENCES "StreamerIdentities" ("Id"); - END IF; - END $$; - - ALTER TABLE "Nominations" - ALTER COLUMN "CategoryId" DROP NOT NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "CategoryGroupName" character varying(80) NOT NULL DEFAULT ''; - - UPDATE "Nominations" n - SET "CategoryGroupName" = c."GroupName" - FROM "Categories" c - WHERE n."CategoryId" = c."Id" - AND COALESCE(n."CategoryGroupName", '') = ''; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "StreamerIdentityId" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "SuggestedCategoryId" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "ResolvedChannel" character varying(120) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "ResolvedPlatform" character varying(40) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "AvgViewers" integer NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackerStatus" character varying(40) NOT NULL DEFAULT 'pending'; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackerCheckedAt" timestamp with time zone NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackingReviewStatus" character varying(30) NOT NULL DEFAULT 'clear'; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackingFlagsJson" text NOT NULL DEFAULT '[]'; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackingReviewNote" character varying(1000) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackingReviewedByTwitchId" character varying(120) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "TrackingReviewedAt" timestamp with time zone NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "Status" character varying(20) NOT NULL DEFAULT 'pending'; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "ReviewedByTwitchId" character varying(120) NULL; - - ALTER TABLE "Nominations" - ADD COLUMN IF NOT EXISTS "ReviewedAt" timestamp with time zone NULL; - - CREATE INDEX IF NOT EXISTS "IX_Nominations_SeasonId_Status" - ON "Nominations" ("SeasonId", "Status"); - - CREATE INDEX IF NOT EXISTS "IX_Nominations_SeasonId_CategoryGroupName_Status" - ON "Nominations" ("SeasonId", "CategoryGroupName", "Status"); - - CREATE INDEX IF NOT EXISTS "IX_Nominations_SeasonId_StreamerIdentityId_CategoryGroupName" - ON "Nominations" ("SeasonId", "StreamerIdentityId", "CategoryGroupName"); - - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 - FROM pg_constraint - WHERE conname = 'FK_Nominations_StreamerIdentities_StreamerIdentityId' - ) THEN - ALTER TABLE "Nominations" - ADD CONSTRAINT "FK_Nominations_StreamerIdentities_StreamerIdentityId" - FOREIGN KEY ("StreamerIdentityId") REFERENCES "StreamerIdentities" ("Id"); - END IF; - - IF NOT EXISTS ( - SELECT 1 - FROM pg_constraint - WHERE conname = 'FK_Nominations_Categories_SuggestedCategoryId' - ) THEN - ALTER TABLE "Nominations" - ADD CONSTRAINT "FK_Nominations_Categories_SuggestedCategoryId" - FOREIGN KEY ("SuggestedCategoryId") REFERENCES "Categories" ("Id") - ON DELETE SET NULL; - END IF; - END $$; - - CREATE TABLE IF NOT EXISTS "TeamMembers" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "Login" character varying(80) NOT NULL, - "DisplayName" character varying(120) NOT NULL, - "Role" character varying(40) NOT NULL, - "PasswordHash" character varying(120) NOT NULL, - "PasswordSalt" character varying(80) NOT NULL, - "BoundTwitchUserId" character varying(120) NULL, - "BoundTwitchDisplayName" character varying(120) NULL, - "MustChangePassword" boolean NOT NULL, - "IsActive" boolean NOT NULL, - "CreatedByTwitchId" character varying(120) NOT NULL, - "UpdatedByTwitchId" character varying(120) NULL, - "CreatedAt" timestamp with time zone NOT NULL, - "UpdatedAt" timestamp with time zone NULL, - "LastLoginAt" timestamp with time zone NULL, - "TwitchBoundAt" timestamp with time zone NULL, - "PasswordResetAt" timestamp with time zone NULL - ); - - ALTER TABLE "TeamMembers" - ADD COLUMN IF NOT EXISTS "BoundTwitchUserId" character varying(120) NULL; - - ALTER TABLE "TeamMembers" - ADD COLUMN IF NOT EXISTS "BoundTwitchDisplayName" character varying(120) NULL; - - ALTER TABLE "TeamMembers" - ADD COLUMN IF NOT EXISTS "TwitchBoundAt" timestamp with time zone NULL; - - CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamMembers_Login" - ON "TeamMembers" ("Login"); - - CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamMembers_BoundTwitchUserId" - ON "TeamMembers" ("BoundTwitchUserId") - WHERE "BoundTwitchUserId" IS NOT NULL; - - CREATE TABLE IF NOT EXISTS "TeamRolePermissions" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "Role" character varying(40) NOT NULL, - "PermissionsJson" text NOT NULL, - "UpdatedByTwitchId" character varying(120) NOT NULL, - "UpdatedAt" timestamp with time zone NOT NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS "IX_TeamRolePermissions_Role" - ON "TeamRolePermissions" ("Role"); - """); -} diff --git a/Backend/Data/SeedAwardCatalogBootstrapper.cs b/Backend/Data/SeedAwardCatalogBootstrapper.cs deleted file mode 100644 index 5481b01..0000000 --- a/Backend/Data/SeedAwardCatalogBootstrapper.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Backend.Domain; -using Backend.Services; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static partial class SeedDataBootstrapper -{ - private static async Task EnsureCategoriesAsync(AwardsDbContext db, Season season) - { - var categories = await db.Categories - .Where(item => item.SeasonId == season.Id) - .ToListAsync(); - var templates = SeedCatalog.DefaultSubcategoryTemplates - .Select((item, index) => item with { SortOrder = index + 1 }) - .ToArray(); - var usedCategories = new HashSet(); - - season.SubcategoryTemplatesJson = SeasonSubcategoryTemplateSettings.Serialize(templates); - - var nextSortOrder = 1; - foreach (var award in SeedCatalog.AwardCategorySeeds.OrderBy(item => item.SortOrder)) - { - foreach (var template in templates) - { - var category = FindReusableCategory(categories, award, template, usedCategories) - ?? new Category { SeasonId = season.Id }; - usedCategories.Add(category); - - category.GroupName = award.Name; - category.Name = template.Name; - category.Slug = BuildCategorySlug(award.Slug, template.Slug); - category.Description = award.Description; - category.SortOrder = nextSortOrder++; - category.MaxNomineesPerUser = 3; - category.ViewerRangeMin = template.ViewerRangeMin; - category.ViewerRangeMax = template.ViewerRangeMax; - - if (category.Id == 0 && !db.Categories.Local.Any(item => ReferenceEquals(item, category))) - { - db.Categories.Add(category); - categories.Add(category); - } - } - } - - var staleCategories = categories - .Where(item => item.Id > 0 && !usedCategories.Contains(item)) - .ToArray(); - await RemoveStaleCategoryDataAsync(db, staleCategories); - await db.SaveChangesAsync(); - } - - private static async Task RemoveStaleCategoryDataAsync(AwardsDbContext db, Category[] staleCategories) - { - if (staleCategories.Length == 0) - { - return; - } - - var staleCategoryIds = staleCategories.Select(item => item.Id).ToArray(); - var staleCandidateIds = await db.Candidates - .Where(item => staleCategoryIds.Contains(item.CategoryId)) - .Select(item => item.Id) - .ToArrayAsync(); - - var staleVoteEntries = await db.VoteEntries - .Where(item => staleCategoryIds.Contains(item.CategoryId) || staleCandidateIds.Contains(item.CandidateId)) - .ToArrayAsync(); - db.VoteEntries.RemoveRange(staleVoteEntries); - - var staleResults = await db.Results - .Where(item => staleCategoryIds.Contains(item.CategoryId) || staleCandidateIds.Contains(item.CandidateId)) - .ToArrayAsync(); - db.Results.RemoveRange(staleResults); - - var affectedClips = await db.ClipSubmissions - .Where(item => - (item.CategoryId != null && staleCategoryIds.Contains(item.CategoryId.Value)) - || (item.CandidateId != null && staleCandidateIds.Contains(item.CandidateId.Value))) - .ToArrayAsync(); - foreach (var clip in affectedClips) - { - if (clip.CategoryId != null && staleCategoryIds.Contains(clip.CategoryId.Value)) - { - clip.CategoryId = null; - } - - if (clip.CandidateId != null && staleCandidateIds.Contains(clip.CandidateId.Value)) - { - clip.CandidateId = null; - } - } - - var affectedNominations = await db.Nominations - .Where(item => - (item.CategoryId != null && staleCategoryIds.Contains(item.CategoryId.Value)) - || (item.SuggestedCategoryId != null && staleCategoryIds.Contains(item.SuggestedCategoryId.Value)) - || (item.CandidateId != null && staleCandidateIds.Contains(item.CandidateId.Value))) - .ToArrayAsync(); - foreach (var nomination in affectedNominations) - { - if (nomination.CategoryId != null && staleCategoryIds.Contains(nomination.CategoryId.Value)) - { - nomination.CategoryId = null; - } - - if (nomination.SuggestedCategoryId != null && staleCategoryIds.Contains(nomination.SuggestedCategoryId.Value)) - { - nomination.SuggestedCategoryId = null; - } - - if (nomination.CandidateId != null && staleCandidateIds.Contains(nomination.CandidateId.Value)) - { - nomination.CandidateId = null; - } - } - - var staleCandidates = await db.Candidates - .Where(item => staleCandidateIds.Contains(item.Id)) - .ToArrayAsync(); - db.Candidates.RemoveRange(staleCandidates); - db.Categories.RemoveRange(staleCategories); - } - - private static Category? FindReusableCategory( - List categories, - AwardCategorySeed award, - SeasonSubcategoryTemplateSetting template, - HashSet usedCategories) - { - var targetSlug = BuildCategorySlug(award.Slug, template.Slug); - - return categories.FirstOrDefault(item => !usedCategories.Contains(item) - && string.Equals(item.Slug, targetSlug, StringComparison.OrdinalIgnoreCase)) - ?? categories.FirstOrDefault(item => !usedCategories.Contains(item) - && string.Equals(item.GroupName, award.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(item.Name, template.Name, StringComparison.OrdinalIgnoreCase)) - ?? categories.FirstOrDefault(item => !usedCategories.Contains(item) - && string.Equals(item.GroupName, award.LegacyGroupName, StringComparison.OrdinalIgnoreCase) - && string.Equals(item.Name, template.Name, StringComparison.OrdinalIgnoreCase)); - } - - private static async Task EnsureCandidatesAsync(AwardsDbContext db, Season season, CandidateSeed[] seeds) - { - var categories = await db.Categories - .Where(item => item.SeasonId == season.Id) - .ToDictionaryAsync(item => item.Slug, StringComparer.OrdinalIgnoreCase); - var existing = await db.Candidates - .Where(item => item.SeasonId == season.Id) - .Select(item => new { item.CategoryId, item.DisplayName, item.ChannelSlug }) - .ToArrayAsync(); - var existingKeys = existing - .Select(item => $"{item.CategoryId}|{item.DisplayName}|{item.ChannelSlug}".ToLowerInvariant()) - .ToHashSet(); - - foreach (var seed in seeds) - { - if (!categories.TryGetValue(seed.CategorySlug, out var category)) - { - continue; - } - - var key = $"{category.Id}|{seed.DisplayName}|{seed.ChannelSlug}".ToLowerInvariant(); - if (existingKeys.Contains(key)) - { - continue; - } - - db.Candidates.Add(new Candidate - { - SeasonId = season.Id, - CategoryId = category.Id, - DisplayName = seed.DisplayName, - ChannelSlug = seed.ChannelSlug, - Platform = seed.Platform, - }); - } - - await db.SaveChangesAsync(); - } - - private static async Task EnsureWinnersAsync(AwardsDbContext db, Season season, WinnerSeed[] seeds) - { - var categories = await db.Categories - .Where(item => item.SeasonId == season.Id) - .ToDictionaryAsync(item => item.Slug, StringComparer.OrdinalIgnoreCase); - var candidates = await db.Candidates - .Where(item => item.SeasonId == season.Id) - .ToArrayAsync(); - var existingResults = await db.Results - .Where(item => item.SeasonId == season.Id) - .ToArrayAsync(); - foreach (var result in existingResults) - { - if (categories.Values.FirstOrDefault(item => item.Id == result.CategoryId) is { } category - && result.CategoryName != category.Name) - { - result.CategoryName = category.Name; - } - } - - var existingResultCategoryIds = existingResults.Select(item => item.CategoryId).ToHashSet(); - - foreach (var seed in seeds) - { - if (!categories.TryGetValue(seed.CategorySlug, out var category) || existingResultCategoryIds.Contains(category.Id)) - { - continue; - } - - var candidate = candidates.FirstOrDefault(item => - item.CategoryId == category.Id - && string.Equals(item.DisplayName, seed.DisplayName, StringComparison.OrdinalIgnoreCase)); - if (candidate is null) - { - continue; - } - - db.Results.Add(new AwardResult - { - SeasonId = season.Id, - CategoryId = category.Id, - CandidateId = candidate.Id, - CategoryName = category.Name, - }); - } - } - - private static string BuildCategorySlug(string awardSlug, string templateSlug) => - $"{SeasonSubcategoryTemplateSettings.Slugify(awardSlug)}-{SeasonSubcategoryTemplateSettings.Slugify(templateSlug)}"; -} diff --git a/Backend/Data/SeedCatalog.cs b/Backend/Data/SeedCatalog.cs deleted file mode 100644 index 608eced..0000000 --- a/Backend/Data/SeedCatalog.cs +++ /dev/null @@ -1,184 +0,0 @@ -namespace Backend.Data; - -using Backend.Services; - -internal sealed record AwardCategorySeed(string LegacyGroupName, string Name, string Slug, string Description, int SortOrder); -internal sealed record CandidateSeed(string CategorySlug, string DisplayName, string ChannelSlug, string Platform); -internal sealed record WinnerSeed(int Year, string CategorySlug, string DisplayName, string ChannelSlug, string Platform); -internal sealed record SiteFaqSeed(string Question, string Answer); -internal sealed record SiteSocialSeed(string Label, string Platform, string Url, string Icon); -internal sealed record SponsorSeed(string Name, string WebsiteUrl, string LogoUrl, string Description, string Tier, int SortOrder); - -internal static class SeedCatalog -{ - internal static readonly SeasonSubcategoryTemplateSetting[] DefaultSubcategoryTemplates = - [ - new("Hidden Star", "hidden-star", 1, 1, 20), - new("Rising Star", "rising-star", 2, 21, 60), - new("Shining Star", "shining-star", 3, 61, null), - ]; - - internal static readonly AwardCategorySeed[] AwardCategorySeeds = - [ - new("Main Awards", "VTuber des Jahres", "vtuber-des-jahres", "Die größte Auszeichnung des Jahres.", 1), - new("Discovery", "Best Newcomer", "best-newcomer", "Neue Stimmen, neue Welten und frische Energie für die Szene.", 2), - new("Creative", "Model & Design", "model-design", "Live2D, 3D, Outfit, Rigging und visuelle Identitaet.", 3), - new("Performance", "Gesang & Musik", "gesang-musik", "Songs, Covers, Konzerte und musikalische Highlights.", 4), - new("Gaming", "Best Gaming", "best-gaming", "Gameplay, Skill, Chaos und legendaere Gaming-Momente.", 5), - new("Entertainment", "Best Variety", "best-variety", "Talk, Comedy, Watchalongs und kreative Streamformate.", 6), - new("Community", "Community Liebling", "community-liebling", "Creator:innen, die ihre Community besonders stark verbinden.", 7), - new("Collab", "Best Collab & Duo", "best-collab-duo", "Gemeinsame Streams, Projekte und Duo-Dynamik.", 8), - ]; - - internal static readonly Dictionary LegacyCategorySlugMap = new(StringComparer.OrdinalIgnoreCase) - { - ["bestes-live-event"] = "best-newcomer", - ["clip-des-jahres"] = "model-design", - ["beste-community"] = "gesang-musik", - }; - - internal static readonly SiteFaqSeed[] SiteFaqSeeds = - [ - new( - "Wer darf nominiert werden?", - "Jede:r aktive deutschsprachige VTuber kann nominiert werden — unabhaengig von Follower-Zahl oder Plattform. Die Community schlaegt in der Nominierungsphase ihre Favorit:innen vor."), - new( - "Wie funktioniert das Voting?", - "Du meldest dich ausschließlich mit deinem Twitch-Account an — nur so kannst du teilnehmen. Das hält das Voting fair und bot-frei. Danach vergibst du pro Kategorie eine Stimme, änderbar bis zum Ende der Phase."), - new( - "Was kostet die Teilnahme?", - "Gar nichts. Nominieren, voten und die Show schauen ist komplett kostenlos — der VTuber Star Award ist ein Community-Event von Fans für Fans."), - new( - "Wann und wo findet die Award-Show statt?", - "Die große Live-Show wird von Jayuhime gehostet und auf Twitch & YouTube gestreamt. Den genauen Termin findest du im Countdown oben — sei live dabei, wenn die Stars gekürt werden!"), - new( - "Ich wurde nominiert — was nun?", - "Glückwunsch! Du erhältst eine Benachrichtigung mit allen Infos. Teile deine Nominierung gern mit deiner Community und ruf zum Voten auf — jede Stimme zählt."), - ]; - - internal static readonly SiteSocialSeed[] SiteSocialSeeds = - [ - new("Twitch", "twitch", "https://twitch.tv/jayuhime", "twitch"), - new("YouTube", "youtube", "https://youtube.com/c/Jayuhime", "youtube"), - new("X", "x", "https://x.com/jayuhime", "x"), - new("Instagram", "instagram", "https://instagram.com/jayuhime", "instagram"), - new("Discord", "discord", "https://discord.gg/jayuhime", "discord"), - ]; - - internal const string DefaultImprintContent = """ -Anbieter -VTuber Star Awards, vertreten durch Jayuhime. - -Kontakt -Nutze für organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse. - -Hinweis -Dieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsächlichen Anbieterangaben ersetzt werden. -"""; - - internal const string DefaultContactContent = """ -Kontakt zum Award-Team -Du hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team über die hinterlegte Kontaktseite. - -Datenschutzfragen -Für Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer. - -Community & Kooperationen -Social Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert. -"""; - - internal const string DefaultSponsorsContent = """ -Sponsoren & Partner -Hier können Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden. - -Partner werden im Rahmen der Show und auf den öffentlichen Kontaktflächen genannt, sobald sie final bestätigt sind. -"""; - - internal static readonly CandidateSeed[] CurrentCandidateSeeds = - [ - new("vtuber-des-jahres-shining-star", "Hoshimi Miyu", "@hoshimimiyu", "Twitch"), - new("vtuber-des-jahres-shining-star", "Kurainu", "@kurainu", "Twitch"), - new("vtuber-des-jahres-shining-star", "Shiro Ch.", "@shiroch", "Twitch"), - new("best-newcomer-hidden-star", "Aoi Sakura", "@aoisakura", "YouTube"), - new("best-newcomer-rising-star", "Nox Live", "@noxlive", "Twitch"), - new("model-design-shining-star", "Velvet Rei", "@velvetrei", "Twitch"), - new("model-design-rising-star", "Mochi Atelier", "@mochiatelier", "Cake"), - new("gesang-musik-shining-star", "Melo Diva", "@melodiva", "YouTube"), - new("gesang-musik-rising-star", "Yuki Stern", "@yukistern", "Twitch"), - new("best-gaming-shining-star", "Kurainu", "@kurainu", "Twitch"), - new("best-gaming-rising-star", "PixelPunk", "@pixelpunk", "Twitch"), - new("best-variety-shining-star", "Taro Chaos", "@tarochaos", "Twitch"), - new("best-variety-rising-star", "Kotaro Plays", "@kotaroplays", "YouTube"), - new("community-liebling-shining-star", "Shiro Ch.", "@shiroch", "Twitch"), - new("community-liebling-hidden-star", "Lumi", "@lumi_vt", "Cake"), - new("best-collab-duo-shining-star", "Akari & Nox", "@akari_vt", "Twitch"), - new("best-collab-duo-rising-star", "Mochi & Hana", "@mochi_mochi", "YouTube"), - ]; - - internal static readonly WinnerSeed[] WinnerSeeds = - [ - new(2025, "vtuber-des-jahres-shining-star", "Hoshimi Miyu", "@hoshimimiyu", "Twitch"), - new(2025, "best-newcomer-hidden-star", "Aoi Sakura", "@aoisakura", "YouTube"), - new(2025, "model-design-shining-star", "Velvet Rei", "@velvetrei", "Twitch"), - new(2025, "gesang-musik-shining-star", "Melo Diva", "@melodiva", "YouTube"), - new(2025, "best-gaming-shining-star", "Kurainu", "@kurainu", "Twitch"), - new(2025, "best-variety-shining-star", "Taro Chaos", "@tarochaos", "Twitch"), - new(2025, "community-liebling-shining-star", "Shiro Ch.", "@shiroch", "Twitch"), - new(2025, "best-collab-duo-shining-star", "Akari & Nox", "@akari_vt", "Cake"), - new(2024, "vtuber-des-jahres-shining-star", "Aoi Sakura", "@aoisakura", "YouTube"), - new(2024, "best-newcomer-hidden-star", "Lumi", "@lumi_vt", "Cake"), - new(2024, "model-design-rising-star", "Mochi Atelier", "@mochiatelier", "Cake"), - new(2024, "gesang-musik-rising-star", "Yuki Stern", "@yukistern", "Twitch"), - new(2024, "best-gaming-shining-star", "Starbyte", "@starbyte", "Twitch"), - new(2024, "best-variety-rising-star", "Kotaro Plays", "@kotaroplays", "YouTube"), - new(2024, "community-liebling-rising-star", "Moonrelay", "@moonrelay", "Twitch"), - new(2024, "best-collab-duo-rising-star", "Pixel & Kotaro", "@pixelpunk", "Twitch"), - new(2023, "vtuber-des-jahres-shining-star", "Akari Nova", "@akarinova", "Twitch"), - new(2023, "best-newcomer-rising-star", "Nox Live", "@noxlive", "Twitch"), - new(2023, "model-design-shining-star", "Rei Velvet", "@reivelvet", "YouTube"), - new(2023, "gesang-musik-shining-star", "Tenshi Vox", "@tenshivox", "Twitch"), - new(2023, "best-gaming-rising-star", "Bit Knight", "@bitknight", "Twitch"), - new(2023, "best-variety-hidden-star", "Hana Hearts", "@hanahearts", "Cake"), - new(2023, "community-liebling-rising-star", "Sora Blau", "@sorablau", "YouTube"), - new(2023, "best-collab-duo-rising-star", "Yuki & Melo", "@yukistern", "Twitch"), - ]; - - internal static readonly SponsorSeed[] DemoSponsorSeeds = - [ - new( - "HoshiForge Studio", - "https://hoshiforge.example", - "/demo/sponsors/hoshiforge-studio.svg", - "Branding-, Overlay- und Debuet-Visuals fuer VTuber-Projekte und Community-Events.", - "Presenting Sponsor", - 10), - new( - "NekoPixel Energy", - "https://nekopixel.example", - "/demo/sponsors/nekopixel-energy.svg", - "Community-fokussierter Drink-Partner fuer lange Showabende, Watchpartys und Creator-Collabs.", - "Gold Partner", - 20), - new( - "PrismLoop Audio", - "https://prismloop.example", - "/demo/sponsors/prismloop-audio.svg", - "Audio-Tools, Intro-Packs und Stream-Sounddesign fuer Live-Shows und Highlight-Clips.", - "Gold Partner", - 30), - new( - "CloudBeacon Hosting", - "https://cloudbeacon.example", - "/demo/sponsors/cloudbeacon-hosting.svg", - "Skalierbares Hosting fuer Voting, Landingpages und Event-Traffic rund um Showtage.", - "Tech Partner", - 40), - new( - "ChibiCanvas Market", - "https://chibicanvas.example", - "/demo/sponsors/chibicanvas-market.svg", - "Merch-, Sticker- und Artist-Marketplace mit Fokus auf VTuber, Emotes und Fanartikel.", - "Community Partner", - 50), - ]; -} diff --git a/Backend/Data/SeedData.cs b/Backend/Data/SeedData.cs deleted file mode 100644 index 7447b0a..0000000 --- a/Backend/Data/SeedData.cs +++ /dev/null @@ -1,211 +0,0 @@ -using Backend.Domain; -using Backend.Services; -using Microsoft.EntityFrameworkCore; -using System.Text.Json; - -namespace Backend.Data; - -public static class SeedData -{ - public static void Apply(ModelBuilder modelBuilder) - { - modelBuilder.Entity().HasData( - new SiteSettings - { - Id = 1, - HostDisplayName = "Jayuhime", - HostTagline = "VTuber & Award Host", - NewsletterUrl = "https://vtuber-star-awards.de/newsletter", - PrivacyEmail = "datenschutz@vtuber-star-awards.de", - PrivacyPolicyContent = """ -Verantwortliche:r -VTuber Star Awards, vertreten durch Jayuhime. Kontakt: datenschutz@vtuber-star-awards.de - -Welche Daten wir verarbeiten -Bei Teilnahme (Voting, Nominierung, Clip-Einreichung) verarbeiten wir ausschließlich deine Twitch-User-ID sowie den Zeitstempel deiner Aktion. -Für Show-Erinnerungen speichern wir optional deine E-Mail-Adresse. - -Rechtsgrundlage -Verarbeitung auf Basis von Art. 6 Abs. 1 lit. b DSGVO. Für optionale Erinnerungen gilt Art. 6 Abs. 1 lit. a DSGVO. - -Zweck der Verarbeitung -Durchführung des VTuber Star Awards, Sicherstellung fairer Abstimmung, Spam-Prävention sowie Admin-Review von Einreichungen. - -Löschfristen -Alle Teilnahmedaten werden spätestens 6 Monate nach der Award-Show automatisch gelöscht. E-Mail-Adressen werden nach der Show entfernt. - -Deine Rechte -Du hast das Recht auf Auskunft, Löschung, Berichtigung, Widerspruch und Beschwerde bei der zuständigen Aufsichtsbehörde. - -Weitergabe an Dritte -Keine Weitergabe zu Werbezwecken. Technische Dienstleister verarbeiten Daten ausschließlich im Rahmen der Auftragsverarbeitung. -""", - PrivacyPolicyUpdatedBy = "seed", - PrivacyPolicyUpdatedAt = new DateTimeOffset(2026, 6, 23, 0, 0, 0, TimeSpan.Zero), - ImprintUrl = "https://vtuber-star-awards.de/impressum", - ImprintContent = SeedCatalog.DefaultImprintContent, - ContactUrl = "https://vtuber-star-awards.de/kontakt", - ContactContent = SeedCatalog.DefaultContactContent, - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsContent = SeedCatalog.DefaultSponsorsContent, - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt für die Award-Show.", - RiskRulesJson = RiskRuleSettings.Serialize(RiskRuleSettings.Defaults), - WorkflowRulesJson = WorkflowRuleSettings.Serialize(WorkflowRuleSettings.Defaults), - TrackingRulesJson = TrackingRulesSettings.Serialize(new TrackingRulesConfiguration( - TrackingRulesSettings.DefaultSource, - TrackingRulesSettings.DefaultImportantMetrics, - TrackingRulesSettings.DefaultOptionalMetrics, - TrackingRulesSettings.DefaultFlags)), - ViewerStatsProviderBaseUrl = TrackingRulesSettings.DefaultBaseUrl, - TrackingReviewNotes = "Fallback-Quellen für manuelle Reviews:\\n- SullyGnome\\n- Twitch-Kanal direkt\\n\\nNutze diese Notizen für Edge Cases und manuelle Tier-Entscheidungen.", - NominationLinkBlacklistJson = NominationLinkBlacklistSettings.Serialize(NominationLinkBlacklistSettings.Defaults), - ClipSubmissionsEnabled = false, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - SessionIdleTimeoutHours = 3, - SponsorsVisible = true, - SocialLinksJson = JsonSerializer.Serialize(new[] - { - new { label = "Twitch", platform = "twitch", url = "https://twitch.tv/jayuhime", icon = "twitch", showOnHost = true, showOnCommunity = true }, - new { label = "YouTube", platform = "youtube", url = "https://youtube.com/c/Jayuhime", icon = "youtube", showOnHost = true, showOnCommunity = true }, - new { label = "X", platform = "x", url = "https://x.com/jayuhime", icon = "x", showOnHost = true, showOnCommunity = true }, - new { label = "Instagram", platform = "instagram", url = "https://instagram.com/jayuhime", icon = "instagram", showOnHost = true, showOnCommunity = true }, - new { label = "Discord", platform = "discord", url = "https://discord.gg/jayuhime", icon = "discord", showOnHost = true, showOnCommunity = true }, - }), - FaqJson = JsonSerializer.Serialize(new[] - { - new { question = "Wer darf nominiert werden?", answer = "Jede:r aktive deutschsprachige VTuber kann nominiert werden — unabhängig von Follower-Zahl oder Plattform. Die Community schlägt in der Nominierungsphase ihre Favorit:innen vor." }, - new { question = "Wie funktioniert das Voting?", answer = "Du meldest dich ausschließlich mit deinem Twitch-Account an — nur so kannst du teilnehmen. Das hält das Voting fair und bot-frei. Danach vergibst du pro Kategorie eine Stimme, änderbar bis zum Ende der Phase." }, - new { question = "Was kostet die Teilnahme?", answer = "Gar nichts. Nominieren, voten und die Show schauen ist komplett kostenlos — der VTuber Star Award ist ein Community-Event von Fans für Fans." }, - new { question = "Wann und wo findet die Award-Show statt?", answer = "Die große Live-Show wird von Jayuhime gehostet und auf Twitch & YouTube gestreamt. Den genauen Termin findest du im Countdown oben — sei live dabei, wenn die Stars gekürt werden!" }, - new { question = "Ich wurde nominiert — was nun?", answer = "Glückwunsch! Du erhältst eine Benachrichtigung mit allen Infos. Teile deine Nominierung gern mit deiner Community und ruf zum Voten auf — jede Stimme zählt." }, - }), - }); - - modelBuilder.Entity().HasData( - new Season - { - Id = 1, - Year = 2026, - Name = "VTuber Star Awards 2026", - ShowStreamUrl = "https://twitch.tv/jayuhime", - IsCurrent = true, - IsCommunityOnly = true, - CurrentPhase = "Community Voting", - NominationStartsAt = new DateOnly(2026, 5, 1), - NominationEndsAt = new DateOnly(2026, 5, 31), - VotingStartsAt = new DateOnly(2026, 6, 1), - VotingEndsAt = new DateOnly(2026, 6, 30), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ShowDate = new DateOnly(2026, 7, 20), - ShowStartsAt = new TimeOnly(20, 0), - }, - new Season - { - Id = 2, - Year = 2025, - Name = "VTuber Star Awards 2025", - ShowStreamUrl = "https://twitch.tv/jayuhime", - IsCurrent = false, - IsCommunityOnly = true, - CurrentPhase = "Archived", - NominationStartsAt = new DateOnly(2025, 5, 1), - NominationEndsAt = new DateOnly(2025, 5, 31), - VotingStartsAt = new DateOnly(2025, 6, 1), - VotingEndsAt = new DateOnly(2025, 6, 30), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ShowDate = new DateOnly(2025, 7, 20), - ShowStartsAt = new TimeOnly(20, 0), - }, - new Season - { - Id = 3, - Year = 2024, - Name = "VTuber Star Awards 2024", - ShowStreamUrl = "https://youtube.com/c/Jayuhime", - IsCurrent = false, - IsCommunityOnly = true, - CurrentPhase = "Archived", - NominationStartsAt = new DateOnly(2024, 5, 1), - NominationEndsAt = new DateOnly(2024, 5, 31), - VotingStartsAt = new DateOnly(2024, 6, 1), - VotingEndsAt = new DateOnly(2024, 6, 30), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ShowDate = new DateOnly(2024, 7, 20), - ShowStartsAt = new TimeOnly(20, 0), - }, - new Season - { - Id = 4, - Year = 2023, - Name = "VTuber Star Awards 2023", - ShowStreamUrl = "https://twitch.tv/jayuhime", - IsCurrent = false, - IsCommunityOnly = true, - CurrentPhase = "Archived", - NominationStartsAt = new DateOnly(2023, 5, 1), - NominationEndsAt = new DateOnly(2023, 5, 31), - VotingStartsAt = new DateOnly(2023, 6, 1), - VotingEndsAt = new DateOnly(2023, 6, 30), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ShowDate = new DateOnly(2023, 7, 20), - ShowStartsAt = new TimeOnly(20, 0), - }); - - modelBuilder.Entity().HasData( - new Category { Id = 1, SeasonId = 1, GroupName = "Main Awards", Name = "VTuber des Jahres", Slug = "vtuber-des-jahres", Description = "Die größte Auszeichnung des Jahres.", SortOrder = 1, MaxNomineesPerUser = 3 }, - new Category { Id = 2, SeasonId = 1, GroupName = "Performance", Name = "Bestes Live Event", Slug = "bestes-live-event", Description = "Events, Konzerte und 3D-Shows.", SortOrder = 2, MaxNomineesPerUser = 3 }, - new Category { Id = 3, SeasonId = 1, GroupName = "Clips & Highlights", Name = "Clip des Jahres", Slug = "clip-des-jahres", Description = "Der lustigste oder emotionalste Clip des Jahres.", SortOrder = 3, MaxNomineesPerUser = 3 }, - new Category { Id = 4, SeasonId = 1, GroupName = "Main Awards", Name = "Beste Community", Slug = "beste-community", Description = "Die aktivste und freundlichste Community.", SortOrder = 4, MaxNomineesPerUser = 3 }, - new Category { Id = 5, SeasonId = 2, GroupName = "Main Awards", Name = "VTuber des Jahres", Slug = "vtuber-des-jahres", Description = "Archivkategorie 2025.", SortOrder = 1, MaxNomineesPerUser = 3 }, - new Category { Id = 6, SeasonId = 2, GroupName = "Performance", Name = "Bestes Live Event", Slug = "bestes-live-event", Description = "Archivkategorie 2025.", SortOrder = 2, MaxNomineesPerUser = 3 }, - new Category { Id = 7, SeasonId = 2, GroupName = "Clips & Highlights", Name = "Clip des Jahres", Slug = "clip-des-jahres", Description = "Archivkategorie 2025.", SortOrder = 3, MaxNomineesPerUser = 3 }, - new Category { Id = 8, SeasonId = 3, GroupName = "Main Awards", Name = "VTuber des Jahres", Slug = "vtuber-des-jahres", Description = "Archivkategorie 2024.", SortOrder = 1, MaxNomineesPerUser = 3 }, - new Category { Id = 9, SeasonId = 3, GroupName = "Clips & Highlights", Name = "Clip des Jahres", Slug = "clip-des-jahres", Description = "Archivkategorie 2024.", SortOrder = 2, MaxNomineesPerUser = 3 }, - new Category { Id = 10, SeasonId = 4, GroupName = "Main Awards", Name = "VTuber des Jahres", Slug = "vtuber-des-jahres", Description = "Archivkategorie 2023.", SortOrder = 1, MaxNomineesPerUser = 3 }); - - modelBuilder.Entity().HasData( - new Candidate { Id = 1, SeasonId = 1, CategoryId = 1, DisplayName = "Hoshimi Miyu", ChannelSlug = "@hoshimimiyu", Platform = "Twitch" }, - new Candidate { Id = 2, SeasonId = 1, CategoryId = 1, DisplayName = "Kurainu", ChannelSlug = "@kurainu", Platform = "Twitch" }, - new Candidate { Id = 3, SeasonId = 1, CategoryId = 1, DisplayName = "Shiro Ch.", ChannelSlug = "@shiroch", Platform = "Twitch" }, - new Candidate { Id = 4, SeasonId = 1, CategoryId = 2, DisplayName = "Kurainu 3D Live", ChannelSlug = "@kurainu", Platform = "Twitch" }, - new Candidate { Id = 5, SeasonId = 1, CategoryId = 2, DisplayName = "Aoi Sakura Showcase", ChannelSlug = "@aoisakura", Platform = "YouTube" }, - new Candidate { Id = 6, SeasonId = 1, CategoryId = 3, DisplayName = "Pyonkichi Kingdom", ChannelSlug = "@pyonkichikingdom", Platform = "Twitch" }, - new Candidate { Id = 7, SeasonId = 1, CategoryId = 4, DisplayName = "Moonrelay", ChannelSlug = "@moonrelay", Platform = "Twitch" }, - new Candidate { Id = 8, SeasonId = 2, CategoryId = 5, DisplayName = "Hoshimi Miyu", ChannelSlug = "@hoshimimiyu", Platform = "Twitch" }, - new Candidate { Id = 9, SeasonId = 2, CategoryId = 6, DisplayName = "Kurainu 3D Live", ChannelSlug = "@kurainu", Platform = "Twitch" }, - new Candidate { Id = 10, SeasonId = 2, CategoryId = 7, DisplayName = "Pyonkichi Kingdom", ChannelSlug = "@pyonkichikingdom", Platform = "Twitch" }, - new Candidate { Id = 11, SeasonId = 3, CategoryId = 8, DisplayName = "Aoi Sakura", ChannelSlug = "@aoisakura", Platform = "YouTube" }, - new Candidate { Id = 12, SeasonId = 3, CategoryId = 9, DisplayName = "Starbyte", ChannelSlug = "@starbyte", Platform = "Twitch" }, - new Candidate { Id = 13, SeasonId = 4, CategoryId = 10, DisplayName = "Tenshi Vox", ChannelSlug = "@tenshivox", Platform = "Twitch" }); - - modelBuilder.Entity().HasData( - new AwardResult { Id = 1, SeasonId = 2, CategoryId = 5, CandidateId = 8, CategoryName = "VTuber des Jahres" }, - new AwardResult { Id = 2, SeasonId = 2, CategoryId = 6, CandidateId = 9, CategoryName = "Bestes Live Event" }, - new AwardResult { Id = 3, SeasonId = 2, CategoryId = 7, CandidateId = 10, CategoryName = "Clip des Jahres" }, - new AwardResult { Id = 4, SeasonId = 3, CategoryId = 8, CandidateId = 11, CategoryName = "VTuber des Jahres" }, - new AwardResult { Id = 5, SeasonId = 3, CategoryId = 9, CandidateId = 12, CategoryName = "Clip des Jahres" }, - new AwardResult { Id = 6, SeasonId = 4, CategoryId = 10, CandidateId = 13, CategoryName = "VTuber des Jahres" }); - - modelBuilder.Entity().HasData( - new Nomination { Id = 1, SeasonId = 1, CategoryId = 1, SubmittedByTwitchId = "twitch_hoshi", CandidateText = "Hoshimi Miyu", CreatedAt = new DateTimeOffset(2026, 6, 10, 13, 0, 0, TimeSpan.Zero) }, - new Nomination { Id = 2, SeasonId = 1, CategoryId = 2, SubmittedByTwitchId = "twitch_kurainu", CandidateText = "Kurainu 3D Live", CreatedAt = new DateTimeOffset(2026, 6, 10, 14, 0, 0, TimeSpan.Zero) }); - - modelBuilder.Entity().HasData( - new VoteBallot { Id = 1, SeasonId = 1, SubmittedByTwitchId = "twitch_vote_1", Status = "submitted", SubmittedAt = new DateTimeOffset(2026, 6, 11, 12, 0, 0, TimeSpan.Zero) }, - new VoteBallot { Id = 2, SeasonId = 1, SubmittedByTwitchId = "twitch_vote_2", Status = "submitted", SubmittedAt = new DateTimeOffset(2026, 6, 11, 12, 5, 0, TimeSpan.Zero) }); - - modelBuilder.Entity().HasData( - new VoteEntry { Id = 1, BallotId = 1, CategoryId = 1, CandidateId = 1 }, - new VoteEntry { Id = 2, BallotId = 1, CategoryId = 2, CandidateId = 4 }, - new VoteEntry { Id = 3, BallotId = 2, CategoryId = 1, CandidateId = 2 }, - new VoteEntry { Id = 4, BallotId = 2, CategoryId = 3, CandidateId = 6 }); - } -} diff --git a/Backend/Data/SeedDataBootstrapper.cs b/Backend/Data/SeedDataBootstrapper.cs deleted file mode 100644 index f3680ac..0000000 --- a/Backend/Data/SeedDataBootstrapper.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static partial class SeedDataBootstrapper -{ - public static async Task EnsureAsync(AwardsDbContext db) - { - await EnsureSiteSettingsAsync(db); - - var seasons = await db.Seasons.ToDictionaryAsync(item => item.Year); - if (seasons.Count == 0) - { - return; - } - - foreach (var season in seasons.Values) - { - await EnsureCategoriesAsync(db, season); - } - - if (seasons.TryGetValue(2026, out var currentSeason)) - { - await EnsureCandidatesAsync(db, currentSeason, SeedCatalog.CurrentCandidateSeeds); - await EnsureSponsorsAsync(db, currentSeason); - await EnsureSeedOperationalDataAsync(db, currentSeason); - } - - foreach (var year in new[] { 2025, 2024, 2023 }) - { - if (!seasons.TryGetValue(year, out var season)) - { - continue; - } - - var winners = SeedCatalog.WinnerSeeds.Where(item => item.Year == year).ToArray(); - await EnsureCandidatesAsync(db, season, winners.Select(item => new CandidateSeed(item.CategorySlug, item.DisplayName, item.ChannelSlug, item.Platform)).ToArray()); - await EnsureWinnersAsync(db, season, winners); - } - - await db.SaveChangesAsync(); - } -} diff --git a/Backend/Data/SeedOperationalDataBootstrapper.cs b/Backend/Data/SeedOperationalDataBootstrapper.cs deleted file mode 100644 index 3255758..0000000 --- a/Backend/Data/SeedOperationalDataBootstrapper.cs +++ /dev/null @@ -1,554 +0,0 @@ -using System.Text.Json; -using Backend.Domain; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static partial class SeedDataBootstrapper -{ - private static async Task EnsureSeedOperationalDataAsync(AwardsDbContext db, Season season) - { - var categories = await db.Categories - .Where(item => item.SeasonId == season.Id) - .ToDictionaryAsync(item => item.Slug, StringComparer.OrdinalIgnoreCase); - var candidates = await db.Candidates - .Where(item => item.SeasonId == season.Id) - .ToArrayAsync(); - - var normalizedLegacyState = await NormalizeLegacyDemoLabelsAsync(db); - await EnsureSeedReviewNominationsAsync(db, season, categories, candidates); - - if (!await db.ClipSubmissions.AnyAsync(item => item.SeasonId == season.Id)) - { - db.ClipSubmissions.AddRange( - new ClipSubmission - { - SeasonId = season.Id, - CategoryId = ResolveCategoryId(categories, "vtuber-des-jahres-shining-star"), - CandidateId = ResolveCandidateId(categories, candidates, "vtuber-des-jahres-shining-star", "Hoshimi Miyu"), - SubmittedByTwitchId = "local_user_3", - ClipUrl = "https://clips.twitch.tv/StarlitDebutMoment", - Title = "Starlight Debut Moment", - Creator = "Hoshimi Miyu", - Platform = "Twitch", - Status = "approved", - ReviewNote = "Geprüfter Clip für Voting-Vorschau.", - ReviewedByTwitchId = "jayuhime_admin", - CreatedFromIp = "127.0.0.1", - CreatedAt = new DateTimeOffset(2026, 6, 18, 8, 15, 0, TimeSpan.Zero), - ReviewedAt = new DateTimeOffset(2026, 6, 18, 12, 5, 0, TimeSpan.Zero), - }, - new ClipSubmission - { - SeasonId = season.Id, - CategoryId = ResolveCategoryId(categories, "vtuber-des-jahres-shining-star"), - CandidateId = ResolveCandidateId(categories, candidates, "vtuber-des-jahres-shining-star", "Kurainu"), - SubmittedByTwitchId = "local_user_4", - ClipUrl = "https://clips.twitch.tv/KurainuFinaleHype", - Title = "Finale-Hype mit Chat-Chaos", - Creator = "Kurainu", - Platform = "Twitch", - Status = "approved", - ReviewNote = "Geprüfter Clip für Voting-Vorschau.", - ReviewedByTwitchId = "jayuhime_admin", - CreatedFromIp = "127.0.0.1", - CreatedAt = new DateTimeOffset(2026, 6, 18, 8, 35, 0, TimeSpan.Zero), - ReviewedAt = new DateTimeOffset(2026, 6, 18, 12, 10, 0, TimeSpan.Zero), - }, - new ClipSubmission - { - SeasonId = season.Id, - CategoryId = ResolveCategoryId(categories, "best-gaming-shining-star"), - CandidateId = ResolveCandidateId(categories, candidates, "best-gaming-shining-star", "Kurainu"), - SubmittedByTwitchId = "local_user", - ClipUrl = "https://clips.twitch.tv/EpicGamingMoment", - Title = "Epischer Clutch im Finale", - Creator = "Kurainu", - Platform = "Twitch", - Status = "pending", - CreatedFromIp = "127.0.0.1", - CreatedAt = new DateTimeOffset(2026, 6, 17, 9, 10, 0, TimeSpan.Zero), - }, - new ClipSubmission - { - SeasonId = season.Id, - CategoryId = ResolveCategoryId(categories, "gesang-musik-shining-star"), - CandidateId = ResolveCandidateId(categories, candidates, "gesang-musik-shining-star", "Melo Diva"), - SubmittedByTwitchId = "local_user_2", - ClipUrl = "https://www.youtube.com/watch?v=liveCoverMoment", - Title = "Live-Cover mit Gänsehaut", - Creator = "Melo Diva", - Platform = "YouTube", - Status = "approved", - ReviewNote = "Geprüfter Clip für Review-Workflow.", - ReviewedByTwitchId = "jayuhime_admin", - CreatedFromIp = "127.0.0.1", - CreatedAt = new DateTimeOffset(2026, 6, 18, 10, 30, 0, TimeSpan.Zero), - ReviewedAt = new DateTimeOffset(2026, 6, 18, 12, 0, 0, TimeSpan.Zero), - }); - } - - if (!normalizedLegacyState.HasRiskSeed && !await db.RiskFlags.AnyAsync(item => item.Source == "seed")) - { - db.RiskFlags.Add(new RiskFlag - { - SeasonId = season.Id, - TwitchUserId = "sample_user", - Source = "seed", - Type = "rapid_vote_updates", - Severity = "medium", - Status = "open", - Summary = "Mehrere Voting-Aenderungen in kurzer Zeit erkannt.", - CreatedFromIp = "127.0.0.1", - UserAgent = "seed-bootstrap", - MetadataJson = JsonSerializer.Serialize(new { recentVoteSubmissions = 3 }), - CreatedAt = new DateTimeOffset(2026, 6, 17, 8, 40, 0, TimeSpan.Zero), - }); - } - - if (!normalizedLegacyState.HasAuditSeed && !await db.AdminAuditEntries.AnyAsync(item => item.ActionType == "seed.initialize")) - { - db.AdminAuditEntries.Add(new AdminAuditEntry - { - AdminTwitchUserId = "system", - ActionType = "seed.initialize", - EntityType = "database", - EntityId = season.Year.ToString(), - Summary = "Startinhalte wurden in der Datenbank bereitgestellt.", - MetadataJson = JsonSerializer.Serialize(new { awardCategories = SeedCatalog.AwardCategorySeeds.Length, subcategories = SeedCatalog.DefaultSubcategoryTemplates.Length }), - CreatedFromIp = "seed", - UserAgent = "seed-bootstrap", - CreatedAt = new DateTimeOffset(2026, 6, 17, 8, 32, 0, TimeSpan.Zero), - }); - } - } - - private static int? ResolveCategoryId(IReadOnlyDictionary categories, string slug) => - categories.TryGetValue(slug, out var category) ? category.Id : null; - - private static int? ResolveCandidateId( - IReadOnlyDictionary categories, - IEnumerable candidates, - string categorySlug, - string displayName) - { - var categoryId = ResolveCategoryId(categories, categorySlug); - return categoryId is int resolvedCategoryId - ? candidates.FirstOrDefault(item => - item.CategoryId == resolvedCategoryId - && string.Equals(item.DisplayName, displayName, StringComparison.OrdinalIgnoreCase))?.Id - : null; - } - - private static async Task NormalizeLegacyDemoLabelsAsync(AwardsDbContext db) - { - var legacySessions = await db.UserSessions - .Where(item => item.TwitchUserId == "admin_demo" || item.TwitchUserId == "jayuhime_demo" || item.TwitchUserId == "demo_user") - .ToArrayAsync(); - - foreach (var session in legacySessions) - { - session.TwitchUserId = session.TwitchUserId switch - { - "admin_demo" => "jayuhime_admin", - "jayuhime_demo" => "jayuhime_viewer", - "demo_user" => "local_user", - _ => session.TwitchUserId, - }; - session.DisplayName = session.DisplayName switch - { - "Admin Demo" => "Jayuhime Admin", - "Demo User" => "Local User", - _ => session.DisplayName, - }; - } - - var legacyClipSubmissions = await db.ClipSubmissions - .Where(item => - item.SubmittedByTwitchId == "demo_user" || - item.SubmittedByTwitchId == "demo_user_2" || - item.ClipUrl.Contains("Demo") || - item.ClipUrl.Contains("demo") || - (item.ReviewNote != null && item.ReviewNote.Contains("Demo-Clip"))) - .ToArrayAsync(); - - foreach (var clip in legacyClipSubmissions) - { - clip.SubmittedByTwitchId = clip.SubmittedByTwitchId switch - { - "demo_user" => "local_user", - "demo_user_2" => "local_user_2", - _ => clip.SubmittedByTwitchId, - }; - clip.ClipUrl = clip.ClipUrl - .Replace("DemoGamingMoment", "EpicGamingMoment") - .Replace("demoSong", "liveCoverMoment"); - clip.ReviewNote = clip.ReviewNote?.Replace("Demo-Clip", "Geprüfter Clip"); - } - await LinkExistingClipsToCandidatesAsync(db); - - var legacyRiskFlags = await db.RiskFlags - .Where(item => - item.Source == "demo" || - item.Summary.StartsWith("Demo:") || - item.TwitchUserId == "jayuhime_demo" || - item.TwitchUserId == "demo_user") - .ToArrayAsync(); - - foreach (var flag in legacyRiskFlags) - { - flag.Source = "seed"; - flag.TwitchUserId = flag.TwitchUserId switch - { - "demo_user" => "local_user", - "jayuhime_demo" => "jayuhime_viewer", - _ => flag.TwitchUserId, - }; - flag.Summary = flag.Summary.Replace("Demo: ", string.Empty); - flag.UserAgent = flag.UserAgent == "demo-seed" ? "seed-bootstrap" : flag.UserAgent; - } - - var legacyAuditEntries = await db.AdminAuditEntries - .Where(item => - item.ActionType == "demo.seed" || - item.Summary.Contains("Demo-Inhalte") || - item.AdminTwitchUserId == "admin_demo" || - item.AdminTwitchUserId == "jayuhime_demo") - .ToArrayAsync(); - - foreach (var entry in legacyAuditEntries) - { - entry.AdminTwitchUserId = entry.AdminTwitchUserId switch - { - "admin_demo" => "jayuhime_admin", - "jayuhime_demo" => "jayuhime_viewer", - _ => entry.AdminTwitchUserId, - }; - if (entry.ActionType == "demo.seed") - { - entry.ActionType = "seed.initialize"; - } - if (entry.Summary.Contains("Demo-Inhalte")) - { - entry.Summary = "Startinhalte wurden in der Datenbank bereitgestellt."; - } - } - - return new LegacySeedState( - legacyRiskFlags.Length > 0 || await db.RiskFlags.AnyAsync(item => item.Source == "seed"), - legacyAuditEntries.Length > 0 || await db.AdminAuditEntries.AnyAsync(item => item.ActionType == "seed.initialize")); - } - - private static async Task LinkExistingClipsToCandidatesAsync(AwardsDbContext db) - { - var clips = await db.ClipSubmissions - .Where(item => item.CandidateId == null && item.CategoryId != null && item.Creator != string.Empty) - .ToArrayAsync(); - if (clips.Length == 0) - { - return; - } - - var seasonIds = clips.Select(item => item.SeasonId).Distinct().ToArray(); - var categoryIds = clips.Select(item => item.CategoryId!.Value).Distinct().ToArray(); - var candidates = await db.Candidates - .Where(item => seasonIds.Contains(item.SeasonId) && categoryIds.Contains(item.CategoryId)) - .ToArrayAsync(); - - foreach (var clip in clips) - { - var creatorKey = NormalizeSeedCandidateKey(clip.Creator); - var candidate = candidates.FirstOrDefault(item => - item.SeasonId == clip.SeasonId - && item.CategoryId == clip.CategoryId - && (NormalizeSeedCandidateKey(item.DisplayName) == creatorKey - || NormalizeSeedCandidateKey(item.ChannelSlug) == creatorKey)); - - if (candidate is not null) - { - clip.CandidateId = candidate.Id; - } - } - } - - private static string NormalizeSeedCandidateKey(string value) => - new( - value - .Trim() - .TrimStart('@') - .ToLowerInvariant() - .Where(char.IsLetterOrDigit) - .ToArray()); - - private static async Task EnsureSeedReviewNominationsAsync( - AwardsDbContext db, - Season season, - IReadOnlyDictionary categories, - Candidate[] candidates) - { - var staleSeedNominations = await db.Nominations - .Where(item => - item.SeasonId == season.Id - && ( - item.SubmittedByTwitchId.StartsWith("seed_review_") - || item.SubmittedByTwitchId == "twitch_hoshi" - || item.SubmittedByTwitchId == "twitch_kurainu" - || item.SubmittedByTwitchId.StartsWith("demo_user") - || item.SubmittedByTwitchId.StartsWith("local_user") - )) - .ToArrayAsync(); - - if (staleSeedNominations.Length > 0) - { - db.Nominations.RemoveRange(staleSeedNominations); - await db.SaveChangesAsync(); - } - - var orderedCategories = categories.Values - .OrderBy(item => item.SortOrder) - .ThenBy(item => item.Name) - .ToArray(); - var candidatesByCategoryId = candidates - .GroupBy(item => item.CategoryId) - .ToDictionary( - grouping => grouping.Key, - grouping => grouping.OrderBy(item => item.DisplayName).ToArray()); - - var seedNominations = new List(); - var createdAt = new DateTimeOffset(2026, 6, 22, 12, 0, 0, TimeSpan.Zero); - - foreach (var category in orderedCategories) - { - candidatesByCategoryId.TryGetValue(category.Id, out var categoryCandidates); - var existingCandidate = categoryCandidates?.FirstOrDefault(); - - seedNominations.AddRange(BuildPendingSeedGroup( - category, - existingCandidate, - groupKey: "existing", - firstSubmitter: $"seed_review_{category.Slug}_existing_a", - secondSubmitter: $"seed_review_{category.Slug}_existing_b", - createdAt, - useSuggestedCategory: true, - trackerStatus: "resolved")); - createdAt = createdAt.AddMinutes(8); - - seedNominations.AddRange(BuildPendingSeedGroup( - category, - existingCandidate: null, - groupKey: "fresh", - firstSubmitter: $"seed_review_{category.Slug}_fresh_a", - secondSubmitter: $"seed_review_{category.Slug}_fresh_b", - createdAt, - useSuggestedCategory: false, - trackerStatus: "unsupported_platform")); - createdAt = createdAt.AddMinutes(8); - } - - foreach (var category in orderedCategories.Take(2)) - { - seedNominations.AddRange(BuildReviewedSeedGroup( - category, - status: "rejected", - displayName: $"{category.GroupName} Review Return", - submittedByPrefix: $"seed_review_{category.Slug}_rejected", - createdAt, - reviewedByTwitchId: "jayuhime_admin", - candidateId: null, - candidateDisplayName: null)); - createdAt = createdAt.AddMinutes(10); - } - - foreach (var category in orderedCategories.Skip(2).Take(2)) - { - candidatesByCategoryId.TryGetValue(category.Id, out var categoryCandidates); - var candidate = categoryCandidates?.FirstOrDefault(); - seedNominations.AddRange(BuildReviewedSeedGroup( - category, - status: "approved", - displayName: candidate?.DisplayName ?? $"{category.GroupName} Approved Pick", - submittedByPrefix: $"seed_review_{category.Slug}_approved", - createdAt, - reviewedByTwitchId: "jayuhime_admin", - candidateId: candidate?.Id, - candidateDisplayName: candidate?.DisplayName)); - createdAt = createdAt.AddMinutes(10); - } - - db.Nominations.AddRange(seedNominations); - await db.SaveChangesAsync(); - } - - private static IEnumerable BuildPendingSeedGroup( - Category category, - Candidate? existingCandidate, - string groupKey, - string firstSubmitter, - string secondSubmitter, - DateTimeOffset createdAt, - bool useSuggestedCategory, - string trackerStatus) - { - var displayName = existingCandidate?.DisplayName ?? BuildFreshSeedName(category, groupKey); - var platform = existingCandidate?.Platform ?? "YouTube"; - var channelSlug = existingCandidate?.ChannelSlug ?? BuildSeedChannelSlug(category, groupKey); - var streamUrl = BuildSeedStreamUrl(platform, channelSlug); - var avgViewers = ResolveSeedViewerValue(category); - int? suggestedCategoryId = useSuggestedCategory ? category.Id : null; - - yield return new Nomination - { - SeasonId = category.SeasonId, - CategoryId = category.Id, - CategoryGroupName = category.GroupName, - SubmittedByTwitchId = firstSubmitter, - CandidateText = displayName, - StreamUrl = streamUrl, - ResolvedChannel = channelSlug.TrimStart('@'), - ResolvedPlatform = platform, - AvgViewers = avgViewers, - SuggestedCategoryId = suggestedCategoryId, - TrackerStatus = trackerStatus, - TrackerCheckedAt = createdAt.AddMinutes(2), - TrackingReviewStatus = "clear", - Status = "pending", - CreatedAt = createdAt, - }; - - yield return new Nomination - { - SeasonId = category.SeasonId, - CategoryId = category.Id, - CategoryGroupName = category.GroupName, - SubmittedByTwitchId = secondSubmitter, - CandidateText = displayName, - StreamUrl = streamUrl, - ResolvedChannel = channelSlug.TrimStart('@'), - ResolvedPlatform = platform, - AvgViewers = avgViewers, - SuggestedCategoryId = suggestedCategoryId, - TrackerStatus = trackerStatus, - TrackerCheckedAt = createdAt.AddMinutes(3), - TrackingReviewStatus = "clear", - Status = "pending", - CreatedAt = createdAt.AddMinutes(1), - }; - } - - private static IEnumerable BuildReviewedSeedGroup( - Category category, - string status, - string displayName, - string submittedByPrefix, - DateTimeOffset createdAt, - string reviewedByTwitchId, - int? candidateId, - string? candidateDisplayName) - { - var channelSlug = BuildSeedChannelSlug(category, $"{status}_{displayName}"); - var streamUrl = BuildSeedStreamUrl("Twitch", channelSlug); - var reviewNote = status == "approved" - ? "Seed-Datensatz: bereits als Kandidat übernommen." - : "Seed-Datensatz: bewusst verworfen für Undo-Tests."; - - yield return new Nomination - { - SeasonId = category.SeasonId, - CategoryId = category.Id, - CategoryGroupName = category.GroupName, - SubmittedByTwitchId = $"{submittedByPrefix}_a", - CandidateId = candidateId, - CandidateText = displayName, - StreamUrl = streamUrl, - ResolvedChannel = channelSlug.TrimStart('@'), - ResolvedPlatform = "Twitch", - AvgViewers = ResolveSeedViewerValue(category), - SuggestedCategoryId = category.Id, - TrackerStatus = "resolved", - TrackerCheckedAt = createdAt.AddMinutes(2), - TrackingReviewStatus = "reviewed", - TrackingReviewNote = reviewNote, - TrackingReviewedByTwitchId = reviewedByTwitchId, - TrackingReviewedAt = createdAt.AddMinutes(4), - Status = status, - ReviewNote = reviewNote, - ReviewedByTwitchId = reviewedByTwitchId, - CreatedAt = createdAt, - ReviewedAt = createdAt.AddMinutes(4), - }; - - yield return new Nomination - { - SeasonId = category.SeasonId, - CategoryId = category.Id, - CategoryGroupName = category.GroupName, - SubmittedByTwitchId = $"{submittedByPrefix}_b", - CandidateId = candidateId, - CandidateText = displayName, - StreamUrl = streamUrl, - ResolvedChannel = channelSlug.TrimStart('@'), - ResolvedPlatform = "Twitch", - AvgViewers = ResolveSeedViewerValue(category), - SuggestedCategoryId = category.Id, - TrackerStatus = "resolved", - TrackerCheckedAt = createdAt.AddMinutes(3), - TrackingReviewStatus = "reviewed", - TrackingReviewNote = reviewNote, - TrackingReviewedByTwitchId = reviewedByTwitchId, - TrackingReviewedAt = createdAt.AddMinutes(5), - Status = status, - ReviewNote = reviewNote, - ReviewedByTwitchId = reviewedByTwitchId, - CreatedAt = createdAt.AddMinutes(1), - ReviewedAt = createdAt.AddMinutes(5), - }; - } - - private static string BuildFreshSeedName(Category category, string groupKey) => - groupKey switch - { - "fresh" => $"{category.GroupName} Spotlight {category.Name}", - _ => $"{category.GroupName} {category.Name} Pick", - }; - - private static string BuildSeedChannelSlug(Category category, string suffix) - { - var raw = $"{category.Slug}-{suffix}" - .Trim() - .TrimStart('@') - .ToLowerInvariant(); - - return new string(raw.Where(char.IsLetterOrDigit).ToArray()); - } - - private static string BuildSeedStreamUrl(string platform, string channelSlug) => - platform.Trim().ToLowerInvariant() switch - { - "youtube" => $"https://www.youtube.com/@{channelSlug}", - "kick" => $"https://kick.com/{channelSlug}", - "cake" => $"https://cake.gg/{channelSlug}", - _ => $"https://www.twitch.tv/{channelSlug}", - }; - - private static int ResolveSeedViewerValue(Category category) - { - if (category.ViewerRangeMin is int min && category.ViewerRangeMax is int max) - { - return min + ((max - min) / 2); - } - - if (category.ViewerRangeMin is int lowerBound) - { - return lowerBound + 12; - } - - if (category.ViewerRangeMax is int upperBound) - { - return Math.Max(1, upperBound - 5); - } - - return 25; - } - - private sealed record LegacySeedState(bool HasRiskSeed, bool HasAuditSeed); -} diff --git a/Backend/Data/SeedSiteSettingsBootstrapper.cs b/Backend/Data/SeedSiteSettingsBootstrapper.cs deleted file mode 100644 index 54f8e3e..0000000 --- a/Backend/Data/SeedSiteSettingsBootstrapper.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Text.Json; -using Backend.Services; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static partial class SeedDataBootstrapper -{ - private static async Task EnsureSiteSettingsAsync(AwardsDbContext db) - { - var settings = await db.SiteSettings.FirstOrDefaultAsync(item => item.Id == 1); - if (settings is null) - { - return; - } - - if (!HasValidSiteArray(settings.FaqJson, "question", "answer")) - { - settings.FaqJson = JsonSerializer.Serialize(SeedCatalog.SiteFaqSeeds.Select(item => new - { - question = item.Question, - answer = item.Answer, - })); - } - - if (!HasValidSiteArray(settings.SocialLinksJson, "label", "platform", "url")) - { - settings.SocialLinksJson = JsonSerializer.Serialize(SeedCatalog.SiteSocialSeeds.Select(item => new - { - label = item.Label, - platform = item.Platform, - url = item.Url, - icon = item.Icon, - showOnHost = true, - showOnCommunity = true, - })); - } - - if (!HasValidRiskRules(settings.RiskRulesJson)) - { - settings.RiskRulesJson = RiskRuleSettings.Serialize(RiskRuleSettings.Defaults); - } - - if (!HasValidWorkflowRules(settings.WorkflowRulesJson)) - { - settings.WorkflowRulesJson = WorkflowRuleSettings.Serialize(WorkflowRuleSettings.Defaults); - } - - if (!HasValidTrackingRules(settings.TrackingRulesJson)) - { - settings.TrackingRulesJson = TrackingRulesSettings.Serialize(new TrackingRulesConfiguration( - TrackingRulesSettings.DefaultSource, - TrackingRulesSettings.DefaultImportantMetrics, - TrackingRulesSettings.DefaultOptionalMetrics, - TrackingRulesSettings.DefaultFlags)); - } - - if (string.IsNullOrWhiteSpace(settings.ViewerStatsProviderBaseUrl)) - { - settings.ViewerStatsProviderBaseUrl = TrackingRulesSettings.DefaultBaseUrl; - } - - if (string.IsNullOrWhiteSpace(settings.TrackingReviewNotes)) - { - settings.TrackingReviewNotes = """ -Fallback-Quellen für manuelle Reviews: -- SullyGnome Channel Summary -- Offizieller Twitch-Kanal - -Prüfe bei Edge Cases: -- passt der Kanal wirklich zur Unterkategorie? -- fehlen TwitchTracker-Daten nur temporär? -- braucht der Fall eine manuelle Team-Notiz? -"""; - } - - if (!HasValidNominationLinkBlacklist(settings.NominationLinkBlacklistJson)) - { - settings.NominationLinkBlacklistJson = NominationLinkBlacklistSettings.Serialize(NominationLinkBlacklistSettings.Defaults); - } - - if (string.IsNullOrWhiteSpace(settings.ClipSubmissionDisabledMessage)) - { - settings.ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen."; - } - - if (string.IsNullOrWhiteSpace(settings.ShowactApplicationDisabledMessage)) - { - settings.ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen."; - } - - if (string.IsNullOrWhiteSpace(settings.ImprintContent)) - { - settings.ImprintContent = SeedCatalog.DefaultImprintContent; - } - - if (string.IsNullOrWhiteSpace(settings.ContactContent)) - { - settings.ContactContent = SeedCatalog.DefaultContactContent; - } - - if (string.IsNullOrWhiteSpace(settings.SponsorsContent)) - { - settings.SponsorsContent = SeedCatalog.DefaultSponsorsContent; - } - - if (string.IsNullOrWhiteSpace(settings.ShowactsUrl)) - { - settings.ShowactsUrl = "https://vtuber-star-awards.de/showacts"; - } - - if (string.IsNullOrWhiteSpace(settings.ShowactsContent)) - { - settings.ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt für die Award-Show."; - } - - if (settings.SessionIdleTimeoutHours < 3) - { - settings.SessionIdleTimeoutHours = 3; - } - } - - private static bool HasValidSiteArray(string? json, params string[] requiredKeys) - { - if (string.IsNullOrWhiteSpace(json)) - { - return false; - } - - try - { - using var document = JsonDocument.Parse(json); - if (document.RootElement.ValueKind != JsonValueKind.Array) - { - return false; - } - - return document.RootElement.EnumerateArray().Any(item => - item.ValueKind == JsonValueKind.Object - && requiredKeys.All(key => - item.TryGetProperty(key, out var value) - && value.ValueKind == JsonValueKind.String - && !string.IsNullOrWhiteSpace(value.GetString()))); - } - catch (JsonException) - { - return false; - } - } - - private static bool HasValidRiskRules(string? json) => - RiskRuleSettings.Read(new Backend.Domain.SiteSettings { RiskRulesJson = json ?? string.Empty }).Length == RiskRuleSettings.Defaults.Length; - - private static bool HasValidWorkflowRules(string? json) => - WorkflowRuleSettings.Read(new Backend.Domain.SiteSettings { WorkflowRulesJson = json ?? string.Empty }).Length == WorkflowRuleSettings.Defaults.Length; - - private static bool HasValidTrackingRules(string? json) - { - var rules = TrackingRulesSettings.Read(new Backend.Domain.SiteSettings { TrackingRulesJson = json ?? string.Empty }); - return rules.ImportantMetrics.Length == TrackingRulesSettings.DefaultImportantMetrics.Length - && rules.OptionalMetrics.Length == TrackingRulesSettings.DefaultOptionalMetrics.Length - && rules.Flags.Length == TrackingRulesSettings.DefaultFlags.Length; - } - - private static bool HasValidNominationLinkBlacklist(string? json) - { - if (string.IsNullOrWhiteSpace(json)) - { - return false; - } - - try - { - using var document = JsonDocument.Parse(json); - return document.RootElement.ValueKind == JsonValueKind.Array - && document.RootElement.EnumerateArray().Any(item => - item.ValueKind == JsonValueKind.Object - && item.TryGetProperty("Url", out var url) - && url.ValueKind == JsonValueKind.String - && !string.IsNullOrWhiteSpace(url.GetString())); - } - catch (JsonException) - { - return false; - } - } -} diff --git a/Backend/Data/SeedSponsorsBootstrapper.cs b/Backend/Data/SeedSponsorsBootstrapper.cs deleted file mode 100644 index 862ecb1..0000000 --- a/Backend/Data/SeedSponsorsBootstrapper.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Backend.Domain; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static partial class SeedDataBootstrapper -{ - private static async Task EnsureSponsorsAsync(AwardsDbContext db, Season season) - { - var existingSponsors = await db.Sponsors - .Where(item => item.SeasonId == season.Id) - .ToListAsync(); - - foreach (var seed in SeedCatalog.DemoSponsorSeeds) - { - var sponsor = existingSponsors.FirstOrDefault(item => - string.Equals(item.Name, seed.Name, StringComparison.OrdinalIgnoreCase)) - ?? new Sponsor - { - SeasonId = season.Id, - CreatedAt = DateTimeOffset.UtcNow, - }; - - sponsor.Name = seed.Name; - sponsor.WebsiteUrl = seed.WebsiteUrl; - sponsor.LogoUrl = seed.LogoUrl; - sponsor.Description = seed.Description; - sponsor.Tier = seed.Tier; - sponsor.SortOrder = seed.SortOrder; - sponsor.IsVisible = true; - sponsor.UpdatedAt = DateTimeOffset.UtcNow; - - if (sponsor.Id == 0) - { - db.Sponsors.Add(sponsor); - existingSponsors.Add(sponsor); - } - } - } -} diff --git a/Backend/Data/SessionBootstrapper.cs b/Backend/Data/SessionBootstrapper.cs deleted file mode 100644 index dbab9fb..0000000 --- a/Backend/Data/SessionBootstrapper.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Backend.Data; - -public static class SessionBootstrapper -{ - public static Task EnsureAsync(AwardsDbContext db) => - db.Database.ExecuteSqlRawAsync( - """ - CREATE TABLE IF NOT EXISTS "UserSessions" ( - "Id" uuid NOT NULL PRIMARY KEY, - "SessionToken" character varying(120) NOT NULL, - "TwitchUserId" character varying(120) NOT NULL, - "DisplayName" character varying(120) NOT NULL, - "Role" character varying(40) NOT NULL, - "CreatedAt" timestamp with time zone NOT NULL, - "LastSeenAt" timestamp with time zone NOT NULL, - "IsActive" boolean NOT NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS "IX_UserSessions_SessionToken" - ON "UserSessions" ("SessionToken"); - """); -} diff --git a/Backend/Domain/Season.cs b/Backend/Domain/Season.cs index 23773e1..ff29432 100644 --- a/Backend/Domain/Season.cs +++ b/Backend/Domain/Season.cs @@ -5,7 +5,7 @@ public sealed class Season public int Id { get; set; } public int Year { get; set; } public string Name { get; set; } = string.Empty; - public string ShowStreamUrl { get; set; } = string.Empty; + public bool IsDemo { get; set; } public bool IsCurrent { get; set; } public bool IsCommunityOnly { get; set; } public string CurrentPhase { get; set; } = string.Empty; @@ -17,6 +17,8 @@ public sealed class Season public DateOnly ReviewEndsAt { get; set; } public DateOnly ShowDate { get; set; } public TimeOnly ShowStartsAt { get; set; } = new(20, 0); + public DateTimeOffset? WinnersPublishedAt { get; set; } + public string? WinnersPublishedByTwitchId { get; set; } public string SubcategoryTemplatesJson { get; set; } = "[]"; public string WorkflowRulesJson { get; set; } = "[]"; public ICollection Categories { get; set; } = []; diff --git a/Backend/Domain/SiteSettings.cs b/Backend/Domain/SiteSettings.cs index 0035c26..3e01d81 100644 --- a/Backend/Domain/SiteSettings.cs +++ b/Backend/Domain/SiteSettings.cs @@ -20,6 +20,22 @@ public sealed class SiteSettings public string SponsorsContent { get; set; } = string.Empty; public string ShowactsUrl { get; set; } = string.Empty; public string ShowactsContent { get; set; } = string.Empty; + public string StreamBannerEyebrow { get; set; } = "Das grosse Finale"; + public string StreamBannerTitle { get; set; } = "Award-Show Finale"; + public string StreamBannerText { get; set; } = string.Empty; + public string StreamBannerLiveButtonLabel { get; set; } = "Jetzt live · Zum Stream"; + public string StreamBannerLiveButtonUrl { get; set; } = string.Empty; + public string StreamBannerLockedButtonLabel { get; set; } = "Stream noch gesperrt"; + public bool StreamBannerUseCompletedContent { get; set; } + public string StreamBannerCompletedEyebrow { get; set; } = "Danke fürs Mitfiebern"; + public string StreamBannerCompletedTitle { get; set; } = "Award-Show abgeschlossen"; + public string StreamBannerCompletedText { get; set; } = "Die grosse Award-Show ist vorbei. Danke an alle, die live dabei waren."; + public string StreamBannerCompletedButtonLabel { get; set; } = "Highlights ansehen"; + public string StreamBannerCompletedButtonUrl { get; set; } = string.Empty; + public string AwardsSectionTitle { get; set; } = string.Empty; + public string AwardsSectionDescription { get; set; } = string.Empty; + public string SubcategoriesSectionTitle { get; set; } = string.Empty; + public string SubcategoriesSectionDescription { get; set; } = string.Empty; public string SocialLinksJson { get; set; } = "[]"; public string FaqJson { get; set; } = "[]"; public string RiskRulesJson { get; set; } = "[]"; diff --git a/Backend/Endpoints/AdminDashboardEndpoints.cs b/Backend/Endpoints/AdminDashboardEndpoints.cs index 6b0308e..6ef0730 100644 --- a/Backend/Endpoints/AdminDashboardEndpoints.cs +++ b/Backend/Endpoints/AdminDashboardEndpoints.cs @@ -1,4 +1,5 @@ using Backend.Contracts; +using Backend.Common; using Backend.Data; using Backend.Security; using Microsoft.EntityFrameworkCore; @@ -20,37 +21,39 @@ public static class AdminDashboardEndpoints return group; } - private static async Task GetDashboard(AwardsDbContext db, HttpContext context) + private static async Task GetDashboard(int? seasonId, AwardsDbContext db, HttpContext context) { var canViewAuditIp = CanViewAuditIp(context); - var currentSeason = await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.IsCurrent); - if (currentSeason is null) + var selectedSeason = seasonId.HasValue + ? await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.Id == seasonId.Value) + : await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.IsCurrent); + if (selectedSeason is null) { return Results.NotFound(); } - var nominationCount = await db.Nominations.CountAsync(item => item.SeasonId == currentSeason.Id); - var voteCount = await db.VoteEntries.CountAsync(item => item.Ballot.SeasonId == currentSeason.Id); - var categoryCount = await db.Categories.CountAsync(item => item.SeasonId == currentSeason.Id); - var reviewCount = await db.Nominations.CountAsync(item => item.SeasonId == currentSeason.Id && item.Status == "pending"); - var riskFlagCount = await db.RiskFlags.CountAsync(item => item.Status == "open"); + var selectedSeasonId = selectedSeason.Id; + var phaseKey = SeasonMappings.NormalizePhaseKey(selectedSeason.CurrentPhase); + var nominationCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId); + var voteCount = await db.VoteEntries.CountAsync(item => item.Ballot.SeasonId == selectedSeasonId); + var categoryCount = await db.Categories.CountAsync(item => item.SeasonId == selectedSeasonId); + var reviewCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId && item.Status == "pending"); + var riskFlagCount = await db.RiskFlags.CountAsync(item => + item.Status == "open" && + (item.SeasonId == selectedSeasonId || item.SeasonId == null)); + var globalRiskFlagCount = await db.RiskFlags.CountAsync(item => + item.Status == "open" && + item.SeasonId == null); - var topCategoryNames = await db.VoteEntries - .AsNoTracking() - .Where(item => item.Ballot.SeasonId == currentSeason.Id) - .Select(item => item.Category.Name) - .ToListAsync(); - - var topCategories = topCategoryNames - .GroupBy(name => name) - .Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count())) - .OrderByDescending(item => item.Votes) - .Take(5) - .ToArray(); + var topCategories = phaseKey == "nomination" + ? await BuildTopNominationCategoriesAsync(db, selectedSeasonId) + : await BuildTopVotingCategoriesAsync(db, selectedSeasonId); var riskFlags = await db.RiskFlags .AsNoTracking() - .Where(item => item.Status == "open") + .Where(item => + item.Status == "open" && + (item.SeasonId == selectedSeasonId || item.SeasonId == null)) .OrderByDescending(item => item.CreatedAt) .Take(8) .ToArrayAsync(); @@ -74,18 +77,27 @@ public static class AdminDashboardEndpoints .ToArrayAsync(); var activityItems = auditEntries - .Take(3) + .Take(6) .Select(item => new AdminActivityDto(item.Summary, $"{Math.Max(1, (int)Math.Round((DateTimeOffset.UtcNow - item.CreatedAt).TotalMinutes))} Min.")) .ToArray(); return Results.Ok(new AdminDashboardResponse( + selectedSeason.Id, + selectedSeason.Year, + selectedSeason.Name, + selectedSeason.IsCurrent, new[] { - new AdminMetricDto("Nominierungen", nominationCount, "Gespeicherte Einreichungen im aktuellen Public-Jahr"), - new AdminMetricDto("Stimmen", voteCount, "Abgegebene Stimmen im aktuellen Public-Jahr"), - new AdminMetricDto("Kategorien", categoryCount, "Aktive Kategorien im aktuellen Public-Jahr"), - new AdminMetricDto("Reviews offen", reviewCount, "Offene Nominierungen mit Review-Bedarf"), - new AdminMetricDto("Risikohinweise", riskFlagCount, "Offene Risk Flags ueber alle Quellen"), + new AdminMetricDto("Nominierungen", nominationCount, $"Gespeicherte Einreichungen im Award-Jahr {selectedSeason.Year}"), + new AdminMetricDto("Stimmen", voteCount, $"Abgegebene Stimmen im Award-Jahr {selectedSeason.Year}"), + new AdminMetricDto("Kategorien", categoryCount, $"Aktive Kategorien im Award-Jahr {selectedSeason.Year}"), + new AdminMetricDto("Reviews offen", reviewCount, "Offene Nominierungen mit Review-Bedarf in diesem Jahr"), + new AdminMetricDto( + "Risikohinweise", + riskFlagCount, + globalRiskFlagCount > 0 + ? $"Offene Hinweise fuer {selectedSeason.Year}, inklusive {globalRiskFlagCount} globaler Hinweise" + : $"Offene Hinweise fuer {selectedSeason.Year}"), }, activityItems, topCategories, @@ -93,6 +105,45 @@ public static class AdminDashboardEndpoints auditEntries)); } + private static async Task BuildTopVotingCategoriesAsync(AwardsDbContext db, int seasonId) + { + var categoryNames = await db.VoteEntries + .AsNoTracking() + .Where(item => item.Ballot.SeasonId == seasonId) + .Select(item => item.Category.Name) + .ToListAsync(); + + return categoryNames + .GroupBy(name => name) + .Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Stimmen")) + .OrderByDescending(item => item.Value) + .Take(5) + .ToArray(); + } + + private static async Task BuildTopNominationCategoriesAsync(AwardsDbContext db, int seasonId) + { + var nominationCategories = await db.Nominations + .AsNoTracking() + .Where(item => item.SeasonId == seasonId) + .Select(item => new + { + CategoryName = item.Category != null ? item.Category.Name : null, + item.CategoryGroupName, + }) + .ToListAsync(); + + return nominationCategories + .Select(item => string.IsNullOrWhiteSpace(item.CategoryName) + ? string.IsNullOrWhiteSpace(item.CategoryGroupName) ? "Ohne Kategorie" : item.CategoryGroupName + : item.CategoryName) + .GroupBy(name => name) + .Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Nominierungen")) + .OrderByDescending(item => item.Value) + .Take(5) + .ToArray(); + } + private static async Task GetAuditEntries( int? limit, string? query, diff --git a/Backend/Endpoints/AdminSeasonCreateEndpoints.cs b/Backend/Endpoints/AdminSeasonCreateEndpoints.cs index 6664606..e74c5bc 100644 --- a/Backend/Endpoints/AdminSeasonCreateEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonCreateEndpoints.cs @@ -27,7 +27,6 @@ public static partial class AdminSeasonManagementEndpoints return Results.BadRequest(new { message = $"A season for {request.Year} already exists." }); } - var showStreamUrl = NormalizeSeasonStreamUrl(request.ShowStreamUrl); var settings = await db.SiteSettings .AsNoTracking() .FirstOrDefaultAsync(item => item.Id == 1, context.RequestAborted); @@ -37,7 +36,7 @@ public static partial class AdminSeasonManagementEndpoints { Year = request.Year, Name = request.Name.Trim(), - ShowStreamUrl = showStreamUrl, + IsDemo = false, CurrentPhase = request.CurrentPhase.Trim(), IsCurrent = request.IsCurrent, IsCommunityOnly = request.IsCommunityOnly, @@ -125,7 +124,6 @@ public static partial class AdminSeasonManagementEndpoints { request.IsCurrent, request.IsCommunityOnly, - showStreamUrl, request.CurrentPhase, request.ShowDate, request.ShowStartsAt, diff --git a/Backend/Endpoints/AdminSeasonDetailEndpoints.cs b/Backend/Endpoints/AdminSeasonDetailEndpoints.cs index 2046181..37c68ab 100644 --- a/Backend/Endpoints/AdminSeasonDetailEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonDetailEndpoints.cs @@ -208,6 +208,23 @@ public static partial class AdminSeasonManagementEndpoints item.Candidate.Platform)) .ToArrayAsync(); + var workflowRules = WorkflowRuleSettings.Read(season, settings); + var votingEntryRows = await db.VoteEntries + .AsNoTracking() + .Where(item => item.Category.SeasonId == seasonId) + .Select(item => new AdminVotingEntryRow( + item.BallotId, + item.CategoryId, + item.CandidateId)) + .ToArrayAsync(); + var votingWorkspace = BuildVotingWorkspace( + categories, + candidates, + pendingNominations, + resultItems, + votingEntryRows, + workflowRules); + var clipSubmissions = await db.ClipSubmissions .AsNoTracking() .Where(item => item.SeasonId == seasonId) @@ -232,7 +249,7 @@ public static partial class AdminSeasonManagementEndpoints season.Id, season.Year, season.Name, - NormalizeSeasonStreamUrl(season.ShowStreamUrl), + season.IsDemo, season.CurrentPhase, season.IsCurrent, season.IsCommunityOnly, @@ -244,6 +261,8 @@ public static partial class AdminSeasonManagementEndpoints season.ReviewEndsAt, season.ShowDate, season.ShowStartsAt, + season.WinnersPublishedAt, + season.WinnersPublishedByTwitchId, subcategoryTemplates, categories, candidates, @@ -253,9 +272,15 @@ public static partial class AdminSeasonManagementEndpoints settings?.TrackingReviewNotes ?? string.Empty, trackingRules.Source.ShowManualReviewNotesInReview, resultItems, + votingWorkspace, clipSubmissions)); } + private sealed record AdminVotingEntryRow( + int BallotId, + int CategoryId, + int CandidateId); + private sealed record AdminNominationRow( int Id, int? CategoryId, @@ -330,6 +355,165 @@ public static partial class AdminSeasonManagementEndpoints item.ReviewedAt); } + private static AdminVotingWorkspaceDto BuildVotingWorkspace( + AdminCategoryItemDto[] categories, + AdminCandidateItemDto[] candidates, + AdminNominationReviewItemDto[] pendingNominations, + AdminAwardResultItemDto[] results, + AdminVotingEntryRow[] voteEntries, + WorkflowRuleSetting[] workflowRules) + { + var resultMap = results.ToDictionary(item => item.CategoryId); + var totalBallots = voteEntries.Select(item => item.BallotId).Distinct().Count(); + var voteCountByCategory = voteEntries + .GroupBy(item => item.CategoryId) + .ToDictionary(group => group.Key, group => group.Count()); + var ballotCountByCategory = voteEntries + .GroupBy(item => item.CategoryId) + .ToDictionary(group => group.Key, group => group.Select(item => item.BallotId).Distinct().Count()); + var voteCountByCandidate = voteEntries + .GroupBy(item => (item.CategoryId, item.CandidateId)) + .ToDictionary(group => group.Key, group => group.Count()); + + var winnerPlacementsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.MaxWinnerPlacements); + var recommendedNominatorsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.RecommendedNominatorsPerSubcategory); + var winnerRequiresClipRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.WinnerRequiresClip); + + var workspaceItems = categories + .Select(category => + { + var categoryCandidates = candidates + .Where(candidate => candidate.CategoryId == category.Id) + .ToArray(); + var categoryVoteCount = voteCountByCategory.TryGetValue(category.Id, out var voteCount) ? voteCount : 0; + var categoryBallotCount = ballotCountByCategory.TryGetValue(category.Id, out var ballotCount) ? ballotCount : 0; + var nominationCount = categoryCandidates.Sum(candidate => Math.Max(candidate.NominationTally, 0)); + var openReviewCount = pendingNominations.Count(item => + item.CategoryId == category.Id + || item.SuggestedCategoryId == category.Id); + var existingResult = resultMap.GetValueOrDefault(category.Id); + var maxVotes = categoryCandidates.Length == 0 + ? 0 + : categoryCandidates.Max(candidate => voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0)); + var topVoteTieCount = maxVotes <= 0 + ? 0 + : categoryCandidates.Count(candidate => voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0) == maxVotes); + + var leaderboard = categoryCandidates + .Select(candidate => + { + var candidateVotes = voteCountByCandidate.GetValueOrDefault((category.Id, candidate.Id), 0); + var hasWinnerConflict = CandidateHasWinnerConflict(candidate, category.Id, results, winnerPlacementsRule); + return new AdminVotingCandidateRankDto( + candidate.Id, + candidate.DisplayName, + candidate.ChannelSlug, + candidate.Platform, + candidateVotes, + categoryVoteCount > 0 ? (int)Math.Round(candidateVotes * 100d / categoryVoteCount) : 0, + Math.Max(candidate.NominationTally, 0), + !string.IsNullOrWhiteSpace(candidate.ClipCompilationUrl), + string.IsNullOrWhiteSpace(candidate.ClipEmbedStatus) ? "unchecked" : candidate.ClipEmbedStatus, + hasWinnerConflict, + existingResult?.CandidateId == candidate.Id, + !string.Equals(candidate.AcceptanceStatus, "declined", StringComparison.OrdinalIgnoreCase), + maxVotes > 0 && candidateVotes == maxVotes && topVoteTieCount > 1); + }) + .OrderByDescending(item => item.Votes) + .ThenByDescending(item => item.NominationTally) + .ThenBy(item => item.DisplayName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var readyCandidates = leaderboard.Count(item => item.IsAccepted); + var leadingCandidate = leaderboard.FirstOrDefault(); + var hasMissingClip = winnerRequiresClipRule.Enabled + && leadingCandidate is not null + && !leadingCandidate.HasClip; + var hasRuleConflict = leadingCandidate?.HasWinnerConflict ?? false; + var hasOpenReviews = openReviewCount > 0; + var hasSoftNominatorWarning = recommendedNominatorsRule.Enabled + && nominationCount < Math.Max(1, recommendedNominatorsRule.Limit); + var winnerReady = existingResult is not null + || leadingCandidate is not null + && leadingCandidate.IsAccepted + && !hasMissingClip + && !hasRuleConflict + && !hasOpenReviews; + + return new AdminVotingCategoryWorkspaceItemDto( + category.Id, + category.GroupName, + category.Name, + category.SortOrder, + category.ViewerRangeMin, + category.ViewerRangeMax, + categoryVoteCount, + categoryBallotCount, + categoryCandidates.Length, + readyCandidates, + nominationCount, + openReviewCount, + existingResult is not null, + winnerReady, + topVoteTieCount > 1, + hasMissingClip, + hasRuleConflict, + hasOpenReviews, + hasSoftNominatorWarning, + leaderboard); + }) + .OrderBy(item => item.SortOrder) + .ThenBy(item => item.GroupName, StringComparer.OrdinalIgnoreCase) + .ThenBy(item => item.CategoryName, StringComparer.OrdinalIgnoreCase) + .ToArray(); + + var summary = new AdminVotingWorkspaceSummaryDto( + voteEntries.Length, + totalBallots, + workspaceItems.Length, + workspaceItems.Count(item => item.VoteCount > 0), + workspaceItems.Count(item => item.WinnerReady), + workspaceItems.Count(item => + item.HasMissingClip + || item.HasRuleConflict + || item.HasOpenReviews + || item.HasTopVoteTie), + workspaceItems.Count(item => item.HasWinner)); + + return new AdminVotingWorkspaceDto(summary, workspaceItems); + } + + private static bool CandidateHasWinnerConflict( + AdminCandidateItemDto candidate, + int categoryId, + AdminAwardResultItemDto[] results, + WorkflowRuleSetting winnerPlacementsRule) + { + if (!winnerPlacementsRule.Enabled) + { + return false; + } + + var identityKey = candidate.StreamerIdentityId.HasValue + ? $"identity:{candidate.StreamerIdentityId.Value}" + : WorkflowRuleSettings.CandidateIdentityKey(candidate.DisplayName, candidate.ChannelSlug); + + var existingWinnerCount = results.Count(result => + { + if (result.CategoryId == categoryId) + { + return false; + } + + var resultIdentityKey = result.StreamerIdentityId.HasValue + ? $"identity:{result.StreamerIdentityId.Value}" + : WorkflowRuleSettings.CandidateIdentityKey(result.CandidateDisplayName, result.CandidateChannelSlug); + return string.Equals(resultIdentityKey, identityKey, StringComparison.Ordinal); + }); + + return existingWinnerCount >= winnerPlacementsRule.Limit; + } + private static AdminNominationReviewGroupDto[] BuildNominationReviewGroups( IEnumerable rows, IEnumerable categoryRows, diff --git a/Backend/Endpoints/AdminSeasonListEndpoints.cs b/Backend/Endpoints/AdminSeasonListEndpoints.cs index 91b0a79..48c935e 100644 --- a/Backend/Endpoints/AdminSeasonListEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonListEndpoints.cs @@ -17,7 +17,10 @@ public static partial class AdminSeasonManagementEndpoints item.Name, item.CurrentPhase, item.IsCurrent, - item.Categories.Count)) + item.IsDemo, + item.Categories.Count, + item.WinnersPublishedAt, + item.WinnersPublishedByTwitchId)) .ToArrayAsync(); return Results.Ok(seasons); diff --git a/Backend/Endpoints/AdminSeasonManagementEndpoints.cs b/Backend/Endpoints/AdminSeasonManagementEndpoints.cs index de8e7b7..4c88e8b 100644 --- a/Backend/Endpoints/AdminSeasonManagementEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonManagementEndpoints.cs @@ -72,6 +72,14 @@ public static partial class AdminSeasonManagementEndpoints .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, Backend.Security.AdminPermissionCatalog.Winners)) .WithName("DeleteAdminResult") .WithOpenApi(); + group.MapPost("/seasons/{seasonId:int}/winners/publish", PublishWinners) + .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, Backend.Security.AdminPermissionCatalog.Winners)) + .WithName("PublishAdminSeasonWinners") + .WithOpenApi(); + group.MapPost("/seasons/{seasonId:int}/winners/unpublish", UnpublishWinners) + .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, Backend.Security.AdminPermissionCatalog.Winners)) + .WithName("UnpublishAdminSeasonWinners") + .WithOpenApi(); group.MapGet("/seasons/{seasonId:int}/workflow-rules", GetWorkflowRules) .AddEndpointFilter((context, next) => AdminEndpointConventions.RequirePermission(context, next, Backend.Security.AdminPermissionCatalog.Settings)) .WithName("GetAdminWorkflowRules") diff --git a/Backend/Endpoints/AdminSeasonManagementSupport.cs b/Backend/Endpoints/AdminSeasonManagementSupport.cs index 9114085..355c850 100644 --- a/Backend/Endpoints/AdminSeasonManagementSupport.cs +++ b/Backend/Endpoints/AdminSeasonManagementSupport.cs @@ -44,6 +44,13 @@ public static partial class AdminSeasonManagementEndpoints string ChannelSlug, string? ClipCompilationUrl); + private sealed record WinnerPublicationSnapshot( + int CategoryId, + int? StreamerIdentityId, + string DisplayName, + string ChannelSlug, + string? ClipCompilationUrl); + private static IResult? ValidateSeasonRequest(CreateSeasonRequest request) { if (request.Year < 2020 || request.Year > 2100) @@ -108,11 +115,6 @@ public static partial class AdminSeasonManagementEndpoints return null; } - private static string NormalizeSeasonStreamUrl(string? showStreamUrl) - { - return SeasonMappings.NormalizeSeasonStreamUrl(showStreamUrl); - } - private static bool IsKnownSeasonPhase(string? currentPhase) { var value = currentPhase?.Trim().ToLowerInvariant() ?? string.Empty; @@ -156,7 +158,17 @@ public static partial class AdminSeasonManagementEndpoints var issueList = issues.ToArray(); return Results.BadRequest(new { - message = $"Public-/Archiv-Readiness blockiert: {string.Join(" ", issueList)}", + message = $"Landingpage-Freigabe blockiert: {string.Join(" ", issueList)}", + issues = issueList, + }); + } + + private static IResult CreateWinnerPublicationError(IEnumerable issues) + { + var issueList = issues.ToArray(); + return Results.BadRequest(new + { + message = $"Gewinner-Freigabe blockiert: {string.Join(" ", issueList)}", issues = issueList, }); } @@ -421,6 +433,87 @@ public static partial class AdminSeasonManagementEndpoints return issues.ToArray(); } + private static async Task BuildWinnerPublicationIssuesAsync( + AwardsDbContext db, + int seasonId, + CancellationToken cancellationToken) + { + var categoryIds = await db.Categories + .AsNoTracking() + .Where(item => item.SeasonId == seasonId) + .Select(item => item.Id) + .ToArrayAsync(cancellationToken); + + var issues = new List(); + if (categoryIds.Length == 0) + { + issues.Add("Mindestens eine Kategorie ist erforderlich."); + return issues.ToArray(); + } + + var resultSnapshots = await db.Results + .AsNoTracking() + .Where(item => item.SeasonId == seasonId) + .Select(item => new WinnerPublicationSnapshot( + item.CategoryId, + item.Candidate.StreamerIdentityId, + item.Candidate.DisplayName, + item.Candidate.ChannelSlug, + item.Candidate.ClipCompilationUrl)) + .ToArrayAsync(cancellationToken); + + var categoriesWithResults = resultSnapshots + .Select(item => item.CategoryId) + .Distinct() + .Count(); + var missingResults = Math.Max(0, categoryIds.Length - categoriesWithResults); + if (missingResults > 0) + { + issues.Add($"{missingResults} Kategorien haben noch keinen Gewinner."); + } + + var openReviewCount = await db.Nominations + .AsNoTracking() + .CountAsync(item => item.SeasonId == seasonId && item.Status == "pending", cancellationToken); + if (openReviewCount > 0) + { + issues.Add($"{openReviewCount} Nominierungs-Review(s) sind noch offen."); + } + + var workflowRules = await LoadWorkflowRulesAsync(db, seasonId, cancellationToken); + var winnerRequiresClipRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.WinnerRequiresClip); + if (WorkflowRuleSettings.ShouldBlock(winnerRequiresClipRule)) + { + var missingWinnerClipCount = resultSnapshots.Count(item => string.IsNullOrWhiteSpace(item.ClipCompilationUrl)); + if (missingWinnerClipCount > 0) + { + issues.Add($"{missingWinnerClipCount} Gewinner haben noch keinen gepflegten Clip-Link."); + } + } + + var winnerPlacementsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.MaxWinnerPlacements); + if (WorkflowRuleSettings.ShouldBlock(winnerPlacementsRule)) + { + var winnerOverflow = resultSnapshots + .GroupBy(item => ResolveCandidateIdentityKey(item.StreamerIdentityId, item.DisplayName, item.ChannelSlug)) + .Select(group => new + { + Count = group.Count(), + DisplayName = group.Select(item => item.DisplayName).FirstOrDefault(name => !string.IsNullOrWhiteSpace(name)) ?? "Unbekannt", + }) + .Where(item => item.Count > winnerPlacementsRule.Limit) + .OrderByDescending(item => item.Count) + .FirstOrDefault(); + if (winnerOverflow is not null) + { + issues.Add( + $"Workflow-Regel blockiert: {winnerOverflow.DisplayName} hat bereits {winnerOverflow.Count} Gewinnerplatz(e). Limit: {winnerPlacementsRule.Limit}."); + } + } + + return issues.ToArray(); + } + private static string ResolveCandidateIdentityKey(int? streamerIdentityId, string displayName, string channelSlug) { if (streamerIdentityId.HasValue) diff --git a/Backend/Endpoints/AdminSeasonResultsEndpoints.cs b/Backend/Endpoints/AdminSeasonResultsEndpoints.cs index c810972..a1e93d0 100644 --- a/Backend/Endpoints/AdminSeasonResultsEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonResultsEndpoints.cs @@ -73,6 +73,7 @@ public static partial class AdminSeasonManagementEndpoints } } + var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId); var existingResult = await db.Results.FirstOrDefaultAsync(item => item.SeasonId == seasonId && item.CategoryId == request.CategoryId); @@ -94,6 +95,13 @@ public static partial class AdminSeasonManagementEndpoints existingResult.CategoryName = category.Name; } + var wasPublished = season?.WinnersPublishedAt is not null; + if (wasPublished && season is not null) + { + season.WinnersPublishedAt = null; + season.WinnersPublishedByTwitchId = null; + } + adminAuditService.AddEntry( session.TwitchUserId, "result.set", @@ -106,6 +114,7 @@ public static partial class AdminSeasonManagementEndpoints categoryId = request.CategoryId, candidateId = request.CandidateId, candidateName = candidate.DisplayName, + unpublishedWinners = wasPublished, }, RequestMetadataReader.Read(context)); @@ -130,12 +139,20 @@ public static partial class AdminSeasonManagementEndpoints var result = await db.Results .Include(item => item.Category) .Include(item => item.Candidate) + .Include(item => item.Season) .FirstOrDefaultAsync(item => item.Id == resultId); if (result is null) { return Results.NotFound(); } + var wasPublished = result.Season.WinnersPublishedAt is not null; + if (wasPublished) + { + result.Season.WinnersPublishedAt = null; + result.Season.WinnersPublishedByTwitchId = null; + } + db.Results.Remove(result); adminAuditService.AddEntry( session.TwitchUserId, @@ -149,10 +166,98 @@ public static partial class AdminSeasonManagementEndpoints result.CategoryId, result.CandidateId, candidateName = result.Candidate.DisplayName, + unpublishedWinners = wasPublished, }, RequestMetadataReader.Read(context)); await db.SaveChangesAsync(context.RequestAborted); return Results.Ok(new { deleted = true, resultId }); } + + private static async Task PublishWinners( + HttpContext context, + int seasonId, + AwardsDbContext db, + IAdminAuditService adminAuditService) + { + var session = AdminEndpointConventions.CurrentSession(context); + var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted); + if (season is null) + { + return Results.NotFound(); + } + + var issues = await BuildWinnerPublicationIssuesAsync(db, seasonId, context.RequestAborted); + if (issues.Length > 0) + { + return CreateWinnerPublicationError(issues); + } + + season.WinnersPublishedAt = DateTimeOffset.UtcNow; + season.WinnersPublishedByTwitchId = session.TwitchUserId; + + adminAuditService.AddEntry( + session.TwitchUserId, + "winners.publish", + "season", + season.Id.ToString(), + $"Gewinner für {season.Year} wurden veröffentlicht.", + new + { + seasonId, + season.Year, + season.WinnersPublishedAt, + }, + RequestMetadataReader.Read(context)); + + await db.SaveChangesAsync(context.RequestAborted); + return Results.Ok(new + { + saved = true, + seasonId, + winnersPublishedAt = season.WinnersPublishedAt, + winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId, + }); + } + + private static async Task UnpublishWinners( + HttpContext context, + int seasonId, + AwardsDbContext db, + IAdminAuditService adminAuditService) + { + var session = AdminEndpointConventions.CurrentSession(context); + var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted); + if (season is null) + { + return Results.NotFound(); + } + + var previousPublishedAt = season.WinnersPublishedAt; + season.WinnersPublishedAt = null; + season.WinnersPublishedByTwitchId = null; + + adminAuditService.AddEntry( + session.TwitchUserId, + "winners.unpublish", + "season", + season.Id.ToString(), + $"Gewinner für {season.Year} wurden von der Landingpage zurückgenommen.", + new + { + seasonId, + season.Year, + previousPublishedAt, + }, + RequestMetadataReader.Read(context)); + + await db.SaveChangesAsync(context.RequestAborted); + return Results.Ok(new + { + saved = true, + seasonId, + winnersPublishedAt = season.WinnersPublishedAt, + winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId, + }); + } } diff --git a/Backend/Endpoints/AdminSeasonUpdateEndpoints.cs b/Backend/Endpoints/AdminSeasonUpdateEndpoints.cs index 6d457c5..0561a6c 100644 --- a/Backend/Endpoints/AdminSeasonUpdateEndpoints.cs +++ b/Backend/Endpoints/AdminSeasonUpdateEndpoints.cs @@ -33,8 +33,6 @@ public static partial class AdminSeasonManagementEndpoints return Results.BadRequest(new { message = $"A season for {request.Year} already exists." }); } - var showStreamUrl = NormalizeSeasonStreamUrl(request.ShowStreamUrl); - var wasCurrent = season.IsCurrent; var previousPhase = season.CurrentPhase; var previousPhaseKey = SeasonMappings.NormalizePhaseKey(previousPhase); @@ -58,7 +56,6 @@ public static partial class AdminSeasonManagementEndpoints season.Year = request.Year; season.Name = request.Name.Trim(); - season.ShowStreamUrl = showStreamUrl; season.CurrentPhase = request.CurrentPhase.Trim(); season.IsCommunityOnly = request.IsCommunityOnly; season.NominationStartsAt = request.NominationStartsAt; @@ -98,7 +95,6 @@ public static partial class AdminSeasonManagementEndpoints { request.Year, request.Name, - showStreamUrl, previousPhase, request.CurrentPhase, wasCurrent, diff --git a/Backend/Endpoints/AdminSiteSettingsEndpoints.cs b/Backend/Endpoints/AdminSiteSettingsEndpoints.cs index ed0f6bb..75de94e 100644 --- a/Backend/Endpoints/AdminSiteSettingsEndpoints.cs +++ b/Backend/Endpoints/AdminSiteSettingsEndpoints.cs @@ -86,6 +86,22 @@ public static class AdminSiteSettingsEndpoints settings.SponsorsContent, settings.ShowactsUrl, settings.ShowactsContent, + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerEyebrow), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerTitle), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerText), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerLiveButtonLabel), + settings.StreamBannerLiveButtonUrl, + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerLockedButtonLabel), + settings.StreamBannerUseCompletedContent, + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerCompletedEyebrow), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerCompletedTitle), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerCompletedText), + SeasonMappings.NormalizePlainTextContent(settings.StreamBannerCompletedButtonLabel), + settings.StreamBannerCompletedButtonUrl, + SeasonMappings.NormalizePlainTextContent(settings.AwardsSectionTitle), + SeasonMappings.NormalizePlainTextContent(settings.AwardsSectionDescription), + SeasonMappings.NormalizePlainTextContent(settings.SubcategoriesSectionTitle), + SeasonMappings.NormalizePlainTextContent(settings.SubcategoriesSectionDescription), SeasonMappings.ReadSocialLinks(settings), SeasonMappings.ReadFaqItems(settings), settings.ShowactFormSchemaJson ?? "[]")); @@ -134,6 +150,22 @@ public static class AdminSiteSettingsEndpoints settings.SponsorsContent = request.SponsorsContent.Trim(); settings.ShowactsUrl = normalizedUrls.ShowactsUrl; settings.ShowactsContent = request.ShowactsContent.Trim(); + settings.StreamBannerEyebrow = SeasonMappings.NormalizePlainTextContent(request.StreamBannerEyebrow); + settings.StreamBannerTitle = SeasonMappings.NormalizePlainTextContent(request.StreamBannerTitle); + settings.StreamBannerText = SeasonMappings.NormalizePlainTextContent(request.StreamBannerText); + settings.StreamBannerLiveButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerLiveButtonLabel); + settings.StreamBannerLiveButtonUrl = normalizedUrls.StreamBannerLiveButtonUrl; + settings.StreamBannerLockedButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerLockedButtonLabel); + settings.StreamBannerUseCompletedContent = request.StreamBannerUseCompletedContent; + settings.StreamBannerCompletedEyebrow = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedEyebrow); + settings.StreamBannerCompletedTitle = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedTitle); + settings.StreamBannerCompletedText = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedText); + settings.StreamBannerCompletedButtonLabel = SeasonMappings.NormalizePlainTextContent(request.StreamBannerCompletedButtonLabel); + settings.StreamBannerCompletedButtonUrl = normalizedUrls.StreamBannerCompletedButtonUrl; + settings.AwardsSectionTitle = SeasonMappings.NormalizePlainTextContent(request.AwardsSectionTitle); + settings.AwardsSectionDescription = SeasonMappings.NormalizePlainTextContent(request.AwardsSectionDescription); + settings.SubcategoriesSectionTitle = SeasonMappings.NormalizePlainTextContent(request.SubcategoriesSectionTitle); + settings.SubcategoriesSectionDescription = SeasonMappings.NormalizePlainTextContent(request.SubcategoriesSectionDescription); settings.SocialLinksJson = JsonSerializer.Serialize(socialLinks); settings.FaqJson = JsonSerializer.Serialize(request.Faq ?? []); settings.ShowactFormSchemaJson = request.ShowactFormSchemaJson ?? "[]"; @@ -171,7 +203,9 @@ public static class AdminSiteSettingsEndpoints || !TryNormalizePublicUrl(request.ImprintUrl, "Impressum-Link", out var imprintUrl, out errorMessage) || !TryNormalizePublicUrl(request.ContactUrl, "Kontakt-Link", out var contactUrl, out errorMessage) || !TryNormalizePublicUrl(request.SponsorsUrl, "Sponsoren-Link", out var sponsorsUrl, out errorMessage) - || !TryNormalizePublicUrl(request.ShowactsUrl, "Showact-Link", out var showactsUrl, out errorMessage)) + || !TryNormalizePublicUrl(request.ShowactsUrl, "Showact-Link", out var showactsUrl, out errorMessage) + || !TryNormalizePublicUrl(request.StreamBannerLiveButtonUrl, "Finale-Banner Button-Link", out var streamBannerLiveButtonUrl, out errorMessage) + || !TryNormalizePublicUrl(request.StreamBannerCompletedButtonUrl, "Finale-Banner Abschluss-Link", out var streamBannerCompletedButtonUrl, out errorMessage)) { return Results.BadRequest(new { message = errorMessage }); } @@ -185,6 +219,8 @@ public static class AdminSiteSettingsEndpoints ContactUrl = contactUrl, SponsorsUrl = sponsorsUrl, ShowactsUrl = showactsUrl, + StreamBannerLiveButtonUrl = streamBannerLiveButtonUrl, + StreamBannerCompletedButtonUrl = streamBannerCompletedButtonUrl, }; var normalizedSocialLinks = new List(); @@ -231,6 +267,8 @@ public static class AdminSiteSettingsEndpoints public string ContactUrl { get; set; } = string.Empty; public string SponsorsUrl { get; set; } = string.Empty; public string ShowactsUrl { get; set; } = string.Empty; + public string StreamBannerLiveButtonUrl { get; set; } = string.Empty; + public string StreamBannerCompletedButtonUrl { get; set; } = string.Empty; } private static async Task GetOptionalFeatureSettings(AwardsDbContext db) diff --git a/Backend/Endpoints/AdminTeamEndpoints.cs b/Backend/Endpoints/AdminTeamEndpoints.cs index 1812255..53c05eb 100644 --- a/Backend/Endpoints/AdminTeamEndpoints.cs +++ b/Backend/Endpoints/AdminTeamEndpoints.cs @@ -19,19 +19,20 @@ public static class AdminTeamEndpoints private static readonly AdminTeamPermissionDto[] PermissionCatalog = [ - new(AdminPermissionCatalog.Dashboard, "Dashboard", "Live-Lage, Aufgaben und Checks sehen.", "/admin/dashboard", true), - new(AdminPermissionCatalog.Years, "Jahre", "Award-Jahre anlegen und pflegen.", "/admin/years", false), - new(AdminPermissionCatalog.Nominations, "Nominierungen", "Nominierungen prüfen und entscheiden.", "/admin/nominations", false), - new(AdminPermissionCatalog.Categories, "Kategorien", "Kategorien und Limits verwalten.", "/admin/categories", false), - new(AdminPermissionCatalog.Candidates, "Kandidaten", "Kandidatenbasis bearbeiten.", "/admin/candidates", false), - new(AdminPermissionCatalog.Clips, "Clips", "Clip-Einreichungen prüfen.", "/admin/clips", false), - new(AdminPermissionCatalog.Risk, "Risiko", "Flags, Regeln und Moderationsrisiken sehen.", "/admin/risk", true), - new(AdminPermissionCatalog.Audit, "Audit-Log", "Admin-Aktionen nachvollziehen.", "/admin/users-logs", true), - new(AdminPermissionCatalog.Analytics, "Analytics", "Metriken und Rankings lesen.", "/admin/analytics", true), - new(AdminPermissionCatalog.Winners, "Gewinner", "Finale Ergebnisse pflegen und freigeben.", "/admin/winners", false), - new(AdminPermissionCatalog.Content, "Landingpage", "FAQ, Links, Datenschutz und öffentliche Inhalte pflegen.", "/admin/content", false), - new(AdminPermissionCatalog.Settings, "Einstellungen", "Systemchecks, Demo-Zugang und Wartung sehen.", "/admin/settings", true), - new(AdminPermissionCatalog.Team, "Team", "Mitglieder, Rollen und Berechtigungen verwalten.", "/admin/team", false), + new(AdminPermissionCatalog.Dashboard, "Dashboard", "Live-Lage, Aufgaben und Checks sehen.", "Betrieb", "/admin/dashboard", true), + new(AdminPermissionCatalog.Nominations, "Nominierungen", "Nominierungen prüfen und entscheiden.", "Betrieb", "/admin/nominations", false), + new(AdminPermissionCatalog.Years, "Jahre", "Award-Jahre anlegen und pflegen.", "Awards", "/admin/years", false), + new(AdminPermissionCatalog.Categories, "Kategorien", "Hauptkategorien, Unterkategorien und Limits verwalten.", "Awards", "/admin/categories", false), + new(AdminPermissionCatalog.Candidates, "Kandidaten", "Kandidatenbasis, Clips und Annahmestatus pflegen.", "Awards", "/admin/candidates", false), + new(AdminPermissionCatalog.Clips, "Clips", "Optionale Clip-Einreichungen prüfen.", "Awards", "/admin/clips", false), + new(AdminPermissionCatalog.Content, "Landingpage", "FAQ, Links, Footer, Showacts und öffentliche Inhalte pflegen.", "Landingpage", "/admin/content", false), + new(AdminPermissionCatalog.Risk, "Risiko", "Flags, Regeln und Moderationsrisiken sehen.", "Kontrolle", "/admin/risk", true), + new(AdminPermissionCatalog.Audit, "Audit-Log", "Admin-Aktionen nachvollziehen.", "Kontrolle", "/admin/users-logs", true), + new(AdminPermissionCatalog.Analytics, "Analytics", "Jahresmetriken und Überblick lesen.", "Auswertung", "/admin/analytics", true), + new(AdminPermissionCatalog.Voting, "Voting", "Stimmenlage und Gewinner-Vorbereitung sehen.", "Auswertung", "/admin/voting", true), + new(AdminPermissionCatalog.Winners, "Gewinner", "Finale Ergebnisse pflegen und freigeben.", "Auswertung", "/admin/winners", false), + new(AdminPermissionCatalog.Settings, "Einstellungen", "Systemchecks, Demo-Zugang, Wartung und Workflow-Steuerung sehen.", "Einstellungen", "/admin/settings", true), + new(AdminPermissionCatalog.Team, "Team", "Mitglieder, Rollen und Berechtigungen verwalten.", "Einstellungen", "/admin/team", false), ]; private static readonly AdminTeamRoleDto[] DefaultRoles = diff --git a/Backend/Endpoints/PublicOverviewEndpoints.cs b/Backend/Endpoints/PublicOverviewEndpoints.cs index a17689e..2abfed2 100644 --- a/Backend/Endpoints/PublicOverviewEndpoints.cs +++ b/Backend/Endpoints/PublicOverviewEndpoints.cs @@ -29,21 +29,26 @@ public static partial class PublicEndpoints var today = DateOnly.FromDateTime(DateTime.UtcNow); var showactApplicationsOpenNow = ShowactApplicationSchedule.IsOpenNow(siteSettings, today); - var canExposeCurrentSeasonWinners = CanExposeCurrentSeasonWinners(season.CurrentPhase); + var latestPublishedWinnerYear = await db.Results + .AsNoTracking() + .Where(result => result.Season.WinnersPublishedAt != null) + .Select(result => (int?)result.Season.Year) + .MaxAsync(); var winnerPreviewRows = await db.Results .AsNoTracking() .Include(result => result.Season) .Include(result => result.Candidate) - .Where(result => - result.Season.Year < season.Year - || canExposeCurrentSeasonWinners && result.Season.Year == season.Year) + .Where(result => result.Season.WinnersPublishedAt != null + && latestPublishedWinnerYear != null + && result.Season.Year == latestPublishedWinnerYear.Value) .OrderByDescending(result => result.Season.Year) .ThenBy(result => result.CategoryName) .Take(8) .Select(result => new { Year = result.Season.Year, + CategoryGroup = result.Category.GroupName, result.CategoryName, WinnerName = result.Candidate.DisplayName, WinnerSlug = result.Candidate.ChannelSlug, @@ -58,6 +63,7 @@ public static partial class PublicEndpoints var winnerPreviewItems = winnerPreviewRows .Select(result => new WinnerPreviewDto( result.Year, + result.CategoryGroup, result.CategoryName, result.WinnerName, result.WinnerSlug, @@ -71,9 +77,9 @@ public static partial class PublicEndpoints var archiveYearRows = await db.Results .AsNoTracking() - .Where(result => - result.Season.Year < season.Year - || canExposeCurrentSeasonWinners && result.Season.Year == season.Year) + .Where(result => result.Season.WinnersPublishedAt != null + && latestPublishedWinnerYear != null + && result.Season.Year < latestPublishedWinnerYear.Value) .GroupBy(result => result.Season.Year) .Select(group => new { @@ -100,6 +106,10 @@ public static partial class PublicEndpoints .ThenBy(category => category.Name, StringComparer.OrdinalIgnoreCase) .ToArray(); var first = ordered[0]; + var groupDescription = ordered + .Select(category => category.Description?.Trim()) + .FirstOrDefault(description => !string.IsNullOrWhiteSpace(description)) + ?? string.Empty; var maxNomineesPerUser = ordered .Select(category => category.MaxNomineesPerUser) .Where(value => value > 0) @@ -110,7 +120,7 @@ public static partial class PublicEndpoints first.Id, first.GroupName, first.GroupName, - first.Description, + groupDescription, maxNomineesPerUser); }) .OrderBy(category => publicCategories @@ -124,7 +134,6 @@ public static partial class PublicEndpoints season.Name, season.ShowDate, season.ShowStartsAt, - SeasonMappings.NormalizeSeasonStreamUrl(season.ShowStreamUrl), season.CurrentPhase, season.IsCommunityOnly, "Twitch", @@ -146,6 +155,23 @@ public static partial class PublicEndpoints siteSettings.ShareDiscordUrl, siteSettings.PrivacyEmail, siteSettings.PrivacyPolicyContent, + SeasonMappings.NormalizePlainTextContent(siteSettings.AwardsSectionTitle), + SeasonMappings.NormalizePlainTextContent(siteSettings.AwardsSectionDescription), + SeasonMappings.NormalizePlainTextContent(siteSettings.SubcategoriesSectionTitle), + SeasonMappings.NormalizePlainTextContent(siteSettings.SubcategoriesSectionDescription), + new PublicStreamBannerContentDto( + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerEyebrow), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerTitle), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerText), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerLiveButtonLabel), + SeasonMappings.NormalizeSeasonStreamUrl(siteSettings.StreamBannerLiveButtonUrl), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerLockedButtonLabel), + siteSettings.StreamBannerUseCompletedContent, + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerCompletedEyebrow), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerCompletedTitle), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerCompletedText), + SeasonMappings.NormalizePlainTextContent(siteSettings.StreamBannerCompletedButtonLabel), + SeasonMappings.NormalizeSeasonStreamUrl(siteSettings.StreamBannerCompletedButtonUrl)), SeasonMappings.ReadSocialLinks(siteSettings), SeasonMappings.BuildFooterLinks(siteSettings)), new PublicFeatureFlagsDto( diff --git a/Backend/Endpoints/PublicSeasonCategoryReadEndpoints.cs b/Backend/Endpoints/PublicSeasonCategoryReadEndpoints.cs index 9fe173d..ae2ae34 100644 --- a/Backend/Endpoints/PublicSeasonCategoryReadEndpoints.cs +++ b/Backend/Endpoints/PublicSeasonCategoryReadEndpoints.cs @@ -57,8 +57,6 @@ public static partial class PublicEndpoints category.GroupName, category.Description, category.MaxNomineesPerUser, - category.ViewerRangeMin, - category.ViewerRangeMax, category.Candidates .Where(candidate => !string.Equals(candidate.AcceptanceStatus, "declined", StringComparison.OrdinalIgnoreCase)) .Select(candidate => diff --git a/Backend/Endpoints/PublicWinnerArchiveEndpoints.cs b/Backend/Endpoints/PublicWinnerArchiveEndpoints.cs index a488083..3664048 100644 --- a/Backend/Endpoints/PublicWinnerArchiveEndpoints.cs +++ b/Backend/Endpoints/PublicWinnerArchiveEndpoints.cs @@ -12,14 +12,24 @@ public static partial class PublicEndpoints var season = await db.Seasons .AsNoTracking() .Where(item => item.Year == year) - .Select(item => new { item.Id, item.Year, item.IsCurrent, item.CurrentPhase }) + .Select(item => new { item.Id, item.Year, item.WinnersPublishedAt }) .FirstOrDefaultAsync(); if (season is null) { return Results.NotFound(); } - if (season.IsCurrent && !CanExposeCurrentSeasonWinners(season.CurrentPhase)) + if (season.WinnersPublishedAt is null) + { + return Results.Ok(new WinnerArchiveResponse(year, [])); + } + + var latestPublishedWinnerYear = await db.Results + .AsNoTracking() + .Where(result => result.Season.WinnersPublishedAt != null) + .Select(result => (int?)result.Season.Year) + .MaxAsync(); + if (latestPublishedWinnerYear == season.Year) { return Results.Ok(new WinnerArchiveResponse(year, [])); } @@ -31,6 +41,7 @@ public static partial class PublicEndpoints .OrderBy(result => result.CategoryName) .Select(result => new { + CategoryGroup = result.Category.GroupName, result.CategoryName, WinnerName = result.Candidate.DisplayName, WinnerSlug = result.Candidate.ChannelSlug, @@ -44,6 +55,7 @@ public static partial class PublicEndpoints var items = winnerRows .Select(result => new WinnerArchiveItemDto( + result.CategoryGroup, result.CategoryName, result.WinnerName, result.WinnerSlug, @@ -57,10 +69,4 @@ public static partial class PublicEndpoints return Results.Ok(new WinnerArchiveResponse(year, items)); } - - private static bool CanExposeCurrentSeasonWinners(string currentPhase) - { - var phaseKey = SeasonMappings.NormalizePhaseKey(currentPhase); - return phaseKey is "completed"; - } } diff --git a/Backend/Extensions/WebApplicationExtensions.cs b/Backend/Extensions/WebApplicationExtensions.cs index acccb45..805ef3e 100644 --- a/Backend/Extensions/WebApplicationExtensions.cs +++ b/Backend/Extensions/WebApplicationExtensions.cs @@ -47,17 +47,11 @@ public static class WebApplicationExtensions await db.Database.MigrateAsync(); } - await SessionBootstrapper.EnsureAsync(db); - await OperationalTablesBootstrapper.EnsureAsync(db); await TeamAccountBootstrapper.EnsureAsync(db, app.Configuration); - if (ShouldSeedPresentationData(app)) - { - await SeedDataBootstrapper.EnsureAsync(db); - } } catch (Exception error) { - logger.LogError(error, "Database initialization failed. Check the PostgreSQL connection, migrations, and seed data."); + logger.LogError(error, "Database initialization failed. Check the PostgreSQL connection, migrations, and startup configuration."); throw; } } @@ -69,17 +63,4 @@ public static class WebApplicationExtensions app.MapPublicEndpoints(); app.MapAdminEndpoints(); } - - private static bool ShouldSeedPresentationData(WebApplication app) - { - var mode = app.Configuration["VTSA_SEED_MODE"] - ?? app.Configuration["SeedData:Mode"]; - - if (string.IsNullOrWhiteSpace(mode)) - { - return app.Environment.IsDevelopment(); - } - - return mode.Trim().ToLowerInvariant() is "demo" or "presentation" or "sample"; - } } diff --git a/Backend/Migrations/20260617060000_InitialCreate.Designer.cs b/Backend/Migrations/20260617060000_InitialCreate.Designer.cs deleted file mode 100644 index 64c99bd..0000000 --- a/Backend/Migrations/20260617060000_InitialCreate.Designer.cs +++ /dev/null @@ -1,833 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260617060000_InitialCreate")] - partial class InitialCreate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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, - SubmittedByTwitchId = "twitch_hoshi" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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, - SubmittedByTwitchId = "twitch_kurainu" - }); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Results") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260617060000_InitialCreate.cs b/Backend/Migrations/20260617060000_InitialCreate.cs deleted file mode 100644 index d210e77..0000000 --- a/Backend/Migrations/20260617060000_InitialCreate.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Backend.Migrations -{ - /// - public partial class InitialCreate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Seasons", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Year = table.Column(type: "integer", nullable: false), - Name = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - IsCurrent = table.Column(type: "boolean", nullable: false), - IsCommunityOnly = table.Column(type: "boolean", nullable: false), - CurrentPhase = table.Column(type: "character varying(60)", maxLength: 60, nullable: false), - NominationStartsAt = table.Column(type: "date", nullable: false), - NominationEndsAt = table.Column(type: "date", nullable: false), - VotingStartsAt = table.Column(type: "date", nullable: false), - VotingEndsAt = table.Column(type: "date", nullable: false), - ReviewStartsAt = table.Column(type: "date", nullable: false), - ReviewEndsAt = table.Column(type: "date", nullable: false), - ShowDate = table.Column(type: "date", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Seasons", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Categories", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SeasonId = table.Column(type: "integer", nullable: false), - GroupName = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), - Name = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - Slug = table.Column(type: "text", nullable: false), - Description = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), - SortOrder = table.Column(type: "integer", nullable: false), - MaxNomineesPerUser = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Categories", x => x.Id); - table.ForeignKey( - name: "FK_Categories_Seasons_SeasonId", - column: x => x.SeasonId, - principalTable: "Seasons", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "VoteBallots", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SeasonId = table.Column(type: "integer", nullable: false), - SubmittedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - Status = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), - SubmittedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_VoteBallots", x => x.Id); - table.ForeignKey( - name: "FK_VoteBallots_Seasons_SeasonId", - column: x => x.SeasonId, - principalTable: "Seasons", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Candidates", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SeasonId = table.Column(type: "integer", nullable: false), - CategoryId = table.Column(type: "integer", nullable: false), - DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - ChannelSlug = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - Platform = table.Column(type: "character varying(40)", maxLength: 40, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Candidates", x => x.Id); - table.ForeignKey( - name: "FK_Candidates_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Candidates_Seasons_SeasonId", - column: x => x.SeasonId, - principalTable: "Seasons", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Nominations", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SeasonId = table.Column(type: "integer", nullable: false), - CategoryId = table.Column(type: "integer", nullable: false), - SubmittedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - CandidateId = table.Column(type: "integer", nullable: true), - CandidateText = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Nominations", x => x.Id); - table.ForeignKey( - name: "FK_Nominations_Candidates_CandidateId", - column: x => x.CandidateId, - principalTable: "Candidates", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_Nominations_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Nominations_Seasons_SeasonId", - column: x => x.SeasonId, - principalTable: "Seasons", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Results", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - SeasonId = table.Column(type: "integer", nullable: false), - CandidateId = table.Column(type: "integer", nullable: false), - CategoryName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Results", x => x.Id); - table.ForeignKey( - name: "FK_Results_Candidates_CandidateId", - column: x => x.CandidateId, - principalTable: "Candidates", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Results_Seasons_SeasonId", - column: x => x.SeasonId, - principalTable: "Seasons", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "VoteEntries", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - BallotId = table.Column(type: "integer", nullable: false), - CategoryId = table.Column(type: "integer", nullable: false), - CandidateId = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_VoteEntries", x => x.Id); - table.ForeignKey( - name: "FK_VoteEntries_Candidates_CandidateId", - column: x => x.CandidateId, - principalTable: "Candidates", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_VoteEntries_Categories_CategoryId", - column: x => x.CategoryId, - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_VoteEntries_VoteBallots_BallotId", - column: x => x.BallotId, - principalTable: "VoteBallots", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "Seasons", - columns: new[] { "Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year" }, - values: new object[,] - { - { 1, "Community Voting", true, true, "VTuber Star Awards 2026", new DateOnly(2026, 5, 31), new DateOnly(2026, 5, 1), new DateOnly(2026, 7, 10), new DateOnly(2026, 7, 1), new DateOnly(2026, 7, 20), new DateOnly(2026, 6, 30), new DateOnly(2026, 6, 1), 2026 }, - { 2, "Archived", true, false, "VTuber Star Awards 2025", new DateOnly(2025, 5, 31), new DateOnly(2025, 5, 1), new DateOnly(2025, 7, 10), new DateOnly(2025, 7, 1), new DateOnly(2025, 7, 20), new DateOnly(2025, 6, 30), new DateOnly(2025, 6, 1), 2025 }, - { 3, "Archived", true, false, "VTuber Star Awards 2024", new DateOnly(2024, 5, 31), new DateOnly(2024, 5, 1), new DateOnly(2024, 7, 10), new DateOnly(2024, 7, 1), new DateOnly(2024, 7, 20), new DateOnly(2024, 6, 30), new DateOnly(2024, 6, 1), 2024 }, - { 4, "Archived", true, false, "VTuber Star Awards 2023", new DateOnly(2023, 5, 31), new DateOnly(2023, 5, 1), new DateOnly(2023, 7, 10), new DateOnly(2023, 7, 1), new DateOnly(2023, 7, 20), new DateOnly(2023, 6, 30), new DateOnly(2023, 6, 1), 2023 } - }); - - migrationBuilder.InsertData( - table: "Categories", - columns: new[] { "Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder" }, - values: new object[,] - { - { 1, "Die groesste Auszeichnung des Jahres.", "Main Awards", 3, "VTuber des Jahres", 1, "vtuber-des-jahres", 1 }, - { 2, "Events, Konzerte und 3D-Shows.", "Performance", 3, "Bestes Live Event", 1, "bestes-live-event", 2 }, - { 3, "Der lustigste oder emotionalste Clip des Jahres.", "Clips & Highlights", 3, "Clip des Jahres", 1, "clip-des-jahres", 3 }, - { 4, "Die aktivste und freundlichste Community.", "Main Awards", 3, "Beste Community", 1, "beste-community", 4 }, - { 5, "Archivkategorie 2025.", "Main Awards", 3, "VTuber des Jahres", 2, "vtuber-des-jahres", 1 }, - { 6, "Archivkategorie 2025.", "Performance", 3, "Bestes Live Event", 2, "bestes-live-event", 2 }, - { 7, "Archivkategorie 2025.", "Clips & Highlights", 3, "Clip des Jahres", 2, "clip-des-jahres", 3 }, - { 8, "Archivkategorie 2024.", "Main Awards", 3, "VTuber des Jahres", 3, "vtuber-des-jahres", 1 }, - { 9, "Archivkategorie 2024.", "Clips & Highlights", 3, "Clip des Jahres", 3, "clip-des-jahres", 2 }, - { 10, "Archivkategorie 2023.", "Main Awards", 3, "VTuber des Jahres", 4, "vtuber-des-jahres", 1 } - }); - - migrationBuilder.InsertData( - table: "VoteBallots", - columns: new[] { "Id", "SeasonId", "Status", "SubmittedAt", "SubmittedByTwitchId" }, - values: new object[,] - { - { 1, 1, "submitted", new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "twitch_vote_1" }, - { 2, 1, "submitted", new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), "twitch_vote_2" } - }); - - migrationBuilder.InsertData( - table: "Candidates", - columns: new[] { "Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId" }, - values: new object[,] - { - { 1, 1, "@hoshimimiyu", "Hoshimi Miyu", "Twitch", 1 }, - { 2, 1, "@kurainu", "Kurainu", "Twitch", 1 }, - { 3, 1, "@shiroch", "Shiro Ch.", "Twitch", 1 }, - { 4, 2, "@kurainu", "Kurainu 3D Live", "Twitch", 1 }, - { 5, 2, "@aoisakura", "Aoi Sakura Showcase", "YouTube", 1 }, - { 6, 3, "@pyonkichikingdom", "Pyonkichi Kingdom", "Twitch", 1 }, - { 7, 4, "@moonrelay", "Moonrelay", "Twitch", 1 }, - { 8, 5, "@hoshimimiyu", "Hoshimi Miyu", "Twitch", 2 }, - { 9, 6, "@kurainu", "Kurainu 3D Live", "Twitch", 2 }, - { 10, 7, "@pyonkichikingdom", "Pyonkichi Kingdom", "Twitch", 2 }, - { 11, 8, "@aoisakura", "Aoi Sakura", "YouTube", 3 }, - { 12, 9, "@starbyte", "Starbyte", "Twitch", 3 }, - { 13, 10, "@tenshivox", "Tenshi Vox", "Twitch", 4 } - }); - - migrationBuilder.InsertData( - table: "Nominations", - columns: new[] { "Id", "CandidateId", "CandidateText", "CategoryId", "CreatedAt", "SeasonId", "SubmittedByTwitchId" }, - values: new object[,] - { - { 1, null, "Hoshimi Miyu", 1, new DateTimeOffset(new DateTime(2026, 6, 10, 13, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 1, "twitch_hoshi" }, - { 2, null, "Kurainu 3D Live", 2, new DateTimeOffset(new DateTime(2026, 6, 10, 14, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), 1, "twitch_kurainu" } - }); - - migrationBuilder.InsertData( - table: "Results", - columns: new[] { "Id", "CandidateId", "CategoryName", "SeasonId" }, - values: new object[,] - { - { 1, 8, "VTuber des Jahres", 2 }, - { 2, 9, "Bestes Live Event", 2 }, - { 3, 10, "Clip des Jahres", 2 }, - { 4, 11, "VTuber des Jahres", 3 }, - { 5, 12, "Clip des Jahres", 3 }, - { 6, 13, "VTuber des Jahres", 4 } - }); - - migrationBuilder.InsertData( - table: "VoteEntries", - columns: new[] { "Id", "BallotId", "CandidateId", "CategoryId" }, - values: new object[,] - { - { 1, 1, 1, 1 }, - { 2, 1, 4, 2 }, - { 3, 2, 2, 1 }, - { 4, 2, 6, 3 } - }); - - migrationBuilder.CreateIndex( - name: "IX_Candidates_CategoryId", - table: "Candidates", - column: "CategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_Candidates_SeasonId", - table: "Candidates", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_Categories_SeasonId_Slug", - table: "Categories", - columns: new[] { "SeasonId", "Slug" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_CandidateId", - table: "Nominations", - column: "CandidateId"); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_CategoryId", - table: "Nominations", - column: "CategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_SeasonId", - table: "Nominations", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_Results_CandidateId", - table: "Results", - column: "CandidateId"); - - migrationBuilder.CreateIndex( - name: "IX_Results_SeasonId", - table: "Results", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_Seasons_Year", - table: "Seasons", - column: "Year", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_VoteBallots_SeasonId", - table: "VoteBallots", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_VoteEntries_BallotId", - table: "VoteEntries", - column: "BallotId"); - - migrationBuilder.CreateIndex( - name: "IX_VoteEntries_CandidateId", - table: "VoteEntries", - column: "CandidateId"); - - migrationBuilder.CreateIndex( - name: "IX_VoteEntries_CategoryId", - table: "VoteEntries", - column: "CategoryId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Nominations"); - - migrationBuilder.DropTable( - name: "Results"); - - migrationBuilder.DropTable( - name: "VoteEntries"); - - migrationBuilder.DropTable( - name: "Candidates"); - - migrationBuilder.DropTable( - name: "VoteBallots"); - - migrationBuilder.DropTable( - name: "Categories"); - - migrationBuilder.DropTable( - name: "Seasons"); - } - } -} diff --git a/Backend/Migrations/20260623112528_AddClipReviewWorkflow.Designer.cs b/Backend/Migrations/20260623112528_AddClipReviewWorkflow.Designer.cs deleted file mode 100644 index 7c4bf25..0000000 --- a/Backend/Migrations/20260623112528_AddClipReviewWorkflow.Designer.cs +++ /dev/null @@ -1,1081 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623112528_AddClipReviewWorkflow")] - partial class AddClipReviewWorkflow - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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, - SubmittedByTwitchId = "twitch_hoshi" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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, - SubmittedByTwitchId = "twitch_kurainu" - }); - }); - - modelBuilder.Entity("Backend.Domain.RiskFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Results") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623112528_AddClipReviewWorkflow.cs b/Backend/Migrations/20260623112528_AddClipReviewWorkflow.cs deleted file mode 100644 index 67917b0..0000000 --- a/Backend/Migrations/20260623112528_AddClipReviewWorkflow.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddClipReviewWorkflow : Migration - { - /// - 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; - """); - } - - /// - 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"; - """); - } - } -} diff --git a/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.Designer.cs b/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.Designer.cs deleted file mode 100644 index 5cd66df..0000000 --- a/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.Designer.cs +++ /dev/null @@ -1,1099 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623113917_AddNominationReviewWorkflow")] - partial class AddNominationReviewWorkflow - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Results") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.cs b/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.cs deleted file mode 100644 index 662309e..0000000 --- a/Backend/Migrations/20260623113917_AddNominationReviewWorkflow.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddNominationReviewWorkflow : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Nominations_SeasonId", - table: "Nominations"); - - migrationBuilder.AddColumn( - name: "ReviewNote", - table: "Nominations", - type: "character varying(500)", - maxLength: 500, - nullable: true); - - migrationBuilder.AddColumn( - name: "ReviewedAt", - table: "Nominations", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "ReviewedByTwitchId", - table: "Nominations", - type: "character varying(120)", - maxLength: 120, - nullable: true); - - migrationBuilder.AddColumn( - 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" }); - } - - /// - 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"); - } - } -} diff --git a/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.Designer.cs b/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.Designer.cs deleted file mode 100644 index 79467f3..0000000 --- a/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.Designer.cs +++ /dev/null @@ -1,1119 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623131228_AddAwardResultCategoryLock")] - partial class AddAwardResultCategoryLock - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.cs b/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.cs deleted file mode 100644 index 07f82dd..0000000 --- a/Backend/Migrations/20260623131228_AddAwardResultCategoryLock.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddAwardResultCategoryLock : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_Results_SeasonId", - table: "Results"); - - migrationBuilder.AddColumn( - 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( - 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); - } - - /// - 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"); - } - } -} diff --git a/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.Designer.cs b/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.Designer.cs deleted file mode 100644 index e8fa06c..0000000 --- a/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.Designer.cs +++ /dev/null @@ -1,1128 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623152322_AddSeasonShowStreamUrl")] - partial class AddSeasonShowStreamUrl - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - ShowStreamUrl = "https://youtube.com/c/Jayuhime", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.cs b/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.cs deleted file mode 100644 index 06fef06..0000000 --- a/Backend/Migrations/20260623152322_AddSeasonShowStreamUrl.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSeasonShowStreamUrl : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - 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"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ShowStreamUrl", - table: "Seasons"); - } - } -} diff --git a/Backend/Migrations/20260623153212_AddSiteSettings.Designer.cs b/Backend/Migrations/20260623153212_AddSiteSettings.Designer.cs deleted file mode 100644 index ff76a51..0000000 --- a/Backend/Migrations/20260623153212_AddSiteSettings.Designer.cs +++ /dev/null @@ -1,1199 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623153212_AddSiteSettings")] - partial class AddSiteSettings - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - ShowStreamUrl = "https://youtube.com/c/Jayuhime", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("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", - FaqJson = "[{\"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.\"}]", - HostDisplayName = "Jayuhime", - HostTagline = "VTuber & Award Host", - ImprintUrl = "https://vtuber-star-awards.de/impressum", - NewsletterUrl = "https://vtuber-star-awards.de/newsletter", - PrivacyEmail = "datenschutz@vtuber-star-awards.de", - SocialLinksJson = "[{\"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\"}]", - SponsorsUrl = "https://vtuber-star-awards.de/partner" - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623153212_AddSiteSettings.cs b/Backend/Migrations/20260623153212_AddSiteSettings.cs deleted file mode 100644 index 2840759..0000000 --- a/Backend/Migrations/20260623153212_AddSiteSettings.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSiteSettings : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "SiteSettings", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - HostDisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - HostTagline = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - NewsletterUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), - PrivacyEmail = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - ImprintUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), - ContactUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), - SponsorsUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), - SocialLinksJson = table.Column(type: "text", nullable: false), - FaqJson = table.Column(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" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.Designer.cs b/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.Designer.cs deleted file mode 100644 index cd5b793..0000000 --- a/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.Designer.cs +++ /dev/null @@ -1,1213 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260623154438_AddPrivacyPolicyContentMetadata")] - partial class AddPrivacyPolicyContentMetadata - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - ShowStreamUrl = "https://youtube.com/c/Jayuhime", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("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", - FaqJson = "[{\"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.\"}]", - HostDisplayName = "Jayuhime", - HostTagline = "VTuber & Award Host", - ImprintUrl = "https://vtuber-star-awards.de/impressum", - 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", - SocialLinksJson = "[{\"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\"}]", - SponsorsUrl = "https://vtuber-star-awards.de/partner" - }); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.cs b/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.cs deleted file mode 100644 index de3d9e0..0000000 --- a/Backend/Migrations/20260623154438_AddPrivacyPolicyContentMetadata.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddPrivacyPolicyContentMetadata : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "PrivacyPolicyContent", - table: "SiteSettings", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "PrivacyPolicyUpdatedAt", - table: "SiteSettings", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - 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" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "PrivacyPolicyContent", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "PrivacyPolicyUpdatedAt", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "PrivacyPolicyUpdatedBy", - table: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260624065545_AddOperationalSiteSettings.Designer.cs b/Backend/Migrations/20260624065545_AddOperationalSiteSettings.Designer.cs deleted file mode 100644 index b5de68c..0000000 --- a/Backend/Migrations/20260624065545_AddOperationalSiteSettings.Designer.cs +++ /dev/null @@ -1,1267 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260624065545_AddOperationalSiteSettings")] - partial class AddOperationalSiteSettings - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - ReviewEndsAt = new DateOnly(2026, 7, 10), - ReviewStartsAt = new DateOnly(2026, 7, 1), - ShowDate = new DateOnly(2026, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - ReviewEndsAt = new DateOnly(2025, 7, 10), - ReviewStartsAt = new DateOnly(2025, 7, 1), - ShowDate = new DateOnly(2025, 7, 20), - ShowStreamUrl = "https://twitch.tv/jayuhime", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - ReviewEndsAt = new DateOnly(2024, 7, 10), - ReviewStartsAt = new DateOnly(2024, 7, 1), - ShowDate = new DateOnly(2024, 7, 20), - ShowStreamUrl = "https://youtube.com/c/Jayuhime", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - ReviewEndsAt = new DateOnly(2023, 7, 10), - ReviewStartsAt = new DateOnly(2023, 7, 1), - ShowDate = new DateOnly(2023, 7, 20), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("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", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260624065545_AddOperationalSiteSettings.cs b/Backend/Migrations/20260624065545_AddOperationalSiteSettings.cs deleted file mode 100644 index c6b1e0b..0000000 --- a/Backend/Migrations/20260624065545_AddOperationalSiteSettings.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddOperationalSiteSettings : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "DemoLoginDisplayName", - table: "SiteSettings", - type: "character varying(120)", - maxLength: 120, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "DemoLoginEmail", - table: "SiteSettings", - type: "character varying(180)", - maxLength: 180, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "DemoLoginEnabled", - table: "SiteSettings", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "DemoLoginManagedByDatabase", - table: "SiteSettings", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "DemoLoginPasswordHash", - table: "SiteSettings", - type: "character varying(120)", - maxLength: 120, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "DemoLoginPasswordSalt", - table: "SiteSettings", - type: "character varying(80)", - maxLength: 80, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "DemoLoginTwitchUserId", - table: "SiteSettings", - type: "character varying(120)", - maxLength: 120, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "MaintenanceMessage", - table: "SiteSettings", - type: "character varying(600)", - maxLength: 600, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "MaintenanceModeEnabled", - table: "SiteSettings", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - 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" }); - } - - /// - 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"); - - } - } -} diff --git a/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.Designer.cs b/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.Designer.cs deleted file mode 100644 index 43b2ad4..0000000 --- a/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.Designer.cs +++ /dev/null @@ -1,1274 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260624133211_AddSeasonShowStartsAt")] - partial class AddSeasonShowStartsAt - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ClipSubmissions"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("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", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Nomination", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.cs b/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.cs deleted file mode 100644 index d9e45c3..0000000 --- a/Backend/Migrations/20260624133211_AddSeasonShowStartsAt.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSeasonShowStartsAt : Migration - { - /// - 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)); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ShowStartsAt", - table: "Seasons"); - } - } -} diff --git a/Backend/Migrations/20260624134432_AddClipCandidateLink.Designer.cs b/Backend/Migrations/20260624134432_AddClipCandidateLink.Designer.cs deleted file mode 100644 index 0e53fcb..0000000 --- a/Backend/Migrations/20260624134432_AddClipCandidateLink.Designer.cs +++ /dev/null @@ -1,1289 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260624134432_AddClipCandidateLink")] - partial class AddClipCandidateLink - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("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", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260624134432_AddClipCandidateLink.cs b/Backend/Migrations/20260624134432_AddClipCandidateLink.cs deleted file mode 100644 index 3e4aac3..0000000 --- a/Backend/Migrations/20260624134432_AddClipCandidateLink.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddClipCandidateLink : Migration - { - /// - 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 $$; - """); - } - - /// - 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"; - """); - } - } -} diff --git a/Backend/Migrations/20260624150500_AddRiskFlagReviewNote.cs b/Backend/Migrations/20260624150500_AddRiskFlagReviewNote.cs deleted file mode 100644 index 1ad1bfe..0000000 --- a/Backend/Migrations/20260624150500_AddRiskFlagReviewNote.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Backend.Data; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - [DbContext(typeof(AwardsDbContext))] - [Migration("20260624150500_AddRiskFlagReviewNote")] - public partial class AddRiskFlagReviewNote : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( - """ - ALTER TABLE IF EXISTS "RiskFlags" - ADD COLUMN IF NOT EXISTS "ReviewNote" character varying(500) NULL; - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( - """ - ALTER TABLE IF EXISTS "RiskFlags" - DROP COLUMN IF EXISTS "ReviewNote"; - """); - } - } -} diff --git a/Backend/Migrations/20260624162000_AddRiskRulesJson.cs b/Backend/Migrations/20260624162000_AddRiskRulesJson.cs deleted file mode 100644 index 275c859..0000000 --- a/Backend/Migrations/20260624162000_AddRiskRulesJson.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Backend.Data; -using Backend.Services; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - [DbContext(typeof(AwardsDbContext))] - [Migration("20260624162000_AddRiskRulesJson")] - public partial class AddRiskRulesJson : Migration - { - /// - 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" = '[]'; - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( - """ - ALTER TABLE IF EXISTS "SiteSettings" - DROP COLUMN IF EXISTS "RiskRulesJson"; - """); - } - } -} diff --git a/Backend/Migrations/20260625110000_AddFooterPageContent.cs b/Backend/Migrations/20260625110000_AddFooterPageContent.cs deleted file mode 100644 index edb44cc..0000000 --- a/Backend/Migrations/20260625110000_AddFooterPageContent.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Backend.Data; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - [DbContext(typeof(AwardsDbContext))] - [Migration("20260625110000_AddFooterPageContent")] - public partial class AddFooterPageContent : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - var imprintContent = SeedCatalog.DefaultImprintContent.Replace("'", "''"); - var contactContent = SeedCatalog.DefaultContactContent.Replace("'", "''"); - var sponsorsContent = SeedCatalog.DefaultSponsorsContent.Replace("'", "''"); - - migrationBuilder.Sql( - $""" - ALTER TABLE IF EXISTS "SiteSettings" - ADD COLUMN IF NOT EXISTS "ImprintContent" text NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS "ContactContent" text NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS "SponsorsContent" text NOT NULL DEFAULT ''; - - UPDATE "SiteSettings" - SET "ImprintContent" = '{imprintContent}' - WHERE "ImprintContent" IS NULL OR btrim("ImprintContent") = ''; - - UPDATE "SiteSettings" - SET "ContactContent" = '{contactContent}' - WHERE "ContactContent" IS NULL OR btrim("ContactContent") = ''; - - UPDATE "SiteSettings" - SET "SponsorsContent" = '{sponsorsContent}' - WHERE "SponsorsContent" IS NULL OR btrim("SponsorsContent") = ''; - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql( - """ - ALTER TABLE IF EXISTS "SiteSettings" - DROP COLUMN IF EXISTS "ImprintContent", - DROP COLUMN IF EXISTS "ContactContent", - DROP COLUMN IF EXISTS "SponsorsContent"; - """); - } - } -} diff --git a/Backend/Migrations/20260625140614_AddTeamManagement.Designer.cs b/Backend/Migrations/20260625140614_AddTeamManagement.Designer.cs deleted file mode 100644 index 9fe9826..0000000 --- a/Backend/Migrations/20260625140614_AddTeamManagement.Designer.cs +++ /dev/null @@ -1,1428 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260625140614_AddTeamManagement")] - partial class AddTeamManagement - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId"); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260625140614_AddTeamManagement.cs b/Backend/Migrations/20260625140614_AddTeamManagement.cs deleted file mode 100644 index 64d85d3..0000000 --- a/Backend/Migrations/20260625140614_AddTeamManagement.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddTeamManagement : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TeamMembers", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Login = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), - DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - Role = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), - PasswordHash = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - PasswordSalt = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), - MustChangePassword = table.Column(type: "boolean", nullable: false), - IsActive = table.Column(type: "boolean", nullable: false), - CreatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - UpdatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), - LastLoginAt = table.Column(type: "timestamp with time zone", nullable: true), - PasswordResetAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TeamMembers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "TeamRolePermissions", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Role = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), - PermissionsJson = table.Column(type: "text", nullable: false), - UpdatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TeamRolePermissions", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_TeamMembers_Login", - table: "TeamMembers", - column: "Login", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TeamRolePermissions_Role", - table: "TeamRolePermissions", - column: "Role", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TeamMembers"); - - migrationBuilder.DropTable( - name: "TeamRolePermissions"); - } - } -} diff --git a/Backend/Migrations/20260625153000_AddCreatorRoleAndTeamTwitchBinding.cs b/Backend/Migrations/20260625153000_AddCreatorRoleAndTeamTwitchBinding.cs deleted file mode 100644 index 4ce10c1..0000000 --- a/Backend/Migrations/20260625153000_AddCreatorRoleAndTeamTwitchBinding.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - [DbContext(typeof(AwardsDbContext))] - [Migration("20260625153000_AddCreatorRoleAndTeamTwitchBinding")] - public partial class AddCreatorRoleAndTeamTwitchBinding : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "BoundTwitchDisplayName", - table: "TeamMembers", - type: "character varying(120)", - maxLength: 120, - nullable: true); - - migrationBuilder.AddColumn( - name: "BoundTwitchUserId", - table: "TeamMembers", - type: "character varying(120)", - maxLength: 120, - nullable: true); - - migrationBuilder.AddColumn( - name: "TwitchBoundAt", - table: "TeamMembers", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_TeamMembers_BoundTwitchUserId", - table: "TeamMembers", - column: "BoundTwitchUserId", - unique: true, - filter: "\"BoundTwitchUserId\" IS NOT NULL"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_TeamMembers_BoundTwitchUserId", - table: "TeamMembers"); - - migrationBuilder.DropColumn( - name: "BoundTwitchDisplayName", - table: "TeamMembers"); - - migrationBuilder.DropColumn( - name: "BoundTwitchUserId", - table: "TeamMembers"); - - migrationBuilder.DropColumn( - name: "TwitchBoundAt", - table: "TeamMembers"); - } - } -} diff --git a/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.Designer.cs b/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.Designer.cs deleted file mode 100644 index 385541c..0000000 --- a/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.Designer.cs +++ /dev/null @@ -1,1471 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260626113057_AddVoteBallotSubmitterUniqueIndex")] - partial class AddVoteBallotSubmitterUniqueIndex - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - CategoryId = 1, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - CategoryId = 1, - ChannelSlug = "@shiroch", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - CategoryId = 2, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - CategoryId = 2, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - CategoryId = 4, - ChannelSlug = "@moonrelay", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - CategoryId = 6, - ChannelSlug = "@kurainu", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - CategoryId = 8, - ChannelSlug = "@aoisakura", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - CategoryId = 9, - ChannelSlug = "@starbyte", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - CategoryId = 10, - ChannelSlug = "@tenshivox", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.cs b/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.cs deleted file mode 100644 index 5af14c2..0000000 --- a/Backend/Migrations/20260626113057_AddVoteBallotSubmitterUniqueIndex.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddVoteBallotSubmitterUniqueIndex : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_VoteBallots_SeasonId", - table: "VoteBallots"); - - migrationBuilder.AddColumn( - name: "TwitchAuthManagedByDatabase", - table: "SiteSettings", - type: "boolean", - nullable: false, - defaultValue: false); - - migrationBuilder.AddColumn( - name: "TwitchClientId", - table: "SiteSettings", - type: "character varying(120)", - maxLength: 120, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "TwitchClientSecret", - table: "SiteSettings", - type: "character varying(180)", - maxLength: 180, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "TwitchRedirectUri", - table: "SiteSettings", - type: "character varying(400)", - maxLength: 400, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "TwitchScope", - table: "SiteSettings", - type: "character varying(300)", - maxLength: 300, - nullable: false, - defaultValue: ""); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "TwitchAuthManagedByDatabase", "TwitchClientId", "TwitchClientSecret", "TwitchRedirectUri", "TwitchScope" }, - values: new object[] { false, "", "", "", "" }); - - migrationBuilder.Sql(""" - DELETE FROM "VoteEntries" - WHERE "BallotId" IN ( - SELECT "Id" - FROM ( - SELECT - "Id", - ROW_NUMBER() OVER ( - PARTITION BY "SeasonId", "SubmittedByTwitchId" - ORDER BY "SubmittedAt" DESC, "Id" DESC - ) AS duplicate_rank - FROM "VoteBallots" - ) ranked_ballots - WHERE duplicate_rank > 1 - ); - - DELETE FROM "VoteBallots" - WHERE "Id" IN ( - SELECT "Id" - FROM ( - SELECT - "Id", - ROW_NUMBER() OVER ( - PARTITION BY "SeasonId", "SubmittedByTwitchId" - ORDER BY "SubmittedAt" DESC, "Id" DESC - ) AS duplicate_rank - FROM "VoteBallots" - ) ranked_ballots - WHERE duplicate_rank > 1 - ); - """); - - migrationBuilder.CreateIndex( - name: "IX_VoteBallots_SeasonId_SubmittedByTwitchId", - table: "VoteBallots", - columns: new[] { "SeasonId", "SubmittedByTwitchId" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_VoteBallots_SeasonId_SubmittedByTwitchId", - table: "VoteBallots"); - - migrationBuilder.DropColumn( - name: "TwitchAuthManagedByDatabase", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "TwitchClientId", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "TwitchClientSecret", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "TwitchRedirectUri", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "TwitchScope", - table: "SiteSettings"); - - migrationBuilder.CreateIndex( - name: "IX_VoteBallots_SeasonId", - table: "VoteBallots", - column: "SeasonId"); - } - } -} diff --git a/Backend/Migrations/20260627114301_AddCandidatePreparationFields.Designer.cs b/Backend/Migrations/20260627114301_AddCandidatePreparationFields.Designer.cs deleted file mode 100644 index 1a2531f..0000000 --- a/Backend/Migrations/20260627114301_AddCandidatePreparationFields.Designer.cs +++ /dev/null @@ -1,1527 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627114301_AddCandidatePreparationFields")] - partial class AddCandidatePreparationFields - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627114301_AddCandidatePreparationFields.cs b/Backend/Migrations/20260627114301_AddCandidatePreparationFields.cs deleted file mode 100644 index 178b2b4..0000000 --- a/Backend/Migrations/20260627114301_AddCandidatePreparationFields.cs +++ /dev/null @@ -1,177 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddCandidatePreparationFields : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "AcceptanceNote", - table: "Candidates", - type: "character varying(500)", - maxLength: 500, - nullable: true); - - migrationBuilder.AddColumn( - name: "AcceptanceStatus", - table: "Candidates", - type: "character varying(30)", - maxLength: 30, - nullable: false, - defaultValue: "open"); - - migrationBuilder.AddColumn( - name: "ClipCompilationPlatform", - table: "Candidates", - type: "character varying(40)", - maxLength: 40, - nullable: true); - - migrationBuilder.AddColumn( - name: "ClipCompilationTitle", - table: "Candidates", - type: "character varying(200)", - maxLength: 200, - nullable: true); - - migrationBuilder.AddColumn( - name: "ClipCompilationUrl", - table: "Candidates", - type: "character varying(500)", - maxLength: 500, - nullable: true); - - migrationBuilder.AddColumn( - name: "ClipEmbedStatus", - table: "Candidates", - type: "character varying(30)", - maxLength: 30, - nullable: false, - defaultValue: "unchecked"); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 2, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 3, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 4, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 5, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 6, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 7, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 8, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 9, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 10, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 11, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 12, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 13, - columns: new[] { "AcceptanceNote", "AcceptanceStatus", "ClipCompilationPlatform", "ClipCompilationTitle", "ClipCompilationUrl", "ClipEmbedStatus" }, - values: new object[] { null, "open", null, null, null, "unchecked" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "AcceptanceNote", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "AcceptanceStatus", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "ClipCompilationPlatform", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "ClipCompilationTitle", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "ClipCompilationUrl", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "ClipEmbedStatus", - table: "Candidates"); - } - } -} diff --git a/Backend/Migrations/20260627125128_AddWorkflowRulesJson.Designer.cs b/Backend/Migrations/20260627125128_AddWorkflowRulesJson.Designer.cs deleted file mode 100644 index f537bb7..0000000 --- a/Backend/Migrations/20260627125128_AddWorkflowRulesJson.Designer.cs +++ /dev/null @@ -1,1534 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627125128_AddWorkflowRulesJson")] - partial class AddWorkflowRulesJson - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627125128_AddWorkflowRulesJson.cs b/Backend/Migrations/20260627125128_AddWorkflowRulesJson.cs deleted file mode 100644 index d638ed6..0000000 --- a/Backend/Migrations/20260627125128_AddWorkflowRulesJson.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddWorkflowRulesJson : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "WorkflowRulesJson" text NOT NULL DEFAULT '[]'; - """); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - column: "WorkflowRulesJson", - value: "[{\"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.\"}]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "WorkflowRulesJson"; - """); - } - } -} diff --git a/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.Designer.cs b/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.Designer.cs deleted file mode 100644 index 0d188de..0000000 --- a/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.Designer.cs +++ /dev/null @@ -1,1552 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627131746_AddOptionalClipFeatureSettings")] - partial class AddOptionalClipFeatureSettings - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.cs b/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.cs deleted file mode 100644 index cfd72a2..0000000 --- a/Backend/Migrations/20260627131746_AddOptionalClipFeatureSettings.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddOptionalClipFeatureSettings : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipReviewEnabled" boolean NOT NULL DEFAULT true; - """); - - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipSubmissionDisabledMessage" character varying(240) NOT NULL DEFAULT 'Clip-Einreichungen sind aktuell geschlossen.'; - """); - - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipSubmissionsEnabled" boolean NOT NULL DEFAULT false; - """); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ClipReviewEnabled", "ClipSubmissionDisabledMessage" }, - values: new object[] { true, "Clip-Einreichungen sind aktuell geschlossen." }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ClipReviewEnabled"; - """); - - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ClipSubmissionDisabledMessage"; - """); - - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ClipSubmissionsEnabled"; - """); - } - } -} diff --git a/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.Designer.cs b/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.Designer.cs deleted file mode 100644 index 49cb260..0000000 --- a/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.Designer.cs +++ /dev/null @@ -1,1559 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627135729_AddNominationLinkBlacklist")] - partial class AddNominationLinkBlacklist - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.cs b/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.cs deleted file mode 100644 index f67889f..0000000 --- a/Backend/Migrations/20260627135729_AddNominationLinkBlacklist.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddNominationLinkBlacklist : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "NominationLinkBlacklistJson", - table: "SiteSettings", - type: "text", - nullable: false, - defaultValue: "[]"); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - column: "NominationLinkBlacklistJson", - value: "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "NominationLinkBlacklistJson", - table: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.Designer.cs b/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.Designer.cs deleted file mode 100644 index b31a5cd..0000000 --- a/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.Designer.cs +++ /dev/null @@ -1,1758 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627163650_AddClipAdminMenuVisible")] - partial class AddClipAdminMenuVisible - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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.ShowactApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ArtistName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactDiscord") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("PerformanceType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("PlatformUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReferenceUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TechnicalNotes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ShowactApplications"); - }); - - modelBuilder.Entity("Backend.Domain.SiteSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipAdminMenuVisible") - .HasColumnType("boolean"); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactApplicationDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ShowactApplicationsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ShowactsContent") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue(""); - - b.Property("ShowactsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SponsorsVisible") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("IsVisible") - .HasColumnType("boolean"); - - b.Property("LogoUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("Tier") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WebsiteUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "IsVisible", "SortOrder"); - - b.ToTable("Sponsors"); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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.ShowactApplication", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.cs b/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.cs deleted file mode 100644 index 892b213..0000000 --- a/Backend/Migrations/20260627163650_AddClipAdminMenuVisible.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddClipAdminMenuVisible : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ClipAdminMenuVisible" boolean NOT NULL DEFAULT FALSE; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationDisabledMessage" character varying(240) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactApplicationsEnabled" boolean NOT NULL DEFAULT FALSE; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactsContent" text NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "ShowactsUrl" character varying(400) NOT NULL DEFAULT ''; - - ALTER TABLE "SiteSettings" - ADD COLUMN IF NOT EXISTS "SponsorsVisible" boolean NOT NULL DEFAULT TRUE; - """); - - 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 - ); - - 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", - keyColumn: "Id", - keyValue: 1, - 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.Sql(""" - CREATE INDEX IF NOT EXISTS "IX_ShowactApplications_SeasonId_Status" - ON "ShowactApplications" ("SeasonId", "Status"); - - CREATE INDEX IF NOT EXISTS "IX_Sponsors_SeasonId_IsVisible_SortOrder" - ON "Sponsors" ("SeasonId", "IsVisible", "SortOrder"); - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - DROP TABLE IF EXISTS "ShowactApplications"; - - DROP TABLE IF EXISTS "Sponsors"; - """); - - migrationBuilder.Sql(""" - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ClipAdminMenuVisible"; - - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ShowactApplicationDisabledMessage"; - - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ShowactApplicationsEnabled"; - - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ShowactsContent"; - - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "ShowactsUrl"; - - ALTER TABLE "SiteSettings" - DROP COLUMN IF EXISTS "SponsorsVisible"; - """); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - column: "WorkflowRulesJson", - value: "[{\"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.\"}]"); - } - } -} diff --git a/Backend/Migrations/20260627171320_AddShareUrls.Designer.cs b/Backend/Migrations/20260627171320_AddShareUrls.Designer.cs deleted file mode 100644 index 59237c0..0000000 --- a/Backend/Migrations/20260627171320_AddShareUrls.Designer.cs +++ /dev/null @@ -1,1768 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627171320_AddShareUrls")] - partial class AddShareUrls - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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.ShowactApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ArtistName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactDiscord") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("PerformanceType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("PlatformUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReferenceUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TechnicalNotes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ShowactApplications"); - }); - - modelBuilder.Entity("Backend.Domain.SiteSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipAdminMenuVisible") - .HasColumnType("boolean"); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareDiscordUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareXUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactApplicationDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ShowactApplicationsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ShowactsContent") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue(""); - - b.Property("ShowactsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SponsorsVisible") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("IsVisible") - .HasColumnType("boolean"); - - b.Property("LogoUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("Tier") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WebsiteUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "IsVisible", "SortOrder"); - - b.ToTable("Sponsors"); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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.ShowactApplication", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627171320_AddShareUrls.cs b/Backend/Migrations/20260627171320_AddShareUrls.cs deleted file mode 100644 index 0402734..0000000 --- a/Backend/Migrations/20260627171320_AddShareUrls.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddShareUrls : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShareDiscordUrl", - table: "SiteSettings", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "ShareXUrl", - table: "SiteSettings", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ShareDiscordUrl", "ShareXUrl" }, - values: new object[] { "", "" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ShareDiscordUrl", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "ShareXUrl", - table: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260627174243_AddShowactDynamicForm.Designer.cs b/Backend/Migrations/20260627174243_AddShowactDynamicForm.Designer.cs deleted file mode 100644 index 9c1cac8..0000000 --- a/Backend/Migrations/20260627174243_AddShowactDynamicForm.Designer.cs +++ /dev/null @@ -1,1777 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260627174243_AddShowactDynamicForm")] - partial class AddShowactDynamicForm - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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.ShowactApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ArtistName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactDiscord") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("FieldResponsesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("PerformanceType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("PlatformUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReferenceUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TechnicalNotes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ShowactApplications"); - }); - - modelBuilder.Entity("Backend.Domain.SiteSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipAdminMenuVisible") - .HasColumnType("boolean"); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareDiscordUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareXUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactApplicationDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ShowactApplicationsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ShowactFormSchemaJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactsContent") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue(""); - - b.Property("ShowactsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SponsorsVisible") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("IsVisible") - .HasColumnType("boolean"); - - b.Property("LogoUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("Tier") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WebsiteUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "IsVisible", "SortOrder"); - - b.ToTable("Sponsors"); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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.ShowactApplication", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260627174243_AddShowactDynamicForm.cs b/Backend/Migrations/20260627174243_AddShowactDynamicForm.cs deleted file mode 100644 index 554f778..0000000 --- a/Backend/Migrations/20260627174243_AddShowactDynamicForm.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddShowactDynamicForm : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShowactFormSchemaJson", - table: "SiteSettings", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "FieldResponsesJson", - table: "ShowactApplications", - type: "text", - nullable: false, - defaultValue: ""); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - column: "ShowactFormSchemaJson", - value: "[]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ShowactFormSchemaJson", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "FieldResponsesJson", - table: "ShowactApplications"); - } - } -} diff --git a/Backend/Migrations/20260628091734_AddCategoryViewerRanges.cs b/Backend/Migrations/20260628091734_AddCategoryViewerRanges.cs deleted file mode 100644 index bec5c66..0000000 --- a/Backend/Migrations/20260628091734_AddCategoryViewerRanges.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddCategoryViewerRanges : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ViewerRangeMax", - table: "Categories", - type: "integer", - nullable: true); - - migrationBuilder.AddColumn( - name: "ViewerRangeMin", - table: "Categories", - type: "integer", - nullable: true); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 2, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 3, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 4, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 5, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 6, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 7, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 8, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 9, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - - migrationBuilder.UpdateData( - table: "Categories", - keyColumn: "Id", - keyValue: 10, - columns: new[] { "ViewerRangeMax", "ViewerRangeMin" }, - values: new object[] { null, null }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ViewerRangeMax", - table: "Categories"); - - migrationBuilder.DropColumn( - name: "ViewerRangeMin", - table: "Categories"); - } - } -} diff --git a/Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.cs b/Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.cs deleted file mode 100644 index 333d2f3..0000000 --- a/Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSessionIdleTimeoutSettings : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "SessionIdleTimeoutHours", - table: "SiteSettings", - type: "integer", - nullable: false, - defaultValue: 3); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - column: "SessionIdleTimeoutHours", - value: 3); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SessionIdleTimeoutHours", - table: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.Designer.cs b/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.Designer.cs deleted file mode 100644 index 648d75c..0000000 --- a/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.Designer.cs +++ /dev/null @@ -1,1799 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260628110302_AddSeasonSubcategoryTemplates")] - partial class AddSeasonSubcategoryTemplates - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("ViewerRangeMax") - .HasColumnType("integer"); - - b.Property("ViewerRangeMin") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SubcategoryTemplatesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.ShowactApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ArtistName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactDiscord") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("FieldResponsesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("PerformanceType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("PlatformUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReferenceUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TechnicalNotes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ShowactApplications"); - }); - - modelBuilder.Entity("Backend.Domain.SiteSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipAdminMenuVisible") - .HasColumnType("boolean"); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SessionIdleTimeoutHours") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(3); - - b.Property("ShareDiscordUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareXUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactApplicationDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ShowactApplicationsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ShowactFormSchemaJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactsContent") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue(""); - - b.Property("ShowactsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SponsorsVisible") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("IsVisible") - .HasColumnType("boolean"); - - b.Property("LogoUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("Tier") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WebsiteUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "IsVisible", "SortOrder"); - - b.ToTable("Sponsors"); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - 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.ShowactApplication", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.cs b/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.cs deleted file mode 100644 index aa4c517..0000000 --- a/Backend/Migrations/20260628110302_AddSeasonSubcategoryTemplates.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSeasonSubcategoryTemplates : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "SubcategoryTemplatesJson", - table: "Seasons", - type: "text", - nullable: false, - defaultValue: "[]"); - - migrationBuilder.UpdateData( - table: "Seasons", - keyColumn: "Id", - keyValue: 1, - column: "SubcategoryTemplatesJson", - value: "[]"); - - migrationBuilder.UpdateData( - table: "Seasons", - keyColumn: "Id", - keyValue: 2, - column: "SubcategoryTemplatesJson", - value: "[]"); - - migrationBuilder.UpdateData( - table: "Seasons", - keyColumn: "Id", - keyValue: 3, - column: "SubcategoryTemplatesJson", - value: "[]"); - - migrationBuilder.UpdateData( - table: "Seasons", - keyColumn: "Id", - keyValue: 4, - column: "SubcategoryTemplatesJson", - value: "[]"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "SubcategoryTemplatesJson", - table: "Seasons"); - } - } -} diff --git a/Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.cs b/Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.cs deleted file mode 100644 index 58eec8c..0000000 --- a/Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.cs +++ /dev/null @@ -1,388 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddNominationGroupTrackerIdentity : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Nominations_Categories_CategoryId", - table: "Nominations"); - - migrationBuilder.AlterColumn( - name: "CategoryId", - table: "Nominations", - type: "integer", - nullable: true, - oldClrType: typeof(int), - oldType: "integer"); - - migrationBuilder.AddColumn( - name: "AvgViewers", - table: "Nominations", - type: "integer", - nullable: true); - - migrationBuilder.AddColumn( - name: "CategoryGroupName", - table: "Nominations", - type: "character varying(80)", - maxLength: 80, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "ResolvedChannel", - table: "Nominations", - type: "character varying(120)", - maxLength: 120, - nullable: true); - - migrationBuilder.AddColumn( - name: "ResolvedPlatform", - table: "Nominations", - type: "character varying(40)", - maxLength: 40, - nullable: true); - - migrationBuilder.AddColumn( - name: "StreamerIdentityId", - table: "Nominations", - type: "integer", - nullable: true); - - migrationBuilder.AddColumn( - name: "SuggestedCategoryId", - table: "Nominations", - type: "integer", - nullable: true); - - migrationBuilder.AddColumn( - name: "TrackerCheckedAt", - table: "Nominations", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "TrackerStatus", - table: "Nominations", - type: "character varying(40)", - maxLength: 40, - nullable: false, - defaultValue: "pending"); - - migrationBuilder.AddColumn( - name: "NominationTally", - table: "Candidates", - type: "integer", - nullable: false, - defaultValue: 0); - - migrationBuilder.AddColumn( - name: "StreamerIdentityId", - table: "Candidates", - type: "integer", - nullable: true); - - migrationBuilder.CreateTable( - name: "StreamerIdentities", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Platform = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), - Login = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - NormalizedKey = table.Column(type: "character varying(180)", maxLength: 180, nullable: false), - DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), - ProfileUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), - LastResolvedAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_StreamerIdentities", x => x.Id); - }); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 1, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 2, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 3, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 4, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 5, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 6, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 7, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 8, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 9, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 10, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 11, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 12, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Candidates", - keyColumn: "Id", - keyValue: 13, - column: "StreamerIdentityId", - value: null); - - migrationBuilder.UpdateData( - table: "Nominations", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "AvgViewers", "CategoryGroupName", "ResolvedChannel", "ResolvedPlatform", "StreamerIdentityId", "SuggestedCategoryId", "TrackerCheckedAt", "TrackerStatus" }, - values: new object[] { null, "", null, null, null, null, null, "pending" }); - - migrationBuilder.UpdateData( - table: "Nominations", - keyColumn: "Id", - keyValue: 2, - columns: new[] { "AvgViewers", "CategoryGroupName", "ResolvedChannel", "ResolvedPlatform", "StreamerIdentityId", "SuggestedCategoryId", "TrackerCheckedAt", "TrackerStatus" }, - values: new object[] { null, "", null, null, null, null, null, "pending" }); - - migrationBuilder.Sql(""" - UPDATE "Nominations" n - SET "CategoryGroupName" = c."GroupName" - FROM "Categories" c - WHERE n."CategoryId" = c."Id" - AND COALESCE(n."CategoryGroupName", '') = ''; - """); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_SeasonId_CategoryGroupName_Status", - table: "Nominations", - columns: new[] { "SeasonId", "CategoryGroupName", "Status" }); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_SeasonId_StreamerIdentityId_CategoryGroupName", - table: "Nominations", - columns: new[] { "SeasonId", "StreamerIdentityId", "CategoryGroupName" }); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_StreamerIdentityId", - table: "Nominations", - column: "StreamerIdentityId"); - - migrationBuilder.CreateIndex( - name: "IX_Nominations_SuggestedCategoryId", - table: "Nominations", - column: "SuggestedCategoryId"); - - migrationBuilder.CreateIndex( - name: "IX_Candidates_StreamerIdentityId", - table: "Candidates", - column: "StreamerIdentityId"); - - migrationBuilder.CreateIndex( - name: "IX_StreamerIdentities_NormalizedKey", - table: "StreamerIdentities", - column: "NormalizedKey", - unique: true); - - migrationBuilder.AddForeignKey( - name: "FK_Candidates_StreamerIdentities_StreamerIdentityId", - table: "Candidates", - column: "StreamerIdentityId", - principalTable: "StreamerIdentities", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_Nominations_Categories_CategoryId", - table: "Nominations", - column: "CategoryId", - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.SetNull); - - migrationBuilder.AddForeignKey( - name: "FK_Nominations_Categories_SuggestedCategoryId", - table: "Nominations", - column: "SuggestedCategoryId", - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.SetNull); - - migrationBuilder.AddForeignKey( - name: "FK_Nominations_StreamerIdentities_StreamerIdentityId", - table: "Nominations", - column: "StreamerIdentityId", - principalTable: "StreamerIdentities", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Candidates_StreamerIdentities_StreamerIdentityId", - table: "Candidates"); - - migrationBuilder.DropForeignKey( - name: "FK_Nominations_Categories_CategoryId", - table: "Nominations"); - - migrationBuilder.DropForeignKey( - name: "FK_Nominations_Categories_SuggestedCategoryId", - table: "Nominations"); - - migrationBuilder.DropForeignKey( - name: "FK_Nominations_StreamerIdentities_StreamerIdentityId", - table: "Nominations"); - - migrationBuilder.DropTable( - name: "StreamerIdentities"); - - migrationBuilder.DropIndex( - name: "IX_Nominations_SeasonId_CategoryGroupName_Status", - table: "Nominations"); - - migrationBuilder.DropIndex( - name: "IX_Nominations_SeasonId_StreamerIdentityId_CategoryGroupName", - table: "Nominations"); - - migrationBuilder.DropIndex( - name: "IX_Nominations_StreamerIdentityId", - table: "Nominations"); - - migrationBuilder.DropIndex( - name: "IX_Nominations_SuggestedCategoryId", - table: "Nominations"); - - migrationBuilder.DropIndex( - name: "IX_Candidates_StreamerIdentityId", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "AvgViewers", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "CategoryGroupName", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "ResolvedChannel", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "ResolvedPlatform", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "StreamerIdentityId", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "SuggestedCategoryId", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "TrackerCheckedAt", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "TrackerStatus", - table: "Nominations"); - - migrationBuilder.DropColumn( - name: "NominationTally", - table: "Candidates"); - - migrationBuilder.DropColumn( - name: "StreamerIdentityId", - table: "Candidates"); - - migrationBuilder.AlterColumn( - name: "CategoryId", - table: "Nominations", - type: "integer", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "integer", - oldNullable: true); - - migrationBuilder.AddForeignKey( - name: "FK_Nominations_Categories_CategoryId", - table: "Nominations", - column: "CategoryId", - principalTable: "Categories", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/Backend/Migrations/20260628161949_AddShowactApplicationSchedule.cs b/Backend/Migrations/20260628161949_AddShowactApplicationSchedule.cs deleted file mode 100644 index 2baeac0..0000000 --- a/Backend/Migrations/20260628161949_AddShowactApplicationSchedule.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddShowactApplicationSchedule : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ShowactApplicationEndsAt", - table: "SiteSettings", - type: "date", - nullable: true); - - migrationBuilder.AddColumn( - name: "ShowactApplicationStartsAt", - table: "SiteSettings", - type: "date", - nullable: true); - - migrationBuilder.UpdateData( - table: "SiteSettings", - keyColumn: "Id", - keyValue: 1, - columns: new[] { "ShowactApplicationEndsAt", "ShowactApplicationStartsAt" }, - values: new object[] { null, null }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "ShowactApplicationEndsAt", - table: "SiteSettings"); - - migrationBuilder.DropColumn( - name: "ShowactApplicationStartsAt", - table: "SiteSettings"); - } - } -} diff --git a/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.Designer.cs b/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.Designer.cs deleted file mode 100644 index 6c61020..0000000 --- a/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.Designer.cs +++ /dev/null @@ -1,2010 +0,0 @@ -// -using System; -using Backend.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Backend.Migrations -{ - [DbContext(typeof(AwardsDbContext))] - [Migration("20260628205353_AddSeasonWorkflowRulesJson")] - partial class AddSeasonWorkflowRulesJson - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.11") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Backend.Domain.AdminAuditEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("AdminTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("EntityId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("EntityType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.ToTable("AdminAuditEntries"); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CategoryName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId", "CategoryId") - .IsUnique(); - - b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AcceptanceNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("AcceptanceStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("open"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ChannelSlug") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ClipCompilationPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ClipCompilationTitle") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ClipCompilationUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ClipEmbedStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("unchecked"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NominationTally") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("StreamerIdentityId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("CategoryId"); - - b.HasIndex("SeasonId"); - - b.HasIndex("StreamerIdentityId"); - - b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 4 - }); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("GroupName") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MaxNomineesPerUser") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Slug") - .IsRequired() - .HasColumnType("text"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("ViewerRangeMax") - .HasColumnType("integer"); - - b.Property("ViewerRangeMin") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Slug") - .IsUnique(); - - b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die größte Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); - }); - - modelBuilder.Entity("Backend.Domain.ClipSubmission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("ClipUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Creator") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AvgViewers") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CandidateText") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CategoryGroupName") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasDefaultValue(""); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("FollowersGained") - .HasColumnType("integer"); - - b.Property("HoursStreamed") - .HasColumnType("integer"); - - b.Property("HoursWatched") - .HasColumnType("integer"); - - b.Property("PeakViewers") - .HasColumnType("integer"); - - b.Property("ResolvedChannel") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ResolvedPlatform") - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("StreamUrl") - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("StreamerIdentityId") - .HasColumnType("integer"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SuggestedCategoryId") - .HasColumnType("integer"); - - b.Property("TrackerCheckedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrackerStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(40) - .HasColumnType("character varying(40)") - .HasDefaultValue("pending"); - - b.Property("TrackingFlagsJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("TrackingReviewNote") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("TrackingReviewStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(30) - .HasColumnType("character varying(30)") - .HasDefaultValue("clear"); - - b.Property("TrackingReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrackingReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.HasIndex("StreamerIdentityId"); - - b.HasIndex("SuggestedCategoryId"); - - b.HasIndex("SeasonId", "Status"); - - b.HasIndex("SeasonId", "CategoryGroupName", "Status"); - - b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); - - b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - CategoryGroupName = "", - 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", - TrackerStatus = "pending", - TrackingFlagsJson = "[]", - TrackingReviewStatus = "clear" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - CategoryGroupName = "", - 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", - TrackerStatus = "pending", - TrackingFlagsJson = "[]", - TrackingReviewStatus = "clear" - }); - }); - - modelBuilder.Entity("Backend.Domain.RiskFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MetadataJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Summary") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("TwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CurrentPhase") - .IsRequired() - .HasMaxLength(60) - .HasColumnType("character varying(60)"); - - b.Property("IsCommunityOnly") - .HasColumnType("boolean"); - - b.Property("IsCurrent") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("NominationEndsAt") - .HasColumnType("date"); - - b.Property("NominationStartsAt") - .HasColumnType("date"); - - b.Property("ReviewEndsAt") - .HasColumnType("date"); - - b.Property("ReviewStartsAt") - .HasColumnType("date"); - - b.Property("ShowDate") - .HasColumnType("date"); - - b.Property("ShowStartsAt") - .HasColumnType("time without time zone"); - - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SubcategoryTemplatesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("VotingEndsAt") - .HasColumnType("date"); - - b.Property("VotingStartsAt") - .HasColumnType("date"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("Year") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Year") - .IsUnique(); - - b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - WorkflowRulesJson = "[]", - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - WorkflowRulesJson = "[]", - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - WorkflowRulesJson = "[]", - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - WorkflowRulesJson = "[]", - Year = 2023 - }); - }); - - modelBuilder.Entity("Backend.Domain.ShowactApplication", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ArtistName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactDiscord") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("ContactEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("FieldResponsesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("PerformanceType") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("PlatformUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReferenceUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewNote") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ReviewedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("TechnicalNotes") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)"); - - b.Property("UserAgent") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "Status"); - - b.ToTable("ShowactApplications"); - }); - - modelBuilder.Entity("Backend.Domain.SiteSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClipAdminMenuVisible") - .HasColumnType("boolean"); - - b.Property("ClipReviewEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("ClipSubmissionDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ClipSubmissionsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ContactContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContactUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("DemoLoginDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginEmail") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("DemoLoginEnabled") - .HasColumnType("boolean"); - - b.Property("DemoLoginManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("DemoLoginPasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DemoLoginPasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DemoLoginTwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("FaqJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("HostDisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("HostTagline") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("ImprintContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("ImprintUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("MaintenanceMessage") - .IsRequired() - .HasMaxLength(600) - .HasColumnType("character varying(600)"); - - b.Property("MaintenanceModeEnabled") - .HasColumnType("boolean"); - - b.Property("MaintenanceTitle") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NewsletterUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("NominationLinkBlacklistJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("PrivacyEmail") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)"); - - b.Property("PrivacyPolicyContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrivacyPolicyUpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PrivacyPolicyUpdatedBy") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("RiskRulesJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SessionIdleTimeoutHours") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(3); - - b.Property("ShareDiscordUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShareXUrl") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactApplicationDisabledMessage") - .IsRequired() - .HasMaxLength(240) - .HasColumnType("character varying(240)"); - - b.Property("ShowactApplicationEndsAt") - .HasColumnType("date"); - - b.Property("ShowactApplicationStartsAt") - .HasColumnType("date"); - - b.Property("ShowactApplicationsEnabled") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false); - - b.Property("ShowactFormSchemaJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("ShowactsContent") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue(""); - - b.Property("ShowactsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SocialLinksJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsContent") - .IsRequired() - .HasColumnType("text"); - - b.Property("SponsorsUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("SponsorsVisible") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true); - - b.Property("TrackingReviewNotes") - .IsRequired() - .HasColumnType("text"); - - b.Property("TrackingRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.Property("TwitchAuthManagedByDatabase") - .HasColumnType("boolean"); - - b.Property("TwitchClientId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchClientSecret") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("TwitchRedirectUri") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("TwitchScope") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)"); - - b.Property("ViewerStatsProviderBaseUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - - b.Property("WorkflowRulesJson") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text") - .HasDefaultValue("[]"); - - b.HasKey("Id"); - - b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team über die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFür Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze für organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsächlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt für die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier können Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den öffentlichen Kontaktflächen genannt, sobald sie final bestätigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TrackingReviewNotes = "Fallback-Quellen für manuelle Reviews:\\n- SullyGnome\\n- Twitch-Kanal direkt\\n\\nNutze diese Notizen für Edge Cases und manuelle Tier-Entscheidungen.", - TrackingRulesJson = "{\"source\":{\"providerKey\":\"twitchtracker\",\"baseUrl\":\"https://twitchtracker.com/api\",\"notesSummary\":\"TwitchTracker Basic API liefert aktuell Channel-Summary-Daten fuer 30 Tage. Andere Zeitfenster bleiben konfigurierbar, werden aber als manueller Review-Fall markiert.\",\"showManualReviewNotesInReview\":true},\"importantMetrics\":[{\"key\":\"avg_viewers\",\"label\":\"Avg Viewer\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Durchschnittliche Viewer fuer den gewaehlten Zeitraum.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"avg_viewers\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"tracker_status\",\"label\":\"Tracker-Status\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Zeigt, ob der TwitchTracker-Lookup sauber aufgeloest werden konnte.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":false,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"tracker_status\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"tracker_checked_at\",\"label\":\"Letzter Tracker-Check\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Zeitpunkt der letzten automatischen Datenaufloesung.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":false,\"manualOverrideAllowed\":false,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"tracker_checked_at\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null}],\"optionalMetrics\":[{\"key\":\"hours_streamed\",\"label\":\"Hours Streamed\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Gesamte Streamstunden im gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"hours_streamed\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"hours_watched\",\"label\":\"Hours Watched\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Gesamte Watch Time fuer den gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"hours_watched\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"peak_viewers\",\"label\":\"Peak Viewer\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Hoechster gleichzeitiger Zuschauerwert im Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"peak_viewers\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"followers_gained\",\"label\":\"Follower Growth\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Follower-Zuwachs im gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"followers_gained\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"category_fit\",\"label\":\"Category Fit\",\"enabled\":false,\"sourceSupport\":\"context_only\",\"description\":\"Admin-Einschaetzung, ob die Person inhaltlich zur Unterkategorie passt.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":false,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[],\"providerFieldKey\":null,\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"top_categories_context\",\"label\":\"Top Categories Context\",\"enabled\":false,\"sourceSupport\":\"context_only\",\"description\":\"Manueller Kontext aus zuletzt meistgestreamten Kategorien oder Games des Channels.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[],\"providerFieldKey\":null,\"topCount\":5,\"minPrimaryCategorySharePercent\":60,\"minPrimaryCategoryHours\":20,\"maxDistinctCategoriesBeforeFlag\":6,\"ignoredCategories\":[\"Just Chatting\",\"Special Events\"],\"matchAwardCategoryAgainstTopCategories\":true,\"flagIfAwardCategoryNotInTopX\":true,\"flagIfCategorySpreadTooWide\":true,\"flagIfNoCategoryContextAvailable\":true,\"minValue\":null,\"maxValue\":null}],\"flags\":[{\"key\":\"tracker_unresolved\",\"label\":\"Tracker-Link nicht aufloesbar\",\"enabled\":true,\"severity\":\"high\",\"description\":\"Der Stream-Link konnte nicht sauber in einen Twitch-Channel aufgeloest werden.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"unsupported_platform\",\"label\":\"Plattform nicht unterstuetzt\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die Nominierung zeigt auf eine Plattform ohne automatische TwitchTracker-Daten.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"no_tracker_data\",\"label\":\"Keine Tracker-Daten\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"TwitchTracker hat keinen belastbaren Summary-Wert geliefert.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"missing_required_metric\",\"label\":\"Pflichtmetrik fehlt\",\"enabled\":true,\"severity\":\"high\",\"description\":\"Mindestens eine aktivierte Pflichtmetrik fuer die Auto-Klassifizierung fehlt.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":true,\"adminNoteRequiredOnOverride\":false},{\"key\":\"manual_review_required\",\"label\":\"Manuelle Pruefung noetig\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die Auto-Daten reichen nicht fuer eine sichere Review-Entscheidung.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"low_confidence_small_channel\",\"label\":\"Low Confidence Small Channel\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Kleine Kanaele koennen manuell tiefer geprueft werden.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"insufficient_activity_context\",\"label\":\"Zu wenig Aktivitaetskontext\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Ohne zusaetzliche Kontextdaten wie Streamstunden ist die Bewertung unsicher.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"category_fit_needs_review\",\"label\":\"Category Fit manuell pruefen\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Unterkategorie muss inhaltlich manuell bestaetigt werden.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"unsupported_metric_window\",\"label\":\"Gewaehltes Zeitfenster nicht auto-verfuegbar\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die aktuelle TwitchTracker API liefert diese Metrik nicht fuer das konfigurierte Zeitfenster.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false}]}", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - ViewerStatsProviderBaseUrl = "https://twitchtracker.com/api", - WorkflowRulesJson = "[{\"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.\"},{\"key\":\"recommended_nominators_per_subcategory\",\"label\":\"Empfohlene Nominierer pro Unterkategorie\",\"enabled\":true,\"limit\":4,\"mode\":\"warn\",\"description\":\"Zeigt in der Kategorie-\\u00DCbersicht an, ab wann eine Unterkategorie nominierungsseitig gut getragen ist. Diese Regel blockiert nichts.\"}]" - }); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("IsVisible") - .HasColumnType("boolean"); - - b.Property("LogoUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("SortOrder") - .HasColumnType("integer"); - - b.Property("Tier") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WebsiteUrl") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "IsVisible", "SortOrder"); - - b.ToTable("Sponsors"); - }); - - modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("LastResolvedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("NormalizedKey") - .IsRequired() - .HasMaxLength(180) - .HasColumnType("character varying(180)"); - - b.Property("Platform") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("ProfileUrl") - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedKey") - .IsUnique(); - - b.ToTable("StreamerIdentities"); - }); - - modelBuilder.Entity("Backend.Domain.TeamMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BoundTwitchDisplayName") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("BoundTwitchUserId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastLoginAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Login") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("MustChangePassword") - .HasColumnType("boolean"); - - b.Property("PasswordHash") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("PasswordResetAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PasswordSalt") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("TwitchBoundAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("BoundTwitchUserId") - .IsUnique(); - - b.HasIndex("Login") - .IsUnique(); - - b.ToTable("TeamMembers"); - }); - - modelBuilder.Entity("Backend.Domain.TeamRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PermissionsJson") - .IsRequired() - .HasColumnType("text"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UpdatedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("Role") - .IsUnique(); - - b.ToTable("TeamRolePermissions"); - }); - - modelBuilder.Entity("Backend.Domain.UserSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CreatedFromIp") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(40) - .HasColumnType("character varying(40)"); - - b.Property("SessionToken") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("TwitchUserId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.Property("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("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("SeasonId") - .HasColumnType("integer"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(30) - .HasColumnType("character varying(30)"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("SubmittedByTwitchId") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)"); - - b.HasKey("Id"); - - b.HasIndex("SeasonId", "SubmittedByTwitchId") - .IsUnique(); - - b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BallotId") - .HasColumnType("integer"); - - b.Property("CandidateId") - .HasColumnType("integer"); - - b.Property("CategoryId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("BallotId"); - - b.HasIndex("CandidateId"); - - b.HasIndex("CategoryId"); - - b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); - }); - - modelBuilder.Entity("Backend.Domain.AwardResult", b => - { - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .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") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Candidate", b => - { - b.HasOne("Backend.Domain.Category", "Category") - .WithMany("Candidates") - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") - .WithMany("Candidates") - .HasForeignKey("StreamerIdentityId"); - - b.Navigation("Category"); - - b.Navigation("Season"); - - b.Navigation("StreamerIdentity"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany("Categories") - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - 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") - .WithMany() - .HasForeignKey("CandidateId"); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") - .WithMany("Nominations") - .HasForeignKey("StreamerIdentityId"); - - b.HasOne("Backend.Domain.Category", "SuggestedCategory") - .WithMany() - .HasForeignKey("SuggestedCategoryId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - - b.Navigation("Season"); - - b.Navigation("StreamerIdentity"); - - b.Navigation("SuggestedCategory"); - }); - - modelBuilder.Entity("Backend.Domain.RiskFlag", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId"); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.ShowactApplication", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.Sponsor", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.HasOne("Backend.Domain.Season", "Season") - .WithMany() - .HasForeignKey("SeasonId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Season"); - }); - - modelBuilder.Entity("Backend.Domain.VoteEntry", b => - { - b.HasOne("Backend.Domain.VoteBallot", "Ballot") - .WithMany("Entries") - .HasForeignKey("BallotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Candidate", "Candidate") - .WithMany() - .HasForeignKey("CandidateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Backend.Domain.Category", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Ballot"); - - b.Navigation("Candidate"); - - b.Navigation("Category"); - }); - - modelBuilder.Entity("Backend.Domain.Category", b => - { - b.Navigation("Candidates"); - }); - - modelBuilder.Entity("Backend.Domain.Season", b => - { - b.Navigation("Categories"); - - b.Navigation("Results"); - }); - - modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => - { - b.Navigation("Candidates"); - - b.Navigation("Nominations"); - }); - - modelBuilder.Entity("Backend.Domain.VoteBallot", b => - { - b.Navigation("Entries"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.cs b/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.cs deleted file mode 100644 index f1ebbd8..0000000 --- a/Backend/Migrations/20260628205353_AddSeasonWorkflowRulesJson.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Backend.Migrations -{ - /// - public partial class AddSeasonWorkflowRulesJson : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "WorkflowRulesJson", - table: "Seasons", - type: "text", - nullable: false, - defaultValue: "[]"); - - migrationBuilder.Sql( - """ - UPDATE "Seasons" - SET "WorkflowRulesJson" = COALESCE(NULLIF((SELECT "WorkflowRulesJson" FROM "SiteSettings" WHERE "Id" = 1), ''), '[]') - WHERE COALESCE("WorkflowRulesJson", '') = ''; - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "WorkflowRulesJson", - table: "Seasons"); - } - } -} diff --git a/Backend/Migrations/20260628161949_AddShowactApplicationSchedule.Designer.cs b/Backend/Migrations/20260629142009_InitialCleanBaseline.Designer.cs similarity index 62% rename from Backend/Migrations/20260628161949_AddShowactApplicationSchedule.Designer.cs rename to Backend/Migrations/20260629142009_InitialCleanBaseline.Designer.cs index 1165a2b..65ca5ae 100644 --- a/Backend/Migrations/20260628161949_AddShowactApplicationSchedule.Designer.cs +++ b/Backend/Migrations/20260629142009_InitialCleanBaseline.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Backend.Migrations { [DbContext(typeof(AwardsDbContext))] - [Migration("20260628161949_AddShowactApplicationSchedule")] - partial class AddShowactApplicationSchedule + [Migration("20260629142009_InitialCleanBaseline")] + partial class InitialCleanBaseline { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -112,56 +112,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Candidate", b => @@ -240,164 +190,6 @@ namespace Backend.Migrations b.HasIndex("StreamerIdentityId"); b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -448,118 +240,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); }); modelBuilder.Entity("Backend.Domain.ClipSubmission", b => @@ -668,6 +348,18 @@ namespace Backend.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("FollowersGained") + .HasColumnType("integer"); + + b.Property("HoursStreamed") + .HasColumnType("integer"); + + b.Property("HoursWatched") + .HasColumnType("integer"); + + b.Property("PeakViewers") + .HasColumnType("integer"); + b.Property("ResolvedChannel") .HasMaxLength(120) .HasColumnType("character varying(120)"); @@ -720,6 +412,30 @@ namespace Backend.Migrations .HasColumnType("character varying(40)") .HasDefaultValue("pending"); + b.Property("TrackingFlagsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + + b.Property("TrackingReviewNote") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TrackingReviewStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasDefaultValue("clear"); + + b.Property("TrackingReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackingReviewedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + b.HasKey("Id"); b.HasIndex("CandidateId"); @@ -737,32 +453,6 @@ namespace Backend.Migrations b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - CategoryGroupName = "", - 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", - TrackerStatus = "pending" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - CategoryGroupName = "", - 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", - TrackerStatus = "pending" - }); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -859,6 +549,11 @@ namespace Backend.Migrations b.Property("IsCurrent") .HasColumnType("boolean"); + b.Property("IsDemo") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(160) @@ -882,11 +577,6 @@ namespace Backend.Migrations b.Property("ShowStartsAt") .HasColumnType("time without time zone"); - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - b.Property("SubcategoryTemplatesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -899,6 +589,19 @@ namespace Backend.Migrations b.Property("VotingStartsAt") .HasColumnType("date"); + b.Property("WinnersPublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WinnersPublishedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("WorkflowRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("Year") .HasColumnType("integer"); @@ -908,84 +611,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); }); modelBuilder.Entity("Backend.Domain.ShowactApplication", b => @@ -1087,6 +712,14 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AwardsSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AwardsSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("ClipAdminMenuVisible") .HasColumnType("boolean"); @@ -1274,6 +907,80 @@ namespace Backend.Migrations .HasColumnType("boolean") .HasDefaultValue(true); + b.Property("StreamBannerCompletedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerCompletedEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerCompletedTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerLockedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerUseCompletedContent") + .HasColumnType("boolean"); + + b.Property("SubcategoriesSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubcategoriesSectionTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingReviewNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("TwitchAuthManagedByDatabase") .HasColumnType("boolean"); @@ -1297,6 +1004,11 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("ViewerStatsProviderBaseUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + b.Property("WorkflowRulesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -1306,58 +1018,6 @@ namespace Backend.Migrations b.HasKey("Id"); b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); }); modelBuilder.Entity("Backend.Domain.Sponsor", b => @@ -1656,24 +1316,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); }); modelBuilder.Entity("Backend.Domain.VoteEntry", b => @@ -1702,36 +1344,6 @@ namespace Backend.Migrations b.HasIndex("CategoryId"); b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); }); modelBuilder.Entity("Backend.Domain.AwardResult", b => @@ -1804,6 +1416,12 @@ namespace Backend.Migrations .HasForeignKey("CandidateId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Backend.Domain.Season", null) + .WithMany() + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Candidate"); }); diff --git a/Backend/Migrations/20260629142009_InitialCleanBaseline.cs b/Backend/Migrations/20260629142009_InitialCleanBaseline.cs new file mode 100644 index 0000000..ab484b5 --- /dev/null +++ b/Backend/Migrations/20260629142009_InitialCleanBaseline.cs @@ -0,0 +1,795 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Backend.Migrations +{ + /// + public partial class InitialCleanBaseline : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AdminAuditEntries", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + AdminTwitchUserId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + ActionType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + EntityType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + EntityId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Summary = table.Column(type: "character varying(240)", maxLength: 240, nullable: false), + MetadataJson = table.Column(type: "text", nullable: false), + CreatedFromIp = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + UserAgent = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AdminAuditEntries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Seasons", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Year = table.Column(type: "integer", nullable: false), + Name = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + IsDemo = table.Column(type: "boolean", nullable: false, defaultValue: false), + IsCurrent = table.Column(type: "boolean", nullable: false), + IsCommunityOnly = table.Column(type: "boolean", nullable: false), + CurrentPhase = table.Column(type: "character varying(60)", maxLength: 60, nullable: false), + NominationStartsAt = table.Column(type: "date", nullable: false), + NominationEndsAt = table.Column(type: "date", nullable: false), + VotingStartsAt = table.Column(type: "date", nullable: false), + VotingEndsAt = table.Column(type: "date", nullable: false), + ReviewStartsAt = table.Column(type: "date", nullable: false), + ReviewEndsAt = table.Column(type: "date", nullable: false), + ShowDate = table.Column(type: "date", nullable: false), + ShowStartsAt = table.Column(type: "time without time zone", nullable: false), + WinnersPublishedAt = table.Column(type: "timestamp with time zone", nullable: true), + WinnersPublishedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + SubcategoryTemplatesJson = table.Column(type: "text", nullable: false, defaultValue: "[]"), + WorkflowRulesJson = table.Column(type: "text", nullable: false, defaultValue: "[]") + }, + constraints: table => + { + table.PrimaryKey("PK_Seasons", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SiteSettings", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + HostDisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + HostTagline = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + NewsletterUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + ShareXUrl = table.Column(type: "text", nullable: false), + ShareDiscordUrl = table.Column(type: "text", nullable: false), + PrivacyEmail = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + PrivacyPolicyContent = table.Column(type: "text", nullable: false), + PrivacyPolicyUpdatedBy = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + PrivacyPolicyUpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + ImprintUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + ImprintContent = table.Column(type: "text", nullable: false), + ContactUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + ContactContent = table.Column(type: "text", nullable: false), + SponsorsUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + SponsorsContent = table.Column(type: "text", nullable: false), + ShowactsUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + ShowactsContent = table.Column(type: "text", nullable: false, defaultValue: ""), + StreamBannerEyebrow = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + StreamBannerTitle = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + StreamBannerText = table.Column(type: "text", nullable: false), + StreamBannerLiveButtonLabel = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + StreamBannerLiveButtonUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + StreamBannerLockedButtonLabel = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + StreamBannerUseCompletedContent = table.Column(type: "boolean", nullable: false), + StreamBannerCompletedEyebrow = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + StreamBannerCompletedTitle = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + StreamBannerCompletedText = table.Column(type: "text", nullable: false), + StreamBannerCompletedButtonLabel = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + StreamBannerCompletedButtonUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + AwardsSectionTitle = table.Column(type: "text", nullable: false), + AwardsSectionDescription = table.Column(type: "text", nullable: false), + SubcategoriesSectionTitle = table.Column(type: "text", nullable: false), + SubcategoriesSectionDescription = table.Column(type: "text", nullable: false), + SocialLinksJson = table.Column(type: "text", nullable: false), + FaqJson = table.Column(type: "text", nullable: false), + RiskRulesJson = table.Column(type: "text", nullable: false), + WorkflowRulesJson = table.Column(type: "text", nullable: false, defaultValue: "[]"), + TrackingRulesJson = table.Column(type: "text", nullable: false, defaultValue: "[]"), + ViewerStatsProviderBaseUrl = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + TrackingReviewNotes = table.Column(type: "text", nullable: false), + NominationLinkBlacklistJson = table.Column(type: "text", nullable: false, defaultValue: "[]"), + ClipSubmissionsEnabled = table.Column(type: "boolean", nullable: false, defaultValue: false), + ClipReviewEnabled = table.Column(type: "boolean", nullable: false, defaultValue: true), + ClipAdminMenuVisible = table.Column(type: "boolean", nullable: false), + ClipSubmissionDisabledMessage = table.Column(type: "character varying(240)", maxLength: 240, nullable: false), + ShowactApplicationsEnabled = table.Column(type: "boolean", nullable: false, defaultValue: false), + ShowactApplicationStartsAt = table.Column(type: "date", nullable: true), + ShowactApplicationEndsAt = table.Column(type: "date", nullable: true), + ShowactApplicationDisabledMessage = table.Column(type: "character varying(240)", maxLength: 240, nullable: false), + ShowactFormSchemaJson = table.Column(type: "text", nullable: false), + SponsorsVisible = table.Column(type: "boolean", nullable: false, defaultValue: true), + DemoLoginManagedByDatabase = table.Column(type: "boolean", nullable: false), + DemoLoginEnabled = table.Column(type: "boolean", nullable: false), + DemoLoginEmail = table.Column(type: "character varying(180)", maxLength: 180, nullable: false), + DemoLoginPasswordHash = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + DemoLoginPasswordSalt = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + DemoLoginTwitchUserId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + DemoLoginDisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + TwitchAuthManagedByDatabase = table.Column(type: "boolean", nullable: false), + TwitchClientId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + TwitchClientSecret = table.Column(type: "character varying(180)", maxLength: 180, nullable: false), + TwitchRedirectUri = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + TwitchScope = table.Column(type: "character varying(300)", maxLength: 300, nullable: false), + SessionIdleTimeoutHours = table.Column(type: "integer", nullable: false, defaultValue: 3), + MaintenanceModeEnabled = table.Column(type: "boolean", nullable: false), + MaintenanceTitle = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + MaintenanceMessage = table.Column(type: "character varying(600)", maxLength: 600, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SiteSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "StreamerIdentities", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Platform = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + Login = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + NormalizedKey = table.Column(type: "character varying(180)", maxLength: 180, nullable: false), + DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + ProfileUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + LastResolvedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_StreamerIdentities", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TeamMembers", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Login = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Role = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + PasswordHash = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + PasswordSalt = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + BoundTwitchUserId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + BoundTwitchDisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + MustChangePassword = table.Column(type: "boolean", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + UpdatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + LastLoginAt = table.Column(type: "timestamp with time zone", nullable: true), + TwitchBoundAt = table.Column(type: "timestamp with time zone", nullable: true), + PasswordResetAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TeamMembers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TeamRolePermissions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Role = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + PermissionsJson = table.Column(type: "text", nullable: false), + UpdatedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TeamRolePermissions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "UserSessions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + SessionToken = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + TwitchUserId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Role = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + CreatedFromIp = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + UserAgent = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + LastSeenAt = table.Column(type: "timestamp with time zone", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserSessions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + GroupName = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + Name = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Slug = table.Column(type: "text", nullable: false), + Description = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + MaxNomineesPerUser = table.Column(type: "integer", nullable: false), + ViewerRangeMin = table.Column(type: "integer", nullable: true), + ViewerRangeMax = table.Column(type: "integer", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + table.ForeignKey( + name: "FK_Categories_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "RiskFlags", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: true), + TwitchUserId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + Source = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + Type = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + Severity = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Summary = table.Column(type: "character varying(240)", maxLength: 240, nullable: false), + CreatedFromIp = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + UserAgent = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + MetadataJson = table.Column(type: "text", nullable: false), + ReviewNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ReviewedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ReviewedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RiskFlags", x => x.Id); + table.ForeignKey( + name: "FK_RiskFlags_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "ShowactApplications", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + ArtistName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + ContactEmail = table.Column(type: "character varying(180)", maxLength: 180, nullable: false), + ContactDiscord = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + PlatformUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + PerformanceType = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + Description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + TechnicalNotes = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + ReferenceUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + FieldResponsesJson = table.Column(type: "text", nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ReviewNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ReviewedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + CreatedFromIp = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + UserAgent = table.Column(type: "character varying(400)", maxLength: 400, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ReviewedAt = table.Column(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( + name: "Sponsors", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + Name = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + WebsiteUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + LogoUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Tier = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + IsVisible = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(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); + }); + + migrationBuilder.CreateTable( + name: "VoteBallots", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + SubmittedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Status = table.Column(type: "character varying(30)", maxLength: 30, nullable: false), + SubmittedAt = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VoteBallots", x => x.Id); + table.ForeignKey( + name: "FK_VoteBallots_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Candidates", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: false), + StreamerIdentityId = table.Column(type: "integer", nullable: true), + DisplayName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + ChannelSlug = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Platform = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + NominationTally = table.Column(type: "integer", nullable: false, defaultValue: 0), + AcceptanceStatus = table.Column(type: "character varying(30)", maxLength: 30, nullable: false, defaultValue: "open"), + AcceptanceNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ClipCompilationUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ClipCompilationTitle = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + ClipCompilationPlatform = table.Column(type: "character varying(40)", maxLength: 40, nullable: true), + ClipEmbedStatus = table.Column(type: "character varying(30)", maxLength: 30, nullable: false, defaultValue: "unchecked") + }, + constraints: table => + { + table.PrimaryKey("PK_Candidates", x => x.Id); + table.ForeignKey( + name: "FK_Candidates_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Candidates_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Candidates_StreamerIdentities_StreamerIdentityId", + column: x => x.StreamerIdentityId, + principalTable: "StreamerIdentities", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "ClipSubmissions", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: true), + CandidateId = table.Column(type: "integer", nullable: true), + SubmittedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + ClipUrl = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Creator = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + Platform = table.Column(type: "character varying(40)", maxLength: 40, nullable: false), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ReviewNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ReviewedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + CreatedFromIp = table.Column(type: "character varying(80)", maxLength: 80, nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ReviewedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ClipSubmissions", x => x.Id); + table.ForeignKey( + name: "FK_ClipSubmissions_Candidates_CandidateId", + column: x => x.CandidateId, + principalTable: "Candidates", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_ClipSubmissions_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "Nominations", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: true), + CategoryGroupName = table.Column(type: "character varying(80)", maxLength: 80, nullable: false, defaultValue: ""), + SubmittedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + CandidateId = table.Column(type: "integer", nullable: true), + StreamerIdentityId = table.Column(type: "integer", nullable: true), + SuggestedCategoryId = table.Column(type: "integer", nullable: true), + CandidateText = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + StreamUrl = table.Column(type: "character varying(300)", maxLength: 300, nullable: true), + ResolvedChannel = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + ResolvedPlatform = table.Column(type: "character varying(40)", maxLength: 40, nullable: true), + AvgViewers = table.Column(type: "integer", nullable: true), + HoursStreamed = table.Column(type: "integer", nullable: true), + HoursWatched = table.Column(type: "integer", nullable: true), + PeakViewers = table.Column(type: "integer", nullable: true), + FollowersGained = table.Column(type: "integer", nullable: true), + TrackerStatus = table.Column(type: "character varying(40)", maxLength: 40, nullable: false, defaultValue: "pending"), + TrackerCheckedAt = table.Column(type: "timestamp with time zone", nullable: true), + TrackingReviewStatus = table.Column(type: "character varying(30)", maxLength: 30, nullable: false, defaultValue: "clear"), + TrackingFlagsJson = table.Column(type: "text", nullable: false, defaultValue: "[]"), + TrackingReviewNote = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + TrackingReviewedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + TrackingReviewedAt = table.Column(type: "timestamp with time zone", nullable: true), + Status = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + ReviewNote = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + ReviewedByTwitchId = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + ReviewedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Nominations", x => x.Id); + table.ForeignKey( + name: "FK_Nominations_Candidates_CandidateId", + column: x => x.CandidateId, + principalTable: "Candidates", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Nominations_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Nominations_Categories_SuggestedCategoryId", + column: x => x.SuggestedCategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "FK_Nominations_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Nominations_StreamerIdentities_StreamerIdentityId", + column: x => x.StreamerIdentityId, + principalTable: "StreamerIdentities", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "Results", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + SeasonId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: false), + CandidateId = table.Column(type: "integer", nullable: false), + CategoryName = table.Column(type: "character varying(120)", maxLength: 120, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Results", x => x.Id); + table.ForeignKey( + name: "FK_Results_Candidates_CandidateId", + column: x => x.CandidateId, + principalTable: "Candidates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Results_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_Results_Seasons_SeasonId", + column: x => x.SeasonId, + principalTable: "Seasons", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VoteEntries", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + BallotId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: false), + CandidateId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VoteEntries", x => x.Id); + table.ForeignKey( + name: "FK_VoteEntries_Candidates_CandidateId", + column: x => x.CandidateId, + principalTable: "Candidates", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_VoteEntries_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_VoteEntries_VoteBallots_BallotId", + column: x => x.BallotId, + principalTable: "VoteBallots", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Candidates_CategoryId", + table: "Candidates", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Candidates_SeasonId", + table: "Candidates", + column: "SeasonId"); + + migrationBuilder.CreateIndex( + name: "IX_Candidates_StreamerIdentityId", + table: "Candidates", + column: "StreamerIdentityId"); + + migrationBuilder.CreateIndex( + name: "IX_Categories_SeasonId_Slug", + table: "Categories", + columns: new[] { "SeasonId", "Slug" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClipSubmissions_CandidateId", + table: "ClipSubmissions", + column: "CandidateId"); + + migrationBuilder.CreateIndex( + name: "IX_ClipSubmissions_SeasonId_Status", + table: "ClipSubmissions", + columns: new[] { "SeasonId", "Status" }); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_CandidateId", + table: "Nominations", + column: "CandidateId"); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_CategoryId", + table: "Nominations", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_SeasonId_CategoryGroupName_Status", + table: "Nominations", + columns: new[] { "SeasonId", "CategoryGroupName", "Status" }); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_SeasonId_Status", + table: "Nominations", + columns: new[] { "SeasonId", "Status" }); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_SeasonId_StreamerIdentityId_CategoryGroupName", + table: "Nominations", + columns: new[] { "SeasonId", "StreamerIdentityId", "CategoryGroupName" }); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_StreamerIdentityId", + table: "Nominations", + column: "StreamerIdentityId"); + + migrationBuilder.CreateIndex( + name: "IX_Nominations_SuggestedCategoryId", + table: "Nominations", + column: "SuggestedCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Results_CandidateId", + table: "Results", + column: "CandidateId"); + + 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.CreateIndex( + name: "IX_RiskFlags_SeasonId", + table: "RiskFlags", + column: "SeasonId"); + + migrationBuilder.CreateIndex( + name: "IX_Seasons_Year", + table: "Seasons", + column: "Year", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ShowactApplications_SeasonId_Status", + table: "ShowactApplications", + columns: new[] { "SeasonId", "Status" }); + + migrationBuilder.CreateIndex( + name: "IX_Sponsors_SeasonId_IsVisible_SortOrder", + table: "Sponsors", + columns: new[] { "SeasonId", "IsVisible", "SortOrder" }); + + migrationBuilder.CreateIndex( + name: "IX_StreamerIdentities_NormalizedKey", + table: "StreamerIdentities", + column: "NormalizedKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TeamMembers_BoundTwitchUserId", + table: "TeamMembers", + column: "BoundTwitchUserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TeamMembers_Login", + table: "TeamMembers", + column: "Login", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_TeamRolePermissions_Role", + table: "TeamRolePermissions", + column: "Role", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_UserSessions_SessionToken", + table: "UserSessions", + column: "SessionToken", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_VoteBallots_SeasonId_SubmittedByTwitchId", + table: "VoteBallots", + columns: new[] { "SeasonId", "SubmittedByTwitchId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_VoteEntries_BallotId", + table: "VoteEntries", + column: "BallotId"); + + migrationBuilder.CreateIndex( + name: "IX_VoteEntries_CandidateId", + table: "VoteEntries", + column: "CandidateId"); + + migrationBuilder.CreateIndex( + name: "IX_VoteEntries_CategoryId", + table: "VoteEntries", + column: "CategoryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AdminAuditEntries"); + + migrationBuilder.DropTable( + name: "ClipSubmissions"); + + migrationBuilder.DropTable( + name: "Nominations"); + + migrationBuilder.DropTable( + name: "Results"); + + migrationBuilder.DropTable( + name: "RiskFlags"); + + migrationBuilder.DropTable( + name: "ShowactApplications"); + + migrationBuilder.DropTable( + name: "SiteSettings"); + + migrationBuilder.DropTable( + name: "Sponsors"); + + migrationBuilder.DropTable( + name: "TeamMembers"); + + migrationBuilder.DropTable( + name: "TeamRolePermissions"); + + migrationBuilder.DropTable( + name: "UserSessions"); + + migrationBuilder.DropTable( + name: "VoteEntries"); + + migrationBuilder.DropTable( + name: "Candidates"); + + migrationBuilder.DropTable( + name: "VoteBallots"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "StreamerIdentities"); + + migrationBuilder.DropTable( + name: "Seasons"); + } + } +} diff --git a/Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.Designer.cs b/Backend/Migrations/20260629142744_SeedApplicationDefaults.Designer.cs similarity index 60% rename from Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.Designer.cs rename to Backend/Migrations/20260629142744_SeedApplicationDefaults.Designer.cs index dc8953d..3c24049 100644 --- a/Backend/Migrations/20260628092717_AddSessionIdleTimeoutSettings.Designer.cs +++ b/Backend/Migrations/20260629142744_SeedApplicationDefaults.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Backend.Migrations { [DbContext(typeof(AwardsDbContext))] - [Migration("20260628092717_AddSessionIdleTimeoutSettings")] - partial class AddSessionIdleTimeoutSettings + [Migration("20260629142744_SeedApplicationDefaults")] + partial class SeedApplicationDefaults { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -112,56 +112,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Candidate", b => @@ -215,6 +165,11 @@ namespace Backend.Migrations .HasMaxLength(120) .HasColumnType("character varying(120)"); + b.Property("NominationTally") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + b.Property("Platform") .IsRequired() .HasMaxLength(40) @@ -223,158 +178,18 @@ namespace Backend.Migrations b.Property("SeasonId") .HasColumnType("integer"); + b.Property("StreamerIdentityId") + .HasColumnType("integer"); + b.HasKey("Id"); b.HasIndex("CategoryId"); b.HasIndex("SeasonId"); - b.ToTable("Candidates"); + b.HasIndex("StreamerIdentityId"); - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); + b.ToTable("Candidates"); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -425,118 +240,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); }); modelBuilder.Entity("Backend.Domain.ClipSubmission", b => @@ -622,6 +325,9 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AvgViewers") + .HasColumnType("integer"); + b.Property("CandidateId") .HasColumnType("integer"); @@ -629,12 +335,39 @@ namespace Backend.Migrations .HasMaxLength(120) .HasColumnType("character varying(120)"); - b.Property("CategoryId") + b.Property("CategoryGroupName") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasDefaultValue(""); + + b.Property("CategoryId") .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("FollowersGained") + .HasColumnType("integer"); + + b.Property("HoursStreamed") + .HasColumnType("integer"); + + b.Property("HoursWatched") + .HasColumnType("integer"); + + b.Property("PeakViewers") + .HasColumnType("integer"); + + b.Property("ResolvedChannel") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("ResolvedPlatform") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + b.Property("ReviewNote") .HasMaxLength(500) .HasColumnType("character varying(500)"); @@ -658,42 +391,68 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("StreamerIdentityId") + .HasColumnType("integer"); + b.Property("SubmittedByTwitchId") .IsRequired() .HasMaxLength(120) .HasColumnType("character varying(120)"); + b.Property("SuggestedCategoryId") + .HasColumnType("integer"); + + b.Property("TrackerCheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackerStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasDefaultValue("pending"); + + b.Property("TrackingFlagsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + + b.Property("TrackingReviewNote") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TrackingReviewStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasDefaultValue("clear"); + + b.Property("TrackingReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackingReviewedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + b.HasKey("Id"); b.HasIndex("CandidateId"); b.HasIndex("CategoryId"); + b.HasIndex("StreamerIdentityId"); + + b.HasIndex("SuggestedCategoryId"); + b.HasIndex("SeasonId", "Status"); - b.ToTable("Nominations"); + b.HasIndex("SeasonId", "CategoryGroupName", "Status"); - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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" - }); + b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); + + b.ToTable("Nominations"); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -790,6 +549,11 @@ namespace Backend.Migrations b.Property("IsCurrent") .HasColumnType("boolean"); + b.Property("IsDemo") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(160) @@ -813,10 +577,11 @@ namespace Backend.Migrations b.Property("ShowStartsAt") .HasColumnType("time without time zone"); - b.Property("ShowStreamUrl") + b.Property("SubcategoryTemplatesJson") .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); b.Property("VotingEndsAt") .HasColumnType("date"); @@ -824,6 +589,19 @@ namespace Backend.Migrations b.Property("VotingStartsAt") .HasColumnType("date"); + b.Property("WinnersPublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WinnersPublishedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("WorkflowRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("Year") .HasColumnType("integer"); @@ -833,80 +611,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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.ShowactApplication", b => @@ -1008,6 +712,14 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AwardsSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AwardsSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("ClipAdminMenuVisible") .HasColumnType("boolean"); @@ -1151,6 +863,12 @@ namespace Backend.Migrations .HasMaxLength(240) .HasColumnType("character varying(240)"); + b.Property("ShowactApplicationEndsAt") + .HasColumnType("date"); + + b.Property("ShowactApplicationStartsAt") + .HasColumnType("date"); + b.Property("ShowactApplicationsEnabled") .ValueGeneratedOnAdd() .HasColumnType("boolean") @@ -1189,6 +907,80 @@ namespace Backend.Migrations .HasColumnType("boolean") .HasDefaultValue(true); + b.Property("StreamBannerCompletedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerCompletedEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerCompletedTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerLockedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerUseCompletedContent") + .HasColumnType("boolean"); + + b.Property("SubcategoriesSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubcategoriesSectionTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingReviewNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("TwitchAuthManagedByDatabase") .HasColumnType("boolean"); @@ -1212,6 +1004,11 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("ViewerStatsProviderBaseUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + b.Property("WorkflowRulesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -1221,58 +1018,6 @@ namespace Backend.Migrations b.HasKey("Id"); b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); }); modelBuilder.Entity("Backend.Domain.Sponsor", b => @@ -1330,6 +1075,49 @@ namespace Backend.Migrations b.ToTable("Sponsors"); }); + modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("LastResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Login") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("NormalizedKey") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("character varying(180)"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("ProfileUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedKey") + .IsUnique(); + + b.ToTable("StreamerIdentities"); + }); + modelBuilder.Entity("Backend.Domain.TeamMember", b => { b.Property("Id") @@ -1528,24 +1316,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); }); modelBuilder.Entity("Backend.Domain.VoteEntry", b => @@ -1574,36 +1344,6 @@ namespace Backend.Migrations b.HasIndex("CategoryId"); b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); }); modelBuilder.Entity("Backend.Domain.AwardResult", b => @@ -1647,9 +1387,15 @@ namespace Backend.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") + .WithMany("Candidates") + .HasForeignKey("StreamerIdentityId"); + b.Navigation("Category"); b.Navigation("Season"); + + b.Navigation("StreamerIdentity"); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -1670,6 +1416,12 @@ namespace Backend.Migrations .HasForeignKey("CandidateId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Backend.Domain.Season", null) + .WithMany() + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Candidate"); }); @@ -1682,8 +1434,7 @@ namespace Backend.Migrations b.HasOne("Backend.Domain.Category", "Category") .WithMany() .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .OnDelete(DeleteBehavior.SetNull); b.HasOne("Backend.Domain.Season", "Season") .WithMany() @@ -1691,11 +1442,24 @@ namespace Backend.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") + .WithMany("Nominations") + .HasForeignKey("StreamerIdentityId"); + + b.HasOne("Backend.Domain.Category", "SuggestedCategory") + .WithMany() + .HasForeignKey("SuggestedCategoryId") + .OnDelete(DeleteBehavior.SetNull); + b.Navigation("Candidate"); b.Navigation("Category"); b.Navigation("Season"); + + b.Navigation("StreamerIdentity"); + + b.Navigation("SuggestedCategory"); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -1779,6 +1543,13 @@ namespace Backend.Migrations b.Navigation("Results"); }); + modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => + { + b.Navigation("Candidates"); + + b.Navigation("Nominations"); + }); + modelBuilder.Entity("Backend.Domain.VoteBallot", b => { b.Navigation("Entries"); diff --git a/Backend/Migrations/20260629142744_SeedApplicationDefaults.cs b/Backend/Migrations/20260629142744_SeedApplicationDefaults.cs new file mode 100644 index 0000000..aa8e39a --- /dev/null +++ b/Backend/Migrations/20260629142744_SeedApplicationDefaults.cs @@ -0,0 +1,168 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backend.Migrations; + +public partial class SeedApplicationDefaults : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + INSERT INTO "SiteSettings" ( + "Id", + "HostDisplayName", + "HostTagline", + "NewsletterUrl", + "ShareXUrl", + "ShareDiscordUrl", + "PrivacyEmail", + "PrivacyPolicyContent", + "ImprintUrl", + "ImprintContent", + "ContactUrl", + "ContactContent", + "SponsorsUrl", + "SponsorsContent", + "ShowactsUrl", + "ShowactsContent", + "StreamBannerEyebrow", + "StreamBannerTitle", + "StreamBannerText", + "StreamBannerLiveButtonLabel", + "StreamBannerLiveButtonUrl", + "StreamBannerLockedButtonLabel", + "StreamBannerUseCompletedContent", + "StreamBannerCompletedEyebrow", + "StreamBannerCompletedTitle", + "StreamBannerCompletedText", + "StreamBannerCompletedButtonLabel", + "StreamBannerCompletedButtonUrl", + "AwardsSectionTitle", + "AwardsSectionDescription", + "SubcategoriesSectionTitle", + "SubcategoriesSectionDescription", + "SocialLinksJson", + "FaqJson", + "RiskRulesJson", + "WorkflowRulesJson", + "TrackingRulesJson", + "ViewerStatsProviderBaseUrl", + "TrackingReviewNotes", + "NominationLinkBlacklistJson", + "ClipSubmissionsEnabled", + "ClipReviewEnabled", + "ClipAdminMenuVisible", + "ClipSubmissionDisabledMessage", + "ShowactApplicationsEnabled", + "ShowactApplicationStartsAt", + "ShowactApplicationEndsAt", + "ShowactApplicationDisabledMessage", + "ShowactFormSchemaJson", + "SponsorsVisible", + "DemoLoginManagedByDatabase", + "DemoLoginEnabled", + "DemoLoginEmail", + "DemoLoginPasswordHash", + "DemoLoginPasswordSalt", + "DemoLoginTwitchUserId", + "DemoLoginDisplayName", + "TwitchAuthManagedByDatabase", + "TwitchClientId", + "TwitchClientSecret", + "TwitchRedirectUri", + "TwitchScope", + "SessionIdleTimeoutHours", + "MaintenanceModeEnabled", + "MaintenanceTitle", + "MaintenanceMessage" + ) + VALUES ( + 1, + 'Jayuhime', + 'VTuber Star Awards', + '', + 'https://x.com/intent/tweet', + 'https://discord.gg/', + 'privacy@example.invalid', + 'Diese lokale Baseline enthaelt nur nicht-geheime Platzhalter. Pflege produktive Datenschutztexte vor dem Go-live im Admin-Panel.', + '/impressum', + 'Impressumsdaten werden vor dem Go-live im Admin-Panel gepflegt.', + '/kontakt', + 'Fragen zu den Awards und Partnerschaften koennen ueber die offiziellen Kontaktkanaele gestellt werden.', + '/sponsoren', + 'Partner und Sponsoren der aktuellen Award-Season.', + '/showacts', + 'Showact-Bewerbungen fuer das Finale werden hier verwaltet.', + 'Finale live', + 'Die Award-Show startet im Livestream', + 'Wenn das Finale freigeschaltet ist, fuehrt dieser Button zum offiziellen Stream. Der Link wird zentral im Landingpage Stream-Banner gepflegt.', + 'Zum finalen Stream', + 'https://twitch.tv/jayuhime', + 'Stream noch nicht freigegeben', + TRUE, + 'Finale abgeschlossen', + 'Danke fuer diese Award-Nacht', + 'Die Gewinner:innen bleiben im Archiv sichtbar. Highlights und VODs koennen hier verlinkt werden.', + 'Highlights ansehen', + '', + 'Award-Kategorien', + 'Die Kategorien bilden Community-Leistung, Content-Qualitaet und besondere Momente der Season ab.', + 'Unterkategorien', + 'Unterkategorien helfen dem Team, Nominierungen sauber zu reviewen und faire Finalfelder zu bauen.', + '[{"label":"Twitch","url":"https://twitch.tv/jayuhime"},{"label":"Discord","url":"https://discord.gg/"},{"label":"X","url":"https://x.com/"}]', + '[{"question":"Wann startet die naechste Phase?","answer":"Die aktuellen Termine stehen auf der Landingpage und im Admin-Jahresplan."},{"question":"Wie werden Gewinner:innen bestimmt?","answer":"Nominierungen, Review und Voting laufen phasenweise. Das Team prueft Finalfelder vor der Veroeffentlichung."},{"question":"Wo pflege ich den finalen Stream-Link?","answer":"Der Stream-Link wird zentral im Landingpage Stream-Banner gepflegt."}]', + '[]', + '[]', + '[]', + 'https://twitchtracker.com/api', + 'Automatische TwitchTracker-Werte dienen als Review-Hilfe. Bei fehlenden oder unvollstaendigen Daten entscheidet das Team manuell.', + '[{"pattern":"localhost","reason":"Lokale Testlinks werden im Review blockiert."},{"pattern":"example.com","reason":"Platzhalterlinks sollen nicht als echte Nominierung freigegeben werden."}]', + TRUE, + TRUE, + TRUE, + 'Clip-Einreichungen sind aktuell geschlossen.', + TRUE, + DATE '2026-06-01', + DATE '2026-08-15', + 'Showact-Bewerbungen sind aktuell geschlossen.', + '[{"key":"performanceLength","label":"Geplante Laenge","type":"text","required":true},{"key":"contentRating","label":"Content-Hinweise","type":"textarea","required":false}]', + TRUE, + FALSE, + FALSE, + '', + '', + '', + 'jayuhime_admin', + 'Jayuhime Admin', + FALSE, + '', + '', + '', + 'user:read:email', + 3, + FALSE, + 'Sternenpause', + 'Die Award-Galaxie wird gerade liebevoll poliert. Schau gleich wieder vorbei.' + ) + ON CONFLICT ("Id") DO NOTHING; + + SELECT setval(pg_get_serial_sequence('"SiteSettings"', 'Id'), COALESCE((SELECT MAX("Id") FROM "SiteSettings"), 1)); + """ + ); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + DELETE FROM "SiteSettings" + WHERE "Id" = 1 + AND "HostDisplayName" = 'Jayuhime' + AND "DemoLoginPasswordHash" = '' + AND "TwitchClientSecret" = ''; + """ + ); + } +} diff --git a/Backend/Migrations/20260628091734_AddCategoryViewerRanges.Designer.cs b/Backend/Migrations/20260629142749_SeedRealisticDemoScenario.Designer.cs similarity index 60% rename from Backend/Migrations/20260628091734_AddCategoryViewerRanges.Designer.cs rename to Backend/Migrations/20260629142749_SeedRealisticDemoScenario.Designer.cs index e91e905..72458ef 100644 --- a/Backend/Migrations/20260628091734_AddCategoryViewerRanges.Designer.cs +++ b/Backend/Migrations/20260629142749_SeedRealisticDemoScenario.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Backend.Migrations { [DbContext(typeof(AwardsDbContext))] - [Migration("20260628091734_AddCategoryViewerRanges")] - partial class AddCategoryViewerRanges + [Migration("20260629142749_SeedRealisticDemoScenario")] + partial class SeedRealisticDemoScenario { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -112,56 +112,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Candidate", b => @@ -215,6 +165,11 @@ namespace Backend.Migrations .HasMaxLength(120) .HasColumnType("character varying(120)"); + b.Property("NominationTally") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + b.Property("Platform") .IsRequired() .HasMaxLength(40) @@ -223,158 +178,18 @@ namespace Backend.Migrations b.Property("SeasonId") .HasColumnType("integer"); + b.Property("StreamerIdentityId") + .HasColumnType("integer"); + b.HasKey("Id"); b.HasIndex("CategoryId"); b.HasIndex("SeasonId"); - b.ToTable("Candidates"); + b.HasIndex("StreamerIdentityId"); - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - Platform = "Twitch", - SeasonId = 4 - }); + b.ToTable("Candidates"); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -425,118 +240,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); }); modelBuilder.Entity("Backend.Domain.ClipSubmission", b => @@ -622,6 +325,9 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AvgViewers") + .HasColumnType("integer"); + b.Property("CandidateId") .HasColumnType("integer"); @@ -629,12 +335,39 @@ namespace Backend.Migrations .HasMaxLength(120) .HasColumnType("character varying(120)"); - b.Property("CategoryId") + b.Property("CategoryGroupName") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(80) + .HasColumnType("character varying(80)") + .HasDefaultValue(""); + + b.Property("CategoryId") .HasColumnType("integer"); b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("FollowersGained") + .HasColumnType("integer"); + + b.Property("HoursStreamed") + .HasColumnType("integer"); + + b.Property("HoursWatched") + .HasColumnType("integer"); + + b.Property("PeakViewers") + .HasColumnType("integer"); + + b.Property("ResolvedChannel") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("ResolvedPlatform") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + b.Property("ReviewNote") .HasMaxLength(500) .HasColumnType("character varying(500)"); @@ -658,42 +391,68 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("StreamerIdentityId") + .HasColumnType("integer"); + b.Property("SubmittedByTwitchId") .IsRequired() .HasMaxLength(120) .HasColumnType("character varying(120)"); + b.Property("SuggestedCategoryId") + .HasColumnType("integer"); + + b.Property("TrackerCheckedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackerStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasDefaultValue("pending"); + + b.Property("TrackingFlagsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + + b.Property("TrackingReviewNote") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TrackingReviewStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasDefaultValue("clear"); + + b.Property("TrackingReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackingReviewedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + b.HasKey("Id"); b.HasIndex("CandidateId"); b.HasIndex("CategoryId"); + b.HasIndex("StreamerIdentityId"); + + b.HasIndex("SuggestedCategoryId"); + b.HasIndex("SeasonId", "Status"); - b.ToTable("Nominations"); + b.HasIndex("SeasonId", "CategoryGroupName", "Status"); - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - 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 - { - Id = 2, - CandidateText = "Kurainu 3D Live", - 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" - }); + b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); + + b.ToTable("Nominations"); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -790,6 +549,11 @@ namespace Backend.Migrations b.Property("IsCurrent") .HasColumnType("boolean"); + b.Property("IsDemo") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(160) @@ -813,10 +577,11 @@ namespace Backend.Migrations b.Property("ShowStartsAt") .HasColumnType("time without time zone"); - b.Property("ShowStreamUrl") + b.Property("SubcategoryTemplatesJson") .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); b.Property("VotingEndsAt") .HasColumnType("date"); @@ -824,6 +589,19 @@ namespace Backend.Migrations b.Property("VotingStartsAt") .HasColumnType("date"); + b.Property("WinnersPublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WinnersPublishedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("WorkflowRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("Year") .HasColumnType("integer"); @@ -833,80 +611,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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.ShowactApplication", b => @@ -1008,6 +712,14 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AwardsSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AwardsSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("ClipAdminMenuVisible") .HasColumnType("boolean"); @@ -1133,6 +845,11 @@ namespace Backend.Migrations .IsRequired() .HasColumnType("text"); + b.Property("SessionIdleTimeoutHours") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(3); + b.Property("ShareDiscordUrl") .IsRequired() .HasColumnType("text"); @@ -1146,6 +863,12 @@ namespace Backend.Migrations .HasMaxLength(240) .HasColumnType("character varying(240)"); + b.Property("ShowactApplicationEndsAt") + .HasColumnType("date"); + + b.Property("ShowactApplicationStartsAt") + .HasColumnType("date"); + b.Property("ShowactApplicationsEnabled") .ValueGeneratedOnAdd() .HasColumnType("boolean") @@ -1184,6 +907,80 @@ namespace Backend.Migrations .HasColumnType("boolean") .HasDefaultValue(true); + b.Property("StreamBannerCompletedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerCompletedEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerCompletedTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerLockedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerUseCompletedContent") + .HasColumnType("boolean"); + + b.Property("SubcategoriesSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubcategoriesSectionTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingReviewNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("TwitchAuthManagedByDatabase") .HasColumnType("boolean"); @@ -1207,6 +1004,11 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("ViewerStatsProviderBaseUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + b.Property("WorkflowRulesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -1216,57 +1018,6 @@ namespace Backend.Migrations b.HasKey("Id"); b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); }); modelBuilder.Entity("Backend.Domain.Sponsor", b => @@ -1324,6 +1075,49 @@ namespace Backend.Migrations b.ToTable("Sponsors"); }); + modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("LastResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Login") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("NormalizedKey") + .IsRequired() + .HasMaxLength(180) + .HasColumnType("character varying(180)"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("ProfileUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedKey") + .IsUnique(); + + b.ToTable("StreamerIdentities"); + }); + modelBuilder.Entity("Backend.Domain.TeamMember", b => { b.Property("Id") @@ -1522,24 +1316,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); }); modelBuilder.Entity("Backend.Domain.VoteEntry", b => @@ -1568,36 +1344,6 @@ namespace Backend.Migrations b.HasIndex("CategoryId"); b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); }); modelBuilder.Entity("Backend.Domain.AwardResult", b => @@ -1641,9 +1387,15 @@ namespace Backend.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") + .WithMany("Candidates") + .HasForeignKey("StreamerIdentityId"); + b.Navigation("Category"); b.Navigation("Season"); + + b.Navigation("StreamerIdentity"); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -1664,6 +1416,12 @@ namespace Backend.Migrations .HasForeignKey("CandidateId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Backend.Domain.Season", null) + .WithMany() + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Candidate"); }); @@ -1676,8 +1434,7 @@ namespace Backend.Migrations b.HasOne("Backend.Domain.Category", "Category") .WithMany() .HasForeignKey("CategoryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .OnDelete(DeleteBehavior.SetNull); b.HasOne("Backend.Domain.Season", "Season") .WithMany() @@ -1685,11 +1442,24 @@ namespace Backend.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("Backend.Domain.StreamerIdentity", "StreamerIdentity") + .WithMany("Nominations") + .HasForeignKey("StreamerIdentityId"); + + b.HasOne("Backend.Domain.Category", "SuggestedCategory") + .WithMany() + .HasForeignKey("SuggestedCategoryId") + .OnDelete(DeleteBehavior.SetNull); + b.Navigation("Candidate"); b.Navigation("Category"); b.Navigation("Season"); + + b.Navigation("StreamerIdentity"); + + b.Navigation("SuggestedCategory"); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -1773,6 +1543,13 @@ namespace Backend.Migrations b.Navigation("Results"); }); + modelBuilder.Entity("Backend.Domain.StreamerIdentity", b => + { + b.Navigation("Candidates"); + + b.Navigation("Nominations"); + }); + modelBuilder.Entity("Backend.Domain.VoteBallot", b => { b.Navigation("Entries"); diff --git a/Backend/Migrations/20260629142749_SeedRealisticDemoScenario.cs b/Backend/Migrations/20260629142749_SeedRealisticDemoScenario.cs new file mode 100644 index 0000000..2d0b919 --- /dev/null +++ b/Backend/Migrations/20260629142749_SeedRealisticDemoScenario.cs @@ -0,0 +1,257 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backend.Migrations; + +public partial class SeedRealisticDemoScenario : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + INSERT INTO "Seasons" ( + "Id", "Year", "Name", "IsDemo", "IsCurrent", "IsCommunityOnly", "CurrentPhase", + "NominationStartsAt", "NominationEndsAt", "VotingStartsAt", "VotingEndsAt", + "ReviewStartsAt", "ReviewEndsAt", "ShowDate", "ShowStartsAt", + "WinnersPublishedAt", "WinnersPublishedByTwitchId", "SubcategoryTemplatesJson", "WorkflowRulesJson" + ) + VALUES + (1000, 2025, 'VTuber Star Awards 2025 Demo Archiv', TRUE, FALSE, FALSE, 'Archiv', + DATE '2025-04-01', DATE '2025-04-28', DATE '2025-05-10', DATE '2025-05-24', + DATE '2025-04-29', DATE '2025-05-09', DATE '2025-06-21', TIME '20:00', + TIMESTAMPTZ '2025-06-22 10:00:00+00', 'demo_owner', '[]', '[]'), + (1001, 2026, 'VTuber Star Awards 2026 Demo', TRUE, TRUE, FALSE, 'Voting', + DATE '2026-05-18', DATE '2026-06-16', DATE '2026-06-24', DATE '2026-07-19', + DATE '2026-06-17', DATE '2026-06-23', DATE '2026-08-08', TIME '20:00', + NULL, NULL, '[]', '[]'); + + INSERT INTO "Categories" ( + "Id", "SeasonId", "GroupName", "Name", "Slug", "Description", "SortOrder", "MaxNomineesPerUser", "ViewerRangeMin", "ViewerRangeMax" + ) + VALUES + (1101, 1001, 'Spotlight', 'Rising Star', 'rising-star', 'Neue oder stark gewachsene Creator:innen mit klarer Entwicklung.', 10, 2, 0, 75), + (1102, 1001, 'Spotlight', 'Community Heart', 'community-heart', 'Creator:innen, deren Community besonders sichtbar und einladend ist.', 20, 2, 0, 200), + (1103, 1001, 'Spotlight', 'Breakout Moment', 'breakout-moment', 'Ein einzelner Moment, Clip oder Stream, der die Season gepraegt hat.', 30, 2, 0, NULL), + (1104, 1001, 'Content', 'Best Variety Stream', 'best-variety-stream', 'Abwechslungsreiche Streams mit sicherem roten Faden.', 40, 2, 30, 350), + (1105, 1001, 'Content', 'Best Gaming Stream', 'best-gaming-stream', 'Gaming-Streams mit starker Moderation und guter Dramaturgie.', 50, 2, 25, 400), + (1106, 1001, 'Content', 'Best Music Performance', 'best-music-performance', 'Live-Gesang, Instrumente oder Musikproduktion im Stream.', 60, 2, 0, 250), + (1107, 1001, 'Content', 'Best Art Stream', 'best-art-stream', 'Art-, Design- oder Rigging-Streams mit nachvollziehbarem Prozess.', 70, 2, 0, 250), + (1108, 1001, 'Content', 'Best Lore Project', 'best-lore-project', 'Storytelling, Lore-Events oder immersive Formatideen.', 80, 2, 0, 300), + (1109, 1001, 'Engagement', 'Best Chat Interaction', 'best-chat-interaction', 'Besonders gute Einbindung von Chat und Community.', 90, 2, 20, 500), + (1110, 1001, 'Engagement', 'Best Collab Energy', 'best-collab-energy', 'Kollaborationen, die alle Beteiligten staerker gemacht haben.', 100, 2, 30, 650), + (1111, 1001, 'Engagement', 'Best Community Event', 'best-community-event', 'Community-Events mit guter Planung und nachhaltiger Wirkung.', 110, 2, 25, 750), + (1112, 1001, 'Production', 'Best Stream Design', 'best-stream-design', 'Overlay, Szenen, Alerts und visuelle Identitaet.', 120, 2, 0, 400), + (1113, 1001, 'Production', 'Best Original Clip', 'best-original-clip', 'Einreichbare Clips mit starkem Timing oder besonderem Moment.', 130, 2, 0, NULL), + (1114, 1001, 'Production', 'Best Technical Glow-Up', 'best-technical-glow-up', 'Messbare Verbesserungen bei Audio, Video, Licht oder Setup.', 140, 2, 0, 300), + (1201, 1000, 'Archiv Spotlight', 'Archiv Rising Star', 'archiv-rising-star', 'Archivkategorie fuer Gewinner-Showcase.', 10, 2, 0, 75), + (1202, 1000, 'Archiv Content', 'Archiv Variety', 'archiv-variety', 'Archivkategorie fuer Gewinner-Showcase.', 20, 2, 0, 350), + (1203, 1000, 'Archiv Content', 'Archiv Music', 'archiv-music', 'Archivkategorie fuer Gewinner-Showcase.', 30, 2, 0, 250), + (1204, 1000, 'Archiv Engagement', 'Archiv Community', 'archiv-community', 'Archivkategorie fuer Gewinner-Showcase.', 40, 2, 0, 500), + (1205, 1000, 'Archiv Production', 'Archiv Stream Design', 'archiv-stream-design', 'Archivkategorie fuer Gewinner-Showcase.', 50, 2, 0, 400), + (1206, 1000, 'Archiv Moment', 'Archiv Clip Moment', 'archiv-clip-moment', 'Archivkategorie fuer Gewinner-Showcase.', 60, 2, 0, NULL); + + INSERT INTO "StreamerIdentities" ("Id", "Platform", "Login", "NormalizedKey", "DisplayName", "ProfileUrl", "LastResolvedAt") + VALUES + (3001, 'Twitch', 'aki_lumina', 'twitch:aki_lumina', 'Aki Lumina', 'https://twitch.tv/aki_lumina', TIMESTAMPTZ '2026-06-27 12:00:00+00'), + (3002, 'Twitch', 'mira_orbit', 'twitch:mira_orbit', 'Mira Orbit', 'https://twitch.tv/mira_orbit', TIMESTAMPTZ '2026-06-27 12:02:00+00'), + (3003, 'Twitch', 'nova_nym', 'twitch:nova_nym', 'Nova Nym', 'https://twitch.tv/nova_nym', TIMESTAMPTZ '2026-06-27 12:04:00+00'), + (3004, 'Twitch', 'luna_koi', 'twitch:luna_koi', 'Luna Koi', 'https://twitch.tv/luna_koi', TIMESTAMPTZ '2026-06-27 12:06:00+00'), + (3005, 'Twitch', 'runa_bits', 'twitch:runa_bits', 'Runa Bits', 'https://twitch.tv/runa_bits', TIMESTAMPTZ '2026-06-27 12:08:00+00'), + (3006, 'Twitch', 'sora_slate', 'twitch:sora_slate', 'Sora Slate', 'https://twitch.tv/sora_slate', TIMESTAMPTZ '2026-06-27 12:10:00+00'), + (3007, 'Twitch', 'ember_vail', 'twitch:ember_vail', 'Ember Vail', 'https://twitch.tv/ember_vail', TIMESTAMPTZ '2026-06-27 12:12:00+00'), + (3008, 'Twitch', 'niko_noct', 'twitch:niko_noct', 'Niko Noct', 'https://twitch.tv/niko_noct', TIMESTAMPTZ '2026-06-27 12:14:00+00'), + (3009, 'Twitch', 'pixel_poppy', 'twitch:pixel_poppy', 'Pixel Poppy', 'https://twitch.tv/pixel_poppy', TIMESTAMPTZ '2026-06-27 12:16:00+00'), + (3010, 'Twitch', 'hana_hertz', 'twitch:hana_hertz', 'Hana Hertz', 'https://twitch.tv/hana_hertz', TIMESTAMPTZ '2026-06-27 12:18:00+00'); + + INSERT INTO "Candidates" ( + "Id", "SeasonId", "CategoryId", "StreamerIdentityId", "DisplayName", "ChannelSlug", "Platform", + "NominationTally", "AcceptanceStatus", "AcceptanceNote", "ClipCompilationUrl", "ClipCompilationTitle", "ClipCompilationPlatform", "ClipEmbedStatus" + ) + VALUES + (2001, 1001, 1101, 3001, 'Aki Lumina', 'aki_lumina', 'Twitch', 9, 'approved', 'Bestaetigt, kleiner Kanal mit starkem Wachstum.', 'https://youtu.be/demo-aki-rise', 'Aki Lumina Rising Star Reel', 'YouTube', 'available'), + (2002, 1001, 1101, 3002, 'Mira Orbit', 'mira_orbit', 'Twitch', 7, 'approved', 'Bestaetigt.', NULL, NULL, NULL, 'unchecked'), + (2003, 1001, 1101, 3003, 'Nova Nym', 'nova_nym', 'Twitch', 5, 'pending', 'Wartet auf finalen Clip.', NULL, NULL, NULL, 'unchecked'), + (2004, 1001, 1102, 3004, 'Luna Koi', 'luna_koi', 'Twitch', 11, 'approved', 'Community-Belege im Review notiert.', NULL, NULL, NULL, 'unchecked'), + (2005, 1001, 1102, 3005, 'Runa Bits', 'runa_bits', 'Twitch', 8, 'approved', 'Starke Discord-Aktion.', NULL, NULL, NULL, 'unchecked'), + (2006, 1001, 1102, 3006, 'Sora Slate', 'sora_slate', 'Twitch', 4, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2007, 1001, 1103, 3007, 'Ember Vail', 'ember_vail', 'Twitch', 10, 'approved', 'Clip-Quelle vorhanden.', 'https://clips.twitch.tv/demo-ember-moment', 'Ember Vail Breakout Clip', 'Twitch', 'available'), + (2008, 1001, 1103, 3008, 'Niko Noct', 'niko_noct', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2009, 1001, 1103, 3009, 'Pixel Poppy', 'pixel_poppy', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2010, 1001, 1104, 3010, 'Hana Hertz', 'hana_hertz', 'Twitch', 12, 'approved', 'Variety-Plan sauber dokumentiert.', 'https://youtu.be/demo-hana-variety', 'Hana Hertz Variety Reel', 'YouTube', 'available'), + (2011, 1001, 1104, NULL, 'Kira Comet', 'kira_comet', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2012, 1001, 1104, NULL, 'Mochi Vale', 'mochi_vale', 'Twitch', 7, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2013, 1001, 1105, NULL, 'Taro Tactics', 'taro_tactics', 'Twitch', 10, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2014, 1001, 1105, NULL, 'Yuna Quest', 'yuna_quest', 'Twitch', 7, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2015, 1001, 1105, NULL, 'Rin Replay', 'rin_replay', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2016, 1001, 1106, NULL, 'Melo Moon', 'melo_moon', 'Twitch', 9, 'approved', 'Live-Set pruefbar.', 'https://youtu.be/demo-melo-live', 'Melo Moon Live Set', 'YouTube', 'available'), + (2017, 1001, 1106, NULL, 'Vivi Verse', 'vivi_verse', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2018, 1001, 1106, NULL, 'Echo Rill', 'echo_rill', 'Twitch', 4, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2019, 1001, 1107, NULL, 'Iris Ink', 'iris_ink', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2020, 1001, 1107, NULL, 'Pia Palette', 'pia_palette', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2021, 1001, 1107, NULL, 'Theo Thimble', 'theo_thimble', 'Twitch', 3, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2022, 1001, 1108, NULL, 'Nyra Novel', 'nyra_novel', 'Twitch', 9, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2023, 1001, 1108, NULL, 'Kai Myth', 'kai_myth', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2024, 1001, 1108, NULL, 'Mina Maze', 'mina_maze', 'Twitch', 4, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2025, 1001, 1109, NULL, 'Bibi Beacon', 'bibi_beacon', 'Twitch', 12, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2026, 1001, 1109, NULL, 'Ori Opal', 'ori_opal', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2027, 1001, 1109, NULL, 'Faye Flux', 'faye_flux', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2028, 1001, 1110, NULL, 'Riku Relay', 'riku_relay', 'Twitch', 10, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2029, 1001, 1110, NULL, 'Nami Node', 'nami_node', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2030, 1001, 1110, NULL, 'Sachi Spark', 'sachi_spark', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2031, 1001, 1111, NULL, 'Cleo Campfire', 'cleo_campfire', 'Twitch', 11, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2032, 1001, 1111, NULL, 'Juno Jamboree', 'juno_jamboree', 'Twitch', 9, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2033, 1001, 1111, NULL, 'Mika Meetup', 'mika_meetup', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2034, 1001, 1112, NULL, 'Neon Nori', 'neon_nori', 'Twitch', 9, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2035, 1001, 1112, NULL, 'Slate Sen', 'slate_sen', 'Twitch', 7, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2036, 1001, 1112, NULL, 'Momo Motion', 'momo_motion', 'Twitch', 4, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2037, 1001, 1113, NULL, 'Lio Laughs', 'lio_laughs', 'Twitch', 10, 'approved', 'Clip bereits im Clip-Review.', 'https://clips.twitch.tv/demo-lio-laugh', 'Lio Laughs Original Clip', 'Twitch', 'available'), + (2038, 1001, 1113, NULL, 'Puck Prism', 'puck_prism', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2039, 1001, 1113, NULL, 'Tessa Toast', 'tessa_toast', 'Twitch', 5, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2040, 1001, 1114, NULL, 'Vera Volt', 'vera_volt', 'Twitch', 9, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2041, 1001, 1114, NULL, 'Maki Mixer', 'maki_mixer', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2042, 1001, 1114, NULL, 'Yori Yield', 'yori_yield', 'Twitch', 4, 'pending', NULL, NULL, NULL, NULL, 'unchecked'), + (2101, 1000, 1201, NULL, 'Archiv Aster', 'archiv_aster', 'Twitch', 14, 'approved', NULL, 'https://youtu.be/demo-archiv-aster', 'Archiv Aster Winner Reel', 'YouTube', 'available'), + (2102, 1000, 1201, NULL, 'Archiv Beryl', 'archiv_beryl', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2103, 1000, 1201, NULL, 'Archiv Coda', 'archiv_coda', 'Twitch', 5, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2104, 1000, 1202, NULL, 'Archiv Drift', 'archiv_drift', 'Twitch', 16, 'approved', NULL, 'https://youtu.be/demo-archiv-drift', 'Archiv Drift Variety Reel', 'YouTube', 'available'), + (2105, 1000, 1202, NULL, 'Archiv Elara', 'archiv_elara', 'Twitch', 9, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2106, 1000, 1202, NULL, 'Archiv Finch', 'archiv_finch', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2107, 1000, 1203, NULL, 'Archiv Lyra', 'archiv_lyra', 'Twitch', 12, 'approved', NULL, 'https://youtu.be/demo-archiv-lyra', 'Archiv Lyra Music Reel', 'YouTube', 'available'), + (2108, 1000, 1203, NULL, 'Archiv Muse', 'archiv_muse', 'Twitch', 7, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2109, 1000, 1203, NULL, 'Archiv Nia', 'archiv_nia', 'Twitch', 5, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2110, 1000, 1204, NULL, 'Archiv Poppy', 'archiv_poppy', 'Twitch', 13, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2111, 1000, 1204, NULL, 'Archiv Quartz', 'archiv_quartz', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2112, 1000, 1204, NULL, 'Archiv Rune', 'archiv_rune', 'Twitch', 5, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2113, 1000, 1205, NULL, 'Archiv Sol', 'archiv_sol', 'Twitch', 11, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2114, 1000, 1205, NULL, 'Archiv Tide', 'archiv_tide', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2115, 1000, 1205, NULL, 'Archiv Uma', 'archiv_uma', 'Twitch', 5, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2116, 1000, 1206, NULL, 'Archiv Vesper', 'archiv_vesper', 'Twitch', 15, 'approved', NULL, 'https://clips.twitch.tv/demo-archiv-vesper', 'Archiv Vesper Clip Moment', 'Twitch', 'available'), + (2117, 1000, 1206, NULL, 'Archiv Wren', 'archiv_wren', 'Twitch', 8, 'approved', NULL, NULL, NULL, NULL, 'unchecked'), + (2118, 1000, 1206, NULL, 'Archiv Yuki', 'archiv_yuki', 'Twitch', 6, 'approved', NULL, NULL, NULL, NULL, 'unchecked'); + + INSERT INTO "Nominations" ( + "Id", "SeasonId", "CategoryId", "CategoryGroupName", "SubmittedByTwitchId", "CandidateId", "StreamerIdentityId", "SuggestedCategoryId", + "CandidateText", "StreamUrl", "ResolvedChannel", "ResolvedPlatform", "AvgViewers", "HoursStreamed", "HoursWatched", "PeakViewers", "FollowersGained", + "TrackerStatus", "TrackerCheckedAt", "TrackingReviewStatus", "TrackingFlagsJson", "TrackingReviewNote", "TrackingReviewedByTwitchId", "TrackingReviewedAt", + "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedAt", "ReviewedAt" + ) + VALUES + (4001, 1001, 1101, 'Spotlight', 'viewer_1001', 2001, 3001, NULL, 'Aki Lumina', 'https://twitch.tv/aki_lumina', 'aki_lumina', 'Twitch', 42, 63, 6800, 118, 730, 'resolved', TIMESTAMPTZ '2026-06-18 09:20:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:10:00+00', 'approved', 'Wachstum und Streamplan passen.', 'reviewer_demo', TIMESTAMPTZ '2026-06-02 18:30:00+00', TIMESTAMPTZ '2026-06-18 10:10:00+00'), + (4002, 1001, 1101, 'Spotlight', 'viewer_1002', 2002, 3002, NULL, 'Mira Orbit', 'https://twitch.tv/mira_orbit', 'mira_orbit', 'Twitch', 38, 58, 5900, 96, 510, 'resolved', TIMESTAMPTZ '2026-06-18 09:24:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:15:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-03 20:15:00+00', TIMESTAMPTZ '2026-06-18 10:15:00+00'), + (4003, 1001, 1103, 'Spotlight', 'viewer_1003', 2007, 3007, NULL, 'Ember Vail', 'https://twitch.tv/ember_vail', 'ember_vail', 'Twitch', 116, 44, 9100, 420, 1200, 'resolved', TIMESTAMPTZ '2026-06-18 09:30:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:22:00+00', 'approved', 'Breakout-Clip verifiziert.', 'reviewer_demo', TIMESTAMPTZ '2026-06-04 14:45:00+00', TIMESTAMPTZ '2026-06-18 10:22:00+00'), + (4004, 1001, 1104, 'Content', 'viewer_1004', 2010, 3010, NULL, 'Hana Hertz', 'https://twitch.tv/hana_hertz', 'hana_hertz', 'Twitch', 184, 72, 22100, 360, 840, 'resolved', TIMESTAMPTZ '2026-06-18 09:40:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:30:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-05 21:05:00+00', TIMESTAMPTZ '2026-06-18 10:30:00+00'), + (4005, 1001, 1105, 'Content', 'viewer_1005', 2013, NULL, NULL, 'Taro Tactics', 'https://twitch.tv/taro_tactics', 'taro_tactics', 'Twitch', 153, 81, 19800, 310, 620, 'resolved', TIMESTAMPTZ '2026-06-18 09:45:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:36:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-06 19:25:00+00', TIMESTAMPTZ '2026-06-18 10:36:00+00'), + (4006, 1001, 1106, 'Content', 'viewer_1006', 2016, NULL, NULL, 'Melo Moon', 'https://twitch.tv/melo_moon', 'melo_moon', 'Twitch', 88, 35, 7400, 210, 430, 'resolved', TIMESTAMPTZ '2026-06-18 09:48:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:41:00+00', 'approved', 'Live-Musik-Set pruefbar.', 'reviewer_demo', TIMESTAMPTZ '2026-06-07 17:55:00+00', TIMESTAMPTZ '2026-06-18 10:41:00+00'), + (4007, 1001, 1108, 'Content', 'viewer_1007', 2022, NULL, NULL, 'Nyra Novel', 'https://twitch.tv/nyra_novel', 'nyra_novel', 'Twitch', NULL, NULL, NULL, NULL, NULL, 'no_data', TIMESTAMPTZ '2026-06-18 09:52:00+00', 'needs_review', '[{"key":"no_tracker_data","label":"Keine Tracker-Daten","severity":"medium","description":"TwitchTracker hat keinen belastbaren Summary-Wert geliefert.","requiresManualReview":true,"blocksApproval":false,"adminNoteRequiredOnOverride":false}]', 'Tracker leer, manuelle Lore-Pruefung noetig.', 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:00:00+00', 'pending', 'Lore-Dokumentation nachfordern.', NULL, TIMESTAMPTZ '2026-06-08 18:10:00+00', NULL), + (4008, 1001, 1109, 'Engagement', 'viewer_1008', 2025, NULL, NULL, 'Bibi Beacon', 'https://twitch.tv/bibi_beacon', 'bibi_beacon', 'Twitch', 208, 65, 31800, 530, 920, 'resolved', TIMESTAMPTZ '2026-06-18 09:58:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:06:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-09 16:20:00+00', TIMESTAMPTZ '2026-06-18 11:06:00+00'), + (4009, 1001, 1110, 'Engagement', 'viewer_1009', 2028, NULL, NULL, 'Riku Relay', 'https://twitch.tv/riku_relay', 'riku_relay', 'Twitch', 177, 54, 24200, 440, 610, 'resolved', TIMESTAMPTZ '2026-06-18 10:02:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:12:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-10 20:40:00+00', TIMESTAMPTZ '2026-06-18 11:12:00+00'), + (4010, 1001, 1111, 'Engagement', 'viewer_1010', 2031, NULL, NULL, 'Cleo Campfire', 'https://twitch.tv/cleo_campfire', 'cleo_campfire', 'Twitch', 232, 49, 28700, 620, 1100, 'resolved', TIMESTAMPTZ '2026-06-18 10:08:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:18:00+00', 'approved', 'Community-Event mit Planungsdoku.', 'reviewer_demo', TIMESTAMPTZ '2026-06-11 15:50:00+00', TIMESTAMPTZ '2026-06-18 11:18:00+00'), + (4011, 1001, 1112, 'Production', 'viewer_1011', 2034, NULL, NULL, 'Neon Nori', 'https://twitch.tv/neon_nori', 'neon_nori', 'Twitch', 121, 37, 11800, 250, 340, 'resolved', TIMESTAMPTZ '2026-06-18 10:14:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:24:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-12 19:05:00+00', TIMESTAMPTZ '2026-06-18 11:24:00+00'), + (4012, 1001, 1113, 'Production', 'viewer_1012', 2037, NULL, NULL, 'Lio Laughs', 'https://twitch.tv/lio_laughs', 'lio_laughs', 'Twitch', 96, 26, 6500, 340, 270, 'resolved', TIMESTAMPTZ '2026-06-18 10:18:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:30:00+00', 'approved', 'Clip pruefbar.', 'reviewer_demo', TIMESTAMPTZ '2026-06-13 22:15:00+00', TIMESTAMPTZ '2026-06-18 11:30:00+00'), + (4013, 1001, 1114, 'Production', 'viewer_1013', 2040, NULL, NULL, 'Vera Volt', 'https://twitch.tv/vera_volt', 'vera_volt', 'Twitch', 74, 41, 8200, 190, 390, 'resolved', TIMESTAMPTZ '2026-06-18 10:22:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:36:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-14 13:35:00+00', TIMESTAMPTZ '2026-06-18 11:36:00+00'), + (4014, 1001, 1102, 'Spotlight', 'viewer_1014', 2004, 3004, NULL, 'Luna Koi', 'https://twitch.tv/luna_koi', 'luna_koi', 'Twitch', 62, 46, 9100, 155, 510, 'resolved', TIMESTAMPTZ '2026-06-18 10:28:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:42:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-15 18:45:00+00', TIMESTAMPTZ '2026-06-18 11:42:00+00'), + (4015, 1001, 1107, 'Content', 'viewer_1015', 2019, NULL, NULL, 'Iris Ink', 'https://twitch.tv/iris_ink', 'iris_ink', 'Twitch', 54, 31, 5200, 130, 260, 'resolved', TIMESTAMPTZ '2026-06-18 10:31:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:48:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-15 20:10:00+00', TIMESTAMPTZ '2026-06-18 11:48:00+00'), + (4016, 1001, 1105, 'Content', 'viewer_1016', 2015, NULL, NULL, 'Rin Replay', 'https://youtube.com/@rin_replay', 'rin_replay', 'YouTube', NULL, NULL, NULL, NULL, NULL, 'unsupported_platform', TIMESTAMPTZ '2026-06-18 10:36:00+00', 'needs_review', '[{"key":"unsupported_platform","label":"Plattform nicht unterstuetzt","severity":"medium","description":"Die Nominierung zeigt auf eine Plattform ohne automatische TwitchTracker-Daten.","requiresManualReview":true,"blocksApproval":false,"adminNoteRequiredOnOverride":false}]', 'YouTube-Link muss manuell geprueft werden.', NULL, NULL, 'pending', 'Stream-Link auf Twitch anfragen.', NULL, TIMESTAMPTZ '2026-06-16 11:25:00+00', NULL); + + INSERT INTO "VoteBallots" ("Id", "SeasonId", "SubmittedByTwitchId", "Status", "SubmittedAt") + VALUES + (5001, 1001, 'vote_user_001', 'submitted', TIMESTAMPTZ '2026-06-25 18:10:00+00'), + (5002, 1001, 'vote_user_002', 'submitted', TIMESTAMPTZ '2026-06-25 18:16:00+00'), + (5003, 1001, 'vote_user_003', 'submitted', TIMESTAMPTZ '2026-06-25 19:05:00+00'), + (5004, 1001, 'vote_user_004', 'submitted', TIMESTAMPTZ '2026-06-26 12:45:00+00'), + (5005, 1001, 'vote_user_005', 'submitted', TIMESTAMPTZ '2026-06-26 20:30:00+00'), + (5006, 1001, 'vote_user_006', 'submitted', TIMESTAMPTZ '2026-06-27 09:20:00+00'), + (5007, 1001, 'vote_user_007', 'draft', TIMESTAMPTZ '2026-06-27 14:15:00+00'), + (5008, 1001, 'vote_user_008', 'submitted', TIMESTAMPTZ '2026-06-28 21:05:00+00'); + + INSERT INTO "VoteEntries" ("Id", "BallotId", "CategoryId", "CandidateId") + VALUES + (5101, 5001, 1101, 2001), (5102, 5001, 1104, 2010), (5103, 5001, 1109, 2025), (5104, 5001, 1113, 2037), + (5105, 5002, 1101, 2002), (5106, 5002, 1105, 2013), (5107, 5002, 1110, 2028), (5108, 5002, 1114, 2040), + (5109, 5003, 1102, 2004), (5110, 5003, 1106, 2016), (5111, 5003, 1111, 2031), (5112, 5003, 1112, 2034), + (5113, 5004, 1103, 2007), (5114, 5004, 1104, 2011), (5115, 5004, 1108, 2022), (5116, 5004, 1113, 2038), + (5117, 5005, 1101, 2001), (5118, 5005, 1105, 2014), (5119, 5005, 1109, 2026), (5120, 5005, 1114, 2041), + (5121, 5006, 1102, 2005), (5122, 5006, 1106, 2016), (5123, 5006, 1110, 2029), (5124, 5006, 1112, 2035), + (5125, 5007, 1103, 2008), (5126, 5007, 1107, 2019), + (5127, 5008, 1101, 2001), (5128, 5008, 1104, 2010), (5129, 5008, 1111, 2032), (5130, 5008, 1113, 2037); + + INSERT INTO "Results" ("Id", "SeasonId", "CategoryId", "CandidateId", "CategoryName") + VALUES + (6001, 1000, 1201, 2101, 'Archiv Rising Star'), + (6002, 1000, 1202, 2104, 'Archiv Variety'), + (6003, 1000, 1203, 2107, 'Archiv Music'), + (6004, 1000, 1204, 2110, 'Archiv Community'), + (6005, 1000, 1205, 2113, 'Archiv Stream Design'), + (6006, 1000, 1206, 2116, 'Archiv Clip Moment'); + + INSERT INTO "ClipSubmissions" ( + "Id", "SeasonId", "CategoryId", "CandidateId", "SubmittedByTwitchId", "ClipUrl", "Title", "Creator", "Platform", + "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedFromIp", "CreatedAt", "ReviewedAt" + ) + VALUES + (7001, 1001, 1103, 2007, 'viewer_2001', 'https://clips.twitch.tv/demo-ember-moment', 'Ember findet den Plot Twist', 'viewer_2001', 'Twitch', 'approved', 'Ton und Kontext passen.', 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-20 18:12:00+00', TIMESTAMPTZ '2026-06-21 09:00:00+00'), + (7002, 1001, 1113, 2037, 'viewer_2002', 'https://clips.twitch.tv/demo-lio-laugh', 'Lio verliert komplett die Fassung', 'viewer_2002', 'Twitch', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-20 20:25:00+00', TIMESTAMPTZ '2026-06-21 09:08:00+00'), + (7003, 1001, 1106, 2016, 'viewer_2003', 'https://youtu.be/demo-melo-live', 'Melo Moon acoustic bridge', 'viewer_2003', 'YouTube', 'pending', 'YouTube-Timestamp noch pruefen.', NULL, '127.0.0.1', TIMESTAMPTZ '2026-06-21 11:40:00+00', NULL), + (7004, 1001, 1104, 2010, 'viewer_2004', 'https://youtu.be/demo-hana-variety', 'Hana improvised den Chat-Run', 'viewer_2004', 'YouTube', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-21 15:10:00+00', TIMESTAMPTZ '2026-06-22 08:45:00+00'), + (7005, 1001, 1114, 2040, 'viewer_2005', 'https://clips.twitch.tv/demo-vera-setup', 'Vera erklaert ihr neues Setup', 'viewer_2005', 'Twitch', 'pending', NULL, NULL, '127.0.0.1', TIMESTAMPTZ '2026-06-22 19:55:00+00', NULL), + (7006, 1001, 1109, 2025, 'viewer_2006', 'https://clips.twitch.tv/demo-bibi-chat', 'Bibi laesst Chat entscheiden', 'viewer_2006', 'Twitch', 'rejected', 'Zu wenig Kontext fuer Award-Clip.', 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-23 12:30:00+00', TIMESTAMPTZ '2026-06-23 14:05:00+00'), + (7007, 1000, 1206, 2116, 'archiv_viewer_1', 'https://clips.twitch.tv/demo-archiv-vesper', 'Archiv Vesper Finale Clip', 'archiv_viewer_1', 'Twitch', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2025-06-10 19:00:00+00', TIMESTAMPTZ '2025-06-11 09:00:00+00'); + + INSERT INTO "ShowactApplications" ( + "Id", "SeasonId", "ArtistName", "ContactEmail", "ContactDiscord", "PlatformUrl", "PerformanceType", "Description", + "TechnicalNotes", "ReferenceUrl", "FieldResponsesJson", "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedFromIp", "UserAgent", "CreatedAt", "ReviewedAt" + ) + VALUES + (8001, 1001, 'Melo Moon', 'melo@example.invalid', 'melo_moon', 'https://twitch.tv/melo_moon', 'Live-Gesang', 'Akustisches Opening-Medley mit zwei kurzen Songs.', 'Benoetigt Instrumentalspur und Monitoring.', 'https://youtu.be/demo-melo-live', '{"performanceLength":"6 Minuten","contentRating":"family friendly"}', 'approved', 'Passt als Opener.', 'show_reviewer', '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2026-06-19 13:10:00+00', TIMESTAMPTZ '2026-06-20 10:00:00+00'), + (8002, 1001, 'Neon Nori', 'nori@example.invalid', 'neon_nori', 'https://twitch.tv/neon_nori', 'Visual Interlude', 'Kurze Motion-Overlay-Performance zwischen zwei Award-Bloecken.', 'OBS-Szene mit WebM-Loop, kein Mikro.', 'https://youtu.be/demo-nori-motion', '{"performanceLength":"3 Minuten","contentRating":"keine Hinweise"}', 'pending', NULL, NULL, '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2026-06-20 16:40:00+00', NULL), + (8003, 1001, 'Cleo Campfire', 'cleo@example.invalid', 'cleo_campfire', 'https://twitch.tv/cleo_campfire', 'Community Skit', 'Kurzer Call-and-response Sketch mit Chat-Kommandos.', 'Braucht Chat-Overlay-Freigabe.', 'https://youtu.be/demo-cleo-skit', '{"performanceLength":"5 Minuten","contentRating":"leichte Improvisation"}', 'pending', 'Technische Details klaeren.', NULL, '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2026-06-22 12:05:00+00', NULL), + (8004, 1000, 'Archiv Lyra', 'lyra@example.invalid', 'archiv_lyra', 'https://twitch.tv/archiv_lyra', 'Archiv Musik', 'Gewinner-Showcase aus dem Vorjahr.', 'VOD bereits vorhanden.', 'https://youtu.be/demo-archiv-lyra', '{"performanceLength":"4 Minuten","contentRating":"family friendly"}', 'approved', 'Archiv-Showact.', 'show_reviewer', '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2025-06-01 12:05:00+00', TIMESTAMPTZ '2025-06-02 09:30:00+00'); + + INSERT INTO "Sponsors" ( + "Id", "SeasonId", "Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder", "IsVisible", "CreatedAt", "UpdatedAt" + ) + VALUES + (9001, 1001, 'CloudBeacon Hosting', 'https://example.invalid/cloudbeacon', '/demo/sponsors/cloudbeacon-hosting.svg', 'Server- und Bot-Hosting fuer Community-Projekte.', 'Main Partner', 10, TRUE, TIMESTAMPTZ '2026-06-01 10:00:00+00', NULL), + (9002, 1001, 'NekoPixel Energy', 'https://example.invalid/nekopixel', '/demo/sponsors/nekopixel-energy.svg', 'Fiktiver Energy-Drink fuer lange Stream-Naechte.', 'Gold Partner', 20, TRUE, TIMESTAMPTZ '2026-06-01 10:05:00+00', NULL), + (9003, 1001, 'PrismLoop Audio', 'https://example.invalid/prismloop', '/demo/sponsors/prismloop-audio.svg', 'Audio-Tools und Soundpacks fuer Creator:innen.', 'Gold Partner', 30, TRUE, TIMESTAMPTZ '2026-06-01 10:10:00+00', NULL), + (9004, 1001, 'HoshiForge Studio', 'https://example.invalid/hoshiforge', '/demo/sponsors/hoshiforge-studio.svg', 'Branding, Overlays und kleine Motion-Pakete.', 'Community Partner', 40, TRUE, TIMESTAMPTZ '2026-06-01 10:15:00+00', NULL), + (9005, 1001, 'ChibiCanvas Market', 'https://example.invalid/chibicanvas', '/demo/sponsors/chibicanvas-market.svg', 'Asset-Marktplatz fuer Panels, Emotes und Stream-Grafiken.', 'Community Partner', 50, TRUE, TIMESTAMPTZ '2026-06-01 10:20:00+00', NULL); + + INSERT INTO "RiskFlags" ( + "Id", "SeasonId", "TwitchUserId", "Source", "Type", "Severity", "Status", "Summary", "CreatedFromIp", "UserAgent", + "MetadataJson", "ReviewNote", "ReviewedByTwitchId", "CreatedAt", "ReviewedAt" + ) + VALUES + (10001, 1001, 'viewer_1016', 'tracking-review', 'unsupported_platform', 'medium', 'open', 'Nominierung nutzt einen YouTube-Link und benoetigt manuellen Tracker-Review.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1001,"nominationId":4016}', NULL, NULL, TIMESTAMPTZ '2026-06-18 10:40:00+00', NULL), + (10002, 1001, 'viewer_1007', 'tracking-review', 'no_tracker_data', 'medium', 'open', 'TwitchTracker lieferte keine belastbaren Daten fuer Nyra Novel.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1001,"nominationId":4007}', 'Manuelle Lore-Pruefung vor Finale.', 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:02:00+00', TIMESTAMPTZ '2026-06-18 11:08:00+00'), + (10003, 1001, 'vote_user_007', 'voting', 'draft_ballot', 'low', 'resolved', 'Draft-Ballot ohne Submission, sichtbar fuer Dashboard-Randfall.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1001,"ballotId":5007}', 'Nur Demo-Randfall.', 'reviewer_demo', TIMESTAMPTZ '2026-06-27 14:20:00+00', TIMESTAMPTZ '2026-06-27 15:00:00+00'), + (10004, 1000, 'archiv_viewer_1', 'archive-cleanup', 'archived_clip', 'low', 'resolved', 'Archiv-Clip wurde fuer Gewinnerarchiv geprueft.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1000,"clipId":7007}', 'Archiv okay.', 'reviewer_demo', TIMESTAMPTZ '2025-06-11 09:10:00+00', TIMESTAMPTZ '2025-06-11 09:25:00+00'); + + SELECT setval(pg_get_serial_sequence('"Seasons"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Seasons"), 1)); + SELECT setval(pg_get_serial_sequence('"Categories"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Categories"), 1)); + SELECT setval(pg_get_serial_sequence('"StreamerIdentities"', 'Id'), COALESCE((SELECT MAX("Id") FROM "StreamerIdentities"), 1)); + SELECT setval(pg_get_serial_sequence('"Candidates"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Candidates"), 1)); + SELECT setval(pg_get_serial_sequence('"Nominations"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Nominations"), 1)); + SELECT setval(pg_get_serial_sequence('"VoteBallots"', 'Id'), COALESCE((SELECT MAX("Id") FROM "VoteBallots"), 1)); + SELECT setval(pg_get_serial_sequence('"VoteEntries"', 'Id'), COALESCE((SELECT MAX("Id") FROM "VoteEntries"), 1)); + SELECT setval(pg_get_serial_sequence('"Results"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Results"), 1)); + SELECT setval(pg_get_serial_sequence('"ClipSubmissions"', 'Id'), COALESCE((SELECT MAX("Id") FROM "ClipSubmissions"), 1)); + SELECT setval(pg_get_serial_sequence('"ShowactApplications"', 'Id'), COALESCE((SELECT MAX("Id") FROM "ShowactApplications"), 1)); + SELECT setval(pg_get_serial_sequence('"Sponsors"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Sponsors"), 1)); + SELECT setval(pg_get_serial_sequence('"RiskFlags"', 'Id'), COALESCE((SELECT MAX("Id") FROM "RiskFlags"), 1)); + """ + ); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + DELETE FROM "RiskFlags" WHERE "SeasonId" IN (1000, 1001); + DELETE FROM "Seasons" WHERE "Id" IN (1000, 1001) AND "IsDemo" = TRUE; + DELETE FROM "StreamerIdentities" WHERE "Id" BETWEEN 3001 AND 3010; + """ + ); + } +} diff --git a/Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.Designer.cs b/Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.Designer.cs similarity index 62% rename from Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.Designer.cs rename to Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.Designer.cs index ff23927..05df729 100644 --- a/Backend/Migrations/20260628115832_AddNominationGroupTrackerIdentity.Designer.cs +++ b/Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace Backend.Migrations { [DbContext(typeof(AwardsDbContext))] - [Migration("20260628115832_AddNominationGroupTrackerIdentity")] - partial class AddNominationGroupTrackerIdentity + [Migration("20260629144001_AdjustDemoSubcategoriesAndArchive")] + partial class AdjustDemoSubcategoriesAndArchive { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -112,56 +112,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Candidate", b => @@ -240,164 +190,6 @@ namespace Backend.Migrations b.HasIndex("StreamerIdentityId"); b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -448,118 +240,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die groesste Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); }); modelBuilder.Entity("Backend.Domain.ClipSubmission", b => @@ -668,6 +348,18 @@ namespace Backend.Migrations b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); + b.Property("FollowersGained") + .HasColumnType("integer"); + + b.Property("HoursStreamed") + .HasColumnType("integer"); + + b.Property("HoursWatched") + .HasColumnType("integer"); + + b.Property("PeakViewers") + .HasColumnType("integer"); + b.Property("ResolvedChannel") .HasMaxLength(120) .HasColumnType("character varying(120)"); @@ -720,6 +412,30 @@ namespace Backend.Migrations .HasColumnType("character varying(40)") .HasDefaultValue("pending"); + b.Property("TrackingFlagsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + + b.Property("TrackingReviewNote") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TrackingReviewStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(30) + .HasColumnType("character varying(30)") + .HasDefaultValue("clear"); + + b.Property("TrackingReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TrackingReviewedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + b.HasKey("Id"); b.HasIndex("CandidateId"); @@ -737,32 +453,6 @@ namespace Backend.Migrations b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - CategoryGroupName = "", - 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", - TrackerStatus = "pending" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - CategoryGroupName = "", - 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", - TrackerStatus = "pending" - }); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -859,6 +549,11 @@ namespace Backend.Migrations b.Property("IsCurrent") .HasColumnType("boolean"); + b.Property("IsDemo") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(160) @@ -882,11 +577,6 @@ namespace Backend.Migrations b.Property("ShowStartsAt") .HasColumnType("time without time zone"); - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - b.Property("SubcategoryTemplatesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -899,6 +589,19 @@ namespace Backend.Migrations b.Property("VotingStartsAt") .HasColumnType("date"); + b.Property("WinnersPublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WinnersPublishedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("WorkflowRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("Year") .HasColumnType("integer"); @@ -908,84 +611,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - Year = 2023 - }); }); modelBuilder.Entity("Backend.Domain.ShowactApplication", b => @@ -1087,6 +712,14 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AwardsSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AwardsSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("ClipAdminMenuVisible") .HasColumnType("boolean"); @@ -1230,6 +863,12 @@ namespace Backend.Migrations .HasMaxLength(240) .HasColumnType("character varying(240)"); + b.Property("ShowactApplicationEndsAt") + .HasColumnType("date"); + + b.Property("ShowactApplicationStartsAt") + .HasColumnType("date"); + b.Property("ShowactApplicationsEnabled") .ValueGeneratedOnAdd() .HasColumnType("boolean") @@ -1268,6 +907,80 @@ namespace Backend.Migrations .HasColumnType("boolean") .HasDefaultValue(true); + b.Property("StreamBannerCompletedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerCompletedEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerCompletedTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerLockedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerUseCompletedContent") + .HasColumnType("boolean"); + + b.Property("SubcategoriesSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubcategoriesSectionTitle") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingReviewNotes") + .IsRequired() + .HasColumnType("text"); + + b.Property("TrackingRulesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("[]"); + b.Property("TwitchAuthManagedByDatabase") .HasColumnType("boolean"); @@ -1291,6 +1004,11 @@ namespace Backend.Migrations .HasMaxLength(300) .HasColumnType("character varying(300)"); + b.Property("ViewerStatsProviderBaseUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + b.Property("WorkflowRulesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -1300,58 +1018,6 @@ namespace Backend.Migrations b.HasKey("Id"); b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team ueber die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFuer Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze fuer organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsaechlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt fuer die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier koennen Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den oeffentlichen Kontaktflaechen genannt, sobald sie final bestaetigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - WorkflowRulesJson = "[{\"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.\"}]" - }); }); modelBuilder.Entity("Backend.Domain.Sponsor", b => @@ -1650,24 +1316,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); }); modelBuilder.Entity("Backend.Domain.VoteEntry", b => @@ -1696,36 +1344,6 @@ namespace Backend.Migrations b.HasIndex("CategoryId"); b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); }); modelBuilder.Entity("Backend.Domain.AwardResult", b => @@ -1798,6 +1416,12 @@ namespace Backend.Migrations .HasForeignKey("CandidateId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Backend.Domain.Season", null) + .WithMany() + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Candidate"); }); diff --git a/Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.cs b/Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.cs new file mode 100644 index 0000000..9a9f5ea --- /dev/null +++ b/Backend/Migrations/20260629144001_AdjustDemoSubcategoriesAndArchive.cs @@ -0,0 +1,292 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Backend.Migrations; + +public partial class AdjustDemoSubcategoriesAndArchive : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + DELETE FROM "RiskFlags" WHERE "SeasonId" IN (999, 1000, 1001); + DELETE FROM "Seasons" WHERE "Id" IN (999, 1000, 1001) AND "IsDemo" = TRUE; + DELETE FROM "StreamerIdentities" WHERE "Id" BETWEEN 3001 AND 3020; + + INSERT INTO "Seasons" ( + "Id", "Year", "Name", "IsDemo", "IsCurrent", "IsCommunityOnly", "CurrentPhase", + "NominationStartsAt", "NominationEndsAt", "VotingStartsAt", "VotingEndsAt", + "ReviewStartsAt", "ReviewEndsAt", "ShowDate", "ShowStartsAt", + "WinnersPublishedAt", "WinnersPublishedByTwitchId", "SubcategoryTemplatesJson", "WorkflowRulesJson" + ) + VALUES + (999, 2024, 'VTuber Star Awards 2024 Demo Archiv', TRUE, FALSE, FALSE, 'Archiv', + DATE '2024-04-01', DATE '2024-04-28', DATE '2024-05-10', DATE '2024-05-24', + DATE '2024-04-29', DATE '2024-05-09', DATE '2024-06-22', TIME '20:00', + TIMESTAMPTZ '2024-06-23 10:00:00+00', 'demo_owner', + '[{"name":"Hidden Star","slug":"hidden-star","sortOrder":1,"viewerRangeMin":1,"viewerRangeMax":20},{"name":"Rising Star","slug":"rising-star","sortOrder":2,"viewerRangeMin":21,"viewerRangeMax":60},{"name":"Shining Star","slug":"shining-star","sortOrder":3,"viewerRangeMin":61,"viewerRangeMax":null}]', + '[]'), + (1000, 2025, 'VTuber Star Awards 2025 Demo Archiv', TRUE, FALSE, FALSE, 'Archiv', + DATE '2025-04-01', DATE '2025-04-28', DATE '2025-05-10', DATE '2025-05-24', + DATE '2025-04-29', DATE '2025-05-09', DATE '2025-06-21', TIME '20:00', + TIMESTAMPTZ '2025-06-22 10:00:00+00', 'demo_owner', + '[{"name":"Hidden Star","slug":"hidden-star","sortOrder":1,"viewerRangeMin":1,"viewerRangeMax":20},{"name":"Rising Star","slug":"rising-star","sortOrder":2,"viewerRangeMin":21,"viewerRangeMax":60},{"name":"Shining Star","slug":"shining-star","sortOrder":3,"viewerRangeMin":61,"viewerRangeMax":null}]', + '[]'), + (1001, 2026, 'VTuber Star Awards 2026 Demo', TRUE, TRUE, FALSE, 'Voting', + DATE '2026-05-18', DATE '2026-06-16', DATE '2026-06-24', DATE '2026-07-19', + DATE '2026-06-17', DATE '2026-06-23', DATE '2026-08-08', TIME '20:00', + NULL, NULL, + '[{"name":"Hidden Star","slug":"hidden-star","sortOrder":1,"viewerRangeMin":1,"viewerRangeMax":20},{"name":"Rising Star","slug":"rising-star","sortOrder":2,"viewerRangeMin":21,"viewerRangeMax":60},{"name":"Shining Star","slug":"shining-star","sortOrder":3,"viewerRangeMin":61,"viewerRangeMax":null}]', + '[]'); + + WITH tiers(ord, name, slug, min_viewers, max_viewers) AS ( + VALUES + (1, 'Hidden Star', 'hidden-star', 1, 20), + (2, 'Rising Star', 'rising-star', 21, 60), + (3, 'Shining Star', 'shining-star', 61, NULL) + ), + active_groups(ord, name, slug) AS ( + VALUES + (1, 'Gaming', 'gaming'), + (2, 'Music', 'music'), + (3, 'Art & Design', 'art-design'), + (4, 'Community', 'community') + ), + archive_2025_groups(ord, name, slug) AS ( + VALUES + (1, 'Gaming', 'gaming'), + (2, 'Music', 'music'), + (3, 'Community', 'community') + ), + archive_2024_groups(ord, name, slug) AS ( + VALUES + (1, 'Gaming', 'gaming'), + (2, 'Music', 'music') + ) + INSERT INTO "Categories" ( + "Id", "SeasonId", "GroupName", "Name", "Slug", "Description", "SortOrder", "MaxNomineesPerUser", "ViewerRangeMin", "ViewerRangeMax" + ) + SELECT 1100 + ((g.ord - 1) * 3) + t.ord, 1001, g.name, t.name, g.slug || '-' || t.slug, + g.name || '-Creator im ' || t.name || '-Tier.', ((g.ord - 1) * 30) + (t.ord * 10), 2, t.min_viewers, t.max_viewers + FROM active_groups g CROSS JOIN tiers t + UNION ALL + SELECT 1200 + ((g.ord - 1) * 3) + t.ord, 1000, g.name, t.name, 'archive-2025-' || g.slug || '-' || t.slug, + 'Archiv-Unterkategorie.', ((g.ord - 1) * 30) + (t.ord * 10), 2, t.min_viewers, t.max_viewers + FROM archive_2025_groups g CROSS JOIN tiers t + UNION ALL + SELECT 1300 + ((g.ord - 1) * 3) + t.ord, 999, g.name, t.name, 'archive-2024-' || g.slug || '-' || t.slug, + 'Archiv-Unterkategorie.', ((g.ord - 1) * 30) + (t.ord * 10), 2, t.min_viewers, t.max_viewers + FROM archive_2024_groups g CROSS JOIN tiers t; + + INSERT INTO "StreamerIdentities" ("Id", "Platform", "Login", "NormalizedKey", "DisplayName", "ProfileUrl", "LastResolvedAt") + VALUES + (3001, 'Twitch', 'aki_lumina', 'twitch:aki_lumina', 'Aki Lumina', 'https://twitch.tv/aki_lumina', TIMESTAMPTZ '2026-06-27 12:00:00+00'), + (3002, 'Twitch', 'mira_orbit', 'twitch:mira_orbit', 'Mira Orbit', 'https://twitch.tv/mira_orbit', TIMESTAMPTZ '2026-06-27 12:02:00+00'), + (3003, 'Twitch', 'nova_nym', 'twitch:nova_nym', 'Nova Nym', 'https://twitch.tv/nova_nym', TIMESTAMPTZ '2026-06-27 12:04:00+00'), + (3004, 'Twitch', 'luna_koi', 'twitch:luna_koi', 'Luna Koi', 'https://twitch.tv/luna_koi', TIMESTAMPTZ '2026-06-27 12:06:00+00'), + (3005, 'Twitch', 'runa_bits', 'twitch:runa_bits', 'Runa Bits', 'https://twitch.tv/runa_bits', TIMESTAMPTZ '2026-06-27 12:08:00+00'), + (3006, 'Twitch', 'sora_slate', 'twitch:sora_slate', 'Sora Slate', 'https://twitch.tv/sora_slate', TIMESTAMPTZ '2026-06-27 12:10:00+00'); + + WITH active_names(name, slug) AS ( + VALUES + ('Aki Lumina', 'aki_lumina'), ('Miki Mint', 'miki_mint'), ('Nova Nym', 'nova_nym'), + ('Mira Orbit', 'mira_orbit'), ('Taro Tactics', 'taro_tactics'), ('Yuna Quest', 'yuna_quest'), + ('Nova Nym Prime', 'nova_nym_prime'), ('Rin Replay', 'rin_replay'), ('Kira Comet', 'kira_comet'), + ('Melo Moon', 'melo_moon'), ('Echo Rill', 'echo_rill'), ('Vivi Verse', 'vivi_verse'), + ('Luna Koi', 'luna_koi'), ('Hana Hertz', 'hana_hertz'), ('Nyra Novel', 'nyra_novel'), + ('Vera Volt', 'vera_volt'), ('Kai Myth', 'kai_myth'), ('Mina Maze', 'mina_maze'), + ('Iris Ink', 'iris_ink'), ('Pia Palette', 'pia_palette'), ('Theo Thimble', 'theo_thimble'), + ('Neon Nori', 'neon_nori'), ('Slate Sen', 'slate_sen'), ('Momo Motion', 'momo_motion'), + ('Chroma Vale', 'chroma_vale'), ('Riku Render', 'riku_render'), ('Faye Flux', 'faye_flux'), + ('Runa Bits', 'runa_bits'), ('Bibi Beacon', 'bibi_beacon'), ('Ori Opal', 'ori_opal'), + ('Sora Slate', 'sora_slate'), ('Cleo Campfire', 'cleo_campfire'), ('Juno Jamboree', 'juno_jamboree'), + ('Ember Vail', 'ember_vail'), ('Riku Relay', 'riku_relay'), ('Nami Node', 'nami_node') + ), + numbered_active AS ( + SELECT row_number() OVER () AS rn, name, slug FROM active_names + ) + INSERT INTO "Candidates" ( + "Id", "SeasonId", "CategoryId", "StreamerIdentityId", "DisplayName", "ChannelSlug", "Platform", + "NominationTally", "AcceptanceStatus", "AcceptanceNote", "ClipCompilationUrl", "ClipCompilationTitle", "ClipCompilationPlatform", "ClipEmbedStatus" + ) + SELECT 2000 + rn, 1001, 1101 + ((rn - 1) / 3)::int, + CASE WHEN rn <= 6 THEN (3000 + rn)::integer ELSE NULL::integer END, + name, slug, 'Twitch', + 3 + ((rn * 2) % 11), + CASE WHEN rn % 6 = 0 THEN 'pending' ELSE 'approved' END, + CASE WHEN rn IN (1, 10, 16, 25, 34) THEN 'Demo-Highlight fuer Review gepflegt.' ELSE NULL END, + CASE rn + WHEN 1 THEN 'https://youtu.be/demo-aki-rise' + WHEN 7 THEN 'https://clips.twitch.tv/demo-nova-finale' + WHEN 10 THEN 'https://youtu.be/demo-melo-live' + WHEN 16 THEN 'https://youtu.be/demo-vera-stage' + WHEN 25 THEN 'https://youtu.be/demo-chroma-design' + WHEN 34 THEN 'https://clips.twitch.tv/demo-ember-moment' + ELSE NULL + END, + CASE rn + WHEN 1 THEN 'Aki Lumina Hidden Star Reel' + WHEN 7 THEN 'Nova Nym Shining Clip' + WHEN 10 THEN 'Melo Moon Live Set' + WHEN 16 THEN 'Vera Volt Stage Reel' + WHEN 25 THEN 'Chroma Vale Design Reel' + WHEN 34 THEN 'Ember Vail Finale Clip' + ELSE NULL + END, + CASE WHEN rn IN (7, 34) THEN 'Twitch' WHEN rn IN (1, 10, 16, 25) THEN 'YouTube' ELSE NULL END, + CASE WHEN rn IN (1, 7, 10, 16, 25, 34) THEN 'available' ELSE 'unchecked' END + FROM numbered_active; + + WITH archive_2025_names(name, slug) AS ( + VALUES + ('Archiv Aster', 'archiv_aster'), ('Archiv Beryl', 'archiv_beryl'), ('Archiv Coda', 'archiv_coda'), + ('Archiv Drift', 'archiv_drift'), ('Archiv Elara', 'archiv_elara'), ('Archiv Finch', 'archiv_finch'), + ('Archiv Lyra', 'archiv_lyra'), ('Archiv Muse', 'archiv_muse'), ('Archiv Nia', 'archiv_nia') + ), + archive_2024_names(name, slug) AS ( + VALUES + ('Archiv Poppy', 'archiv_poppy'), ('Archiv Quartz', 'archiv_quartz'), ('Archiv Rune', 'archiv_rune'), + ('Archiv Sol', 'archiv_sol'), ('Archiv Tide', 'archiv_tide'), ('Archiv Uma', 'archiv_uma') + ), + numbered_2025 AS ( + SELECT row_number() OVER () AS rn, name, slug FROM archive_2025_names + ), + numbered_2024 AS ( + SELECT row_number() OVER () AS rn, name, slug FROM archive_2024_names + ) + INSERT INTO "Candidates" ( + "Id", "SeasonId", "CategoryId", "StreamerIdentityId", "DisplayName", "ChannelSlug", "Platform", + "NominationTally", "AcceptanceStatus", "AcceptanceNote", "ClipCompilationUrl", "ClipCompilationTitle", "ClipCompilationPlatform", "ClipEmbedStatus" + ) + SELECT 2100 + rn, 1000, 1200 + rn, NULL::integer, name, slug, 'Twitch', 7 + rn, 'approved', NULL, + CASE WHEN rn IN (3, 6, 9) THEN 'https://clips.twitch.tv/demo-' || slug ELSE 'https://youtu.be/demo-' || slug END, + name || ' Winner Reel', + CASE WHEN rn IN (3, 6, 9) THEN 'Twitch' ELSE 'YouTube' END, + 'available' + FROM numbered_2025 + UNION ALL + SELECT 2200 + rn, 999, 1300 + rn, NULL::integer, name, slug, 'Twitch', 6 + rn, 'approved', NULL, + CASE WHEN rn IN (3, 6) THEN 'https://clips.twitch.tv/demo-' || slug ELSE 'https://youtu.be/demo-' || slug END, + name || ' Winner Reel', + CASE WHEN rn IN (3, 6) THEN 'Twitch' ELSE 'YouTube' END, + 'available' + FROM numbered_2024; + + INSERT INTO "Nominations" ( + "Id", "SeasonId", "CategoryId", "CategoryGroupName", "SubmittedByTwitchId", "CandidateId", "StreamerIdentityId", "SuggestedCategoryId", + "CandidateText", "StreamUrl", "ResolvedChannel", "ResolvedPlatform", "AvgViewers", "HoursStreamed", "HoursWatched", "PeakViewers", "FollowersGained", + "TrackerStatus", "TrackerCheckedAt", "TrackingReviewStatus", "TrackingFlagsJson", "TrackingReviewNote", "TrackingReviewedByTwitchId", "TrackingReviewedAt", + "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedAt", "ReviewedAt" + ) + VALUES + (4001, 1001, 1101, 'Gaming', 'viewer_1001', 2001, 3001, NULL, 'Aki Lumina', 'https://twitch.tv/aki_lumina', 'aki_lumina', 'Twitch', 14, 42, 2600, 48, 210, 'resolved', TIMESTAMPTZ '2026-06-18 09:20:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:10:00+00', 'approved', 'Hidden tier passt.', 'reviewer_demo', TIMESTAMPTZ '2026-06-02 18:30:00+00', TIMESTAMPTZ '2026-06-18 10:10:00+00'), + (4002, 1001, 1102, 'Gaming', 'viewer_1002', 2004, 3002, NULL, 'Mira Orbit', 'https://twitch.tv/mira_orbit', 'mira_orbit', 'Twitch', 38, 58, 5900, 96, 510, 'resolved', TIMESTAMPTZ '2026-06-18 09:24:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:15:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-03 20:15:00+00', TIMESTAMPTZ '2026-06-18 10:15:00+00'), + (4003, 1001, 1103, 'Gaming', 'viewer_1003', 2007, 3003, NULL, 'Nova Nym Prime', 'https://twitch.tv/nova_nym_prime', 'nova_nym_prime', 'Twitch', 116, 44, 9100, 420, 1200, 'resolved', TIMESTAMPTZ '2026-06-18 09:30:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:22:00+00', 'approved', 'Shining tier passt.', 'reviewer_demo', TIMESTAMPTZ '2026-06-04 14:45:00+00', TIMESTAMPTZ '2026-06-18 10:22:00+00'), + (4004, 1001, 1104, 'Music', 'viewer_1004', 2010, NULL, NULL, 'Melo Moon', 'https://twitch.tv/melo_moon', 'melo_moon', 'Twitch', 18, 35, 2800, 52, 240, 'resolved', TIMESTAMPTZ '2026-06-18 09:40:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:30:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-05 21:05:00+00', TIMESTAMPTZ '2026-06-18 10:30:00+00'), + (4005, 1001, 1105, 'Music', 'viewer_1005', 2013, 3004, NULL, 'Luna Koi', 'https://twitch.tv/luna_koi', 'luna_koi', 'Twitch', 45, 51, 6100, 110, 620, 'resolved', TIMESTAMPTZ '2026-06-18 09:45:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:36:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-06 19:25:00+00', TIMESTAMPTZ '2026-06-18 10:36:00+00'), + (4006, 1001, 1106, 'Music', 'viewer_1006', 2016, NULL, NULL, 'Vera Volt', 'https://twitch.tv/vera_volt', 'vera_volt', 'Twitch', 88, 35, 7400, 210, 430, 'resolved', TIMESTAMPTZ '2026-06-18 09:48:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 10:41:00+00', 'approved', 'Live-Musik-Set pruefbar.', 'reviewer_demo', TIMESTAMPTZ '2026-06-07 17:55:00+00', TIMESTAMPTZ '2026-06-18 10:41:00+00'), + (4007, 1001, 1107, 'Art & Design', 'viewer_1007', 2019, NULL, NULL, 'Iris Ink', 'https://twitch.tv/iris_ink', 'iris_ink', 'Twitch', 16, 31, 2100, 41, 180, 'resolved', TIMESTAMPTZ '2026-06-18 09:52:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:00:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-08 18:10:00+00', TIMESTAMPTZ '2026-06-18 11:00:00+00'), + (4008, 1001, 1108, 'Art & Design', 'viewer_1008', 2022, NULL, NULL, 'Neon Nori', 'https://twitch.tv/neon_nori', 'neon_nori', 'Twitch', 52, 65, 31800, 130, 920, 'resolved', TIMESTAMPTZ '2026-06-18 09:58:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:06:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-09 16:20:00+00', TIMESTAMPTZ '2026-06-18 11:06:00+00'), + (4009, 1001, 1109, 'Art & Design', 'viewer_1009', 2025, NULL, NULL, 'Chroma Vale', 'https://twitch.tv/chroma_vale', 'chroma_vale', 'Twitch', 177, 54, 24200, 440, 610, 'resolved', TIMESTAMPTZ '2026-06-18 10:02:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:12:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-10 20:40:00+00', TIMESTAMPTZ '2026-06-18 11:12:00+00'), + (4010, 1001, 1110, 'Community', 'viewer_1010', 2028, 3005, NULL, 'Runa Bits', 'https://twitch.tv/runa_bits', 'runa_bits', 'Twitch', 12, 49, 1700, 29, 120, 'resolved', TIMESTAMPTZ '2026-06-18 10:08:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:18:00+00', 'approved', 'Community-Event mit Planungsdoku.', 'reviewer_demo', TIMESTAMPTZ '2026-06-11 15:50:00+00', TIMESTAMPTZ '2026-06-18 11:18:00+00'), + (4011, 1001, 1111, 'Community', 'viewer_1011', 2031, 3006, NULL, 'Sora Slate', 'https://twitch.tv/sora_slate', 'sora_slate', 'Twitch', 44, 37, 11800, 92, 340, 'resolved', TIMESTAMPTZ '2026-06-18 10:14:00+00', 'clear', '[]', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:24:00+00', 'approved', NULL, 'reviewer_demo', TIMESTAMPTZ '2026-06-12 19:05:00+00', TIMESTAMPTZ '2026-06-18 11:24:00+00'), + (4012, 1001, 1112, 'Community', 'viewer_1012', 2034, NULL, NULL, 'Ember Vail', 'https://twitch.tv/ember_vail', 'ember_vail', 'Twitch', NULL, NULL, NULL, NULL, NULL, 'no_data', TIMESTAMPTZ '2026-06-18 10:18:00+00', 'needs_review', '[{"key":"no_tracker_data","label":"Keine Tracker-Daten","severity":"medium","description":"TwitchTracker hat keinen belastbaren Summary-Wert geliefert.","requiresManualReview":true,"blocksApproval":false,"adminNoteRequiredOnOverride":false}]', 'Manueller Review noetig.', NULL, NULL, 'pending', 'Tracker-Daten nachfordern.', NULL, TIMESTAMPTZ '2026-06-13 22:15:00+00', NULL); + + INSERT INTO "VoteBallots" ("Id", "SeasonId", "SubmittedByTwitchId", "Status", "SubmittedAt") + VALUES + (5001, 1001, 'vote_user_001', 'submitted', TIMESTAMPTZ '2026-06-25 18:10:00+00'), + (5002, 1001, 'vote_user_002', 'submitted', TIMESTAMPTZ '2026-06-25 18:16:00+00'), + (5003, 1001, 'vote_user_003', 'submitted', TIMESTAMPTZ '2026-06-25 19:05:00+00'), + (5004, 1001, 'vote_user_004', 'submitted', TIMESTAMPTZ '2026-06-26 12:45:00+00'), + (5005, 1001, 'vote_user_005', 'submitted', TIMESTAMPTZ '2026-06-26 20:30:00+00'), + (5006, 1001, 'vote_user_006', 'submitted', TIMESTAMPTZ '2026-06-27 09:20:00+00'), + (5007, 1001, 'vote_user_007', 'draft', TIMESTAMPTZ '2026-06-27 14:15:00+00'), + (5008, 1001, 'vote_user_008', 'submitted', TIMESTAMPTZ '2026-06-28 21:05:00+00'); + + INSERT INTO "VoteEntries" ("Id", "BallotId", "CategoryId", "CandidateId") + VALUES + (5101, 5001, 1101, 2001), (5102, 5001, 1104, 2010), (5103, 5001, 1107, 2019), (5104, 5001, 1110, 2028), + (5105, 5002, 1102, 2004), (5106, 5002, 1105, 2013), (5107, 5002, 1108, 2022), (5108, 5002, 1111, 2031), + (5109, 5003, 1103, 2007), (5110, 5003, 1106, 2016), (5111, 5003, 1109, 2025), (5112, 5003, 1112, 2034), + (5113, 5004, 1101, 2002), (5114, 5004, 1104, 2011), (5115, 5004, 1107, 2020), (5116, 5004, 1110, 2029), + (5117, 5005, 1102, 2005), (5118, 5005, 1105, 2014), (5119, 5005, 1108, 2023), (5120, 5005, 1111, 2032), + (5121, 5006, 1103, 2008), (5122, 5006, 1106, 2017), (5123, 5006, 1109, 2026), (5124, 5006, 1112, 2035), + (5125, 5007, 1101, 2003), (5126, 5007, 1104, 2012), + (5127, 5008, 1102, 2004), (5128, 5008, 1105, 2013), (5129, 5008, 1108, 2022), (5130, 5008, 1111, 2031); + + INSERT INTO "Results" ("Id", "SeasonId", "CategoryId", "CandidateId", "CategoryName") + SELECT 6000 + "Id" - 1200, 1000, "Id", 2100 + "Id" - 1200, "Name" FROM "Categories" WHERE "SeasonId" = 1000 + UNION ALL + SELECT 6100 + "Id" - 1300, 999, "Id", 2200 + "Id" - 1300, "Name" FROM "Categories" WHERE "SeasonId" = 999; + + INSERT INTO "ClipSubmissions" ( + "Id", "SeasonId", "CategoryId", "CandidateId", "SubmittedByTwitchId", "ClipUrl", "Title", "Creator", "Platform", + "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedFromIp", "CreatedAt", "ReviewedAt" + ) + VALUES + (7001, 1001, 1103, 2007, 'viewer_2001', 'https://clips.twitch.tv/demo-nova-finale', 'Nova turns the boss fight', 'viewer_2001', 'Twitch', 'approved', 'Kontext passt.', 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-20 18:12:00+00', TIMESTAMPTZ '2026-06-21 09:00:00+00'), + (7002, 1001, 1106, 2016, 'viewer_2002', 'https://youtu.be/demo-vera-stage', 'Vera sings the finale bridge', 'viewer_2002', 'YouTube', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-20 20:25:00+00', TIMESTAMPTZ '2026-06-21 09:08:00+00'), + (7003, 1001, 1112, 2034, 'viewer_2003', 'https://clips.twitch.tv/demo-ember-moment', 'Ember opens the community event', 'viewer_2003', 'Twitch', 'pending', 'Timing pruefen.', NULL, '127.0.0.1', TIMESTAMPTZ '2026-06-21 11:40:00+00', NULL), + (7004, 1001, 1109, 2025, 'viewer_2004', 'https://youtu.be/demo-chroma-design', 'Chroma explains the overlay rebuild', 'viewer_2004', 'YouTube', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2026-06-21 15:10:00+00', TIMESTAMPTZ '2026-06-22 08:45:00+00'), + (7005, 1001, 1111, 2031, 'viewer_2005', 'https://clips.twitch.tv/demo-sora-community', 'Sora lets chat design the scene', 'viewer_2005', 'Twitch', 'pending', NULL, NULL, '127.0.0.1', TIMESTAMPTZ '2026-06-22 19:55:00+00', NULL), + (7006, 1000, 1203, 2103, 'archiv_viewer_1', 'https://clips.twitch.tv/demo-archiv-coda', 'Archiv Coda finale clip', 'archiv_viewer_1', 'Twitch', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2025-06-10 19:00:00+00', TIMESTAMPTZ '2025-06-11 09:00:00+00'), + (7007, 999, 1303, 2203, 'archiv_viewer_2', 'https://clips.twitch.tv/demo-archiv-rune', 'Archiv Rune classic moment', 'archiv_viewer_2', 'Twitch', 'approved', NULL, 'clip_reviewer', '127.0.0.1', TIMESTAMPTZ '2024-06-10 19:00:00+00', TIMESTAMPTZ '2024-06-11 09:00:00+00'); + + INSERT INTO "ShowactApplications" ( + "Id", "SeasonId", "ArtistName", "ContactEmail", "ContactDiscord", "PlatformUrl", "PerformanceType", "Description", + "TechnicalNotes", "ReferenceUrl", "FieldResponsesJson", "Status", "ReviewNote", "ReviewedByTwitchId", "CreatedFromIp", "UserAgent", "CreatedAt", "ReviewedAt" + ) + VALUES + (8001, 1001, 'Melo Moon', 'melo@example.invalid', 'melo_moon', 'https://twitch.tv/melo_moon', 'Live-Gesang', 'Akustisches Opening-Medley mit zwei kurzen Songs.', 'Benoetigt Instrumentalspur und Monitoring.', 'https://youtu.be/demo-melo-live', '{"performanceLength":"6 Minuten","contentRating":"family friendly"}', 'approved', 'Passt als Opener.', 'show_reviewer', '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2026-06-19 13:10:00+00', TIMESTAMPTZ '2026-06-20 10:00:00+00'), + (8002, 1001, 'Neon Nori', 'nori@example.invalid', 'neon_nori', 'https://twitch.tv/neon_nori', 'Visual Interlude', 'Kurze Motion-Overlay-Performance zwischen zwei Award-Bloecken.', 'OBS-Szene mit WebM-Loop, kein Mikro.', 'https://youtu.be/demo-nori-motion', '{"performanceLength":"3 Minuten","contentRating":"keine Hinweise"}', 'pending', NULL, NULL, '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2026-06-20 16:40:00+00', NULL), + (8003, 1000, 'Archiv Lyra', 'lyra@example.invalid', 'archiv_lyra', 'https://twitch.tv/archiv_lyra', 'Archiv Musik', 'Gewinner-Showcase aus dem Vorjahr.', 'VOD vorhanden.', 'https://youtu.be/demo-archiv-lyra', '{"performanceLength":"4 Minuten","contentRating":"family friendly"}', 'approved', 'Archiv-Showact.', 'show_reviewer', '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2025-06-01 12:05:00+00', TIMESTAMPTZ '2025-06-02 09:30:00+00'), + (8004, 999, 'Archiv Sol', 'sol@example.invalid', 'archiv_sol', 'https://twitch.tv/archiv_sol', 'Archiv Musik', 'Aelteres Archiv-Showcase fuer den Archiv-Endpunkt.', 'VOD vorhanden.', 'https://youtu.be/demo-archiv-sol', '{"performanceLength":"4 Minuten","contentRating":"family friendly"}', 'approved', 'Archiv-Showact.', 'show_reviewer', '127.0.0.1', 'DemoBrowser/1.0', TIMESTAMPTZ '2024-06-01 12:05:00+00', TIMESTAMPTZ '2024-06-02 09:30:00+00'); + + INSERT INTO "Sponsors" ( + "Id", "SeasonId", "Name", "WebsiteUrl", "LogoUrl", "Description", "Tier", "SortOrder", "IsVisible", "CreatedAt", "UpdatedAt" + ) + VALUES + (9001, 1001, 'CloudBeacon Hosting', 'https://example.invalid/cloudbeacon', '/demo/sponsors/cloudbeacon-hosting.svg', 'Server- und Bot-Hosting fuer Community-Projekte.', 'Main Partner', 10, TRUE, TIMESTAMPTZ '2026-06-01 10:00:00+00', NULL), + (9002, 1001, 'NekoPixel Energy', 'https://example.invalid/nekopixel', '/demo/sponsors/nekopixel-energy.svg', 'Fiktiver Energy-Drink fuer lange Stream-Naechte.', 'Gold Partner', 20, TRUE, TIMESTAMPTZ '2026-06-01 10:05:00+00', NULL), + (9003, 1001, 'PrismLoop Audio', 'https://example.invalid/prismloop', '/demo/sponsors/prismloop-audio.svg', 'Audio-Tools und Soundpacks fuer Creator:innen.', 'Gold Partner', 30, TRUE, TIMESTAMPTZ '2026-06-01 10:10:00+00', NULL), + (9004, 1001, 'HoshiForge Studio', 'https://example.invalid/hoshiforge', '/demo/sponsors/hoshiforge-studio.svg', 'Branding, Overlays und kleine Motion-Pakete.', 'Community Partner', 40, TRUE, TIMESTAMPTZ '2026-06-01 10:15:00+00', NULL), + (9005, 1001, 'ChibiCanvas Market', 'https://example.invalid/chibicanvas', '/demo/sponsors/chibicanvas-market.svg', 'Asset-Marktplatz fuer Panels, Emotes und Stream-Grafiken.', 'Community Partner', 50, TRUE, TIMESTAMPTZ '2026-06-01 10:20:00+00', NULL); + + INSERT INTO "RiskFlags" ( + "Id", "SeasonId", "TwitchUserId", "Source", "Type", "Severity", "Status", "Summary", "CreatedFromIp", "UserAgent", + "MetadataJson", "ReviewNote", "ReviewedByTwitchId", "CreatedAt", "ReviewedAt" + ) + VALUES + (10001, 1001, 'viewer_1012', 'tracking-review', 'no_tracker_data', 'medium', 'open', 'TwitchTracker lieferte keine belastbaren Daten fuer Ember Vail.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1001,"nominationId":4012}', 'Manuelle Community-Pruefung vor Finale.', 'reviewer_demo', TIMESTAMPTZ '2026-06-18 11:02:00+00', TIMESTAMPTZ '2026-06-18 11:08:00+00'), + (10002, 1001, 'vote_user_007', 'voting', 'draft_ballot', 'low', 'resolved', 'Draft-Ballot ohne Submission, sichtbar fuer Dashboard-Randfall.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1001,"ballotId":5007}', 'Nur Demo-Randfall.', 'reviewer_demo', TIMESTAMPTZ '2026-06-27 14:20:00+00', TIMESTAMPTZ '2026-06-27 15:00:00+00'), + (10003, 1000, 'archiv_viewer_1', 'archive-cleanup', 'archived_clip', 'low', 'resolved', 'Archiv-Clip 2025 wurde geprueft.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":1000,"clipId":7006}', 'Archiv okay.', 'reviewer_demo', TIMESTAMPTZ '2025-06-11 09:10:00+00', TIMESTAMPTZ '2025-06-11 09:25:00+00'), + (10004, 999, 'archiv_viewer_2', 'archive-cleanup', 'archived_clip', 'low', 'resolved', 'Archiv-Clip 2024 wurde geprueft.', '127.0.0.1', 'DemoBrowser/1.0', '{"seasonId":999,"clipId":7007}', 'Archiv okay.', 'reviewer_demo', TIMESTAMPTZ '2024-06-11 09:10:00+00', TIMESTAMPTZ '2024-06-11 09:25:00+00'); + + SELECT setval(pg_get_serial_sequence('"Seasons"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Seasons"), 1)); + SELECT setval(pg_get_serial_sequence('"Categories"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Categories"), 1)); + SELECT setval(pg_get_serial_sequence('"StreamerIdentities"', 'Id'), COALESCE((SELECT MAX("Id") FROM "StreamerIdentities"), 1)); + SELECT setval(pg_get_serial_sequence('"Candidates"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Candidates"), 1)); + SELECT setval(pg_get_serial_sequence('"Nominations"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Nominations"), 1)); + SELECT setval(pg_get_serial_sequence('"VoteBallots"', 'Id'), COALESCE((SELECT MAX("Id") FROM "VoteBallots"), 1)); + SELECT setval(pg_get_serial_sequence('"VoteEntries"', 'Id'), COALESCE((SELECT MAX("Id") FROM "VoteEntries"), 1)); + SELECT setval(pg_get_serial_sequence('"Results"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Results"), 1)); + SELECT setval(pg_get_serial_sequence('"ClipSubmissions"', 'Id'), COALESCE((SELECT MAX("Id") FROM "ClipSubmissions"), 1)); + SELECT setval(pg_get_serial_sequence('"ShowactApplications"', 'Id'), COALESCE((SELECT MAX("Id") FROM "ShowactApplications"), 1)); + SELECT setval(pg_get_serial_sequence('"Sponsors"', 'Id'), COALESCE((SELECT MAX("Id") FROM "Sponsors"), 1)); + SELECT setval(pg_get_serial_sequence('"RiskFlags"', 'Id'), COALESCE((SELECT MAX("Id") FROM "RiskFlags"), 1)); + """ + ); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql( + """ + DELETE FROM "RiskFlags" WHERE "SeasonId" IN (999, 1000, 1001); + DELETE FROM "Seasons" WHERE "Id" IN (999, 1000, 1001) AND "IsDemo" = TRUE; + DELETE FROM "StreamerIdentities" WHERE "Id" BETWEEN 3001 AND 3020; + """ + ); + } +} diff --git a/Backend/Migrations/AwardsDbContextModelSnapshot.cs b/Backend/Migrations/AwardsDbContextModelSnapshot.cs index d760a6c..e9de46e 100644 --- a/Backend/Migrations/AwardsDbContextModelSnapshot.cs +++ b/Backend/Migrations/AwardsDbContextModelSnapshot.cs @@ -109,56 +109,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Results"); - - b.HasData( - new - { - Id = 1, - CandidateId = 8, - CategoryId = 5, - CategoryName = "VTuber des Jahres", - SeasonId = 2 - }, - new - { - Id = 2, - CandidateId = 9, - CategoryId = 6, - CategoryName = "Bestes Live Event", - SeasonId = 2 - }, - new - { - Id = 3, - CandidateId = 10, - CategoryId = 7, - CategoryName = "Clip des Jahres", - SeasonId = 2 - }, - new - { - Id = 4, - CandidateId = 11, - CategoryId = 8, - CategoryName = "VTuber des Jahres", - SeasonId = 3 - }, - new - { - Id = 5, - CandidateId = 12, - CategoryId = 9, - CategoryName = "Clip des Jahres", - SeasonId = 3 - }, - new - { - Id = 6, - CandidateId = 13, - CategoryId = 10, - CategoryName = "VTuber des Jahres", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Candidate", b => @@ -237,164 +187,6 @@ namespace Backend.Migrations b.HasIndex("StreamerIdentityId"); b.ToTable("Candidates"); - - b.HasData( - new - { - Id = 1, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 2, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 3, - AcceptanceStatus = "open", - CategoryId = 1, - ChannelSlug = "@shiroch", - ClipEmbedStatus = "unchecked", - DisplayName = "Shiro Ch.", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 4, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 5, - AcceptanceStatus = "open", - CategoryId = 2, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura Showcase", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 1 - }, - new - { - Id = 6, - AcceptanceStatus = "open", - CategoryId = 3, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 7, - AcceptanceStatus = "open", - CategoryId = 4, - ChannelSlug = "@moonrelay", - ClipEmbedStatus = "unchecked", - DisplayName = "Moonrelay", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 1 - }, - new - { - Id = 8, - AcceptanceStatus = "open", - CategoryId = 5, - ChannelSlug = "@hoshimimiyu", - ClipEmbedStatus = "unchecked", - DisplayName = "Hoshimi Miyu", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 9, - AcceptanceStatus = "open", - CategoryId = 6, - ChannelSlug = "@kurainu", - ClipEmbedStatus = "unchecked", - DisplayName = "Kurainu 3D Live", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 10, - AcceptanceStatus = "open", - CategoryId = 7, - ChannelSlug = "@pyonkichikingdom", - ClipEmbedStatus = "unchecked", - DisplayName = "Pyonkichi Kingdom", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 2 - }, - new - { - Id = 11, - AcceptanceStatus = "open", - CategoryId = 8, - ChannelSlug = "@aoisakura", - ClipEmbedStatus = "unchecked", - DisplayName = "Aoi Sakura", - NominationTally = 0, - Platform = "YouTube", - SeasonId = 3 - }, - new - { - Id = 12, - AcceptanceStatus = "open", - CategoryId = 9, - ChannelSlug = "@starbyte", - ClipEmbedStatus = "unchecked", - DisplayName = "Starbyte", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 3 - }, - new - { - Id = 13, - AcceptanceStatus = "open", - CategoryId = 10, - ChannelSlug = "@tenshivox", - ClipEmbedStatus = "unchecked", - DisplayName = "Tenshi Vox", - NominationTally = 0, - Platform = "Twitch", - SeasonId = 4 - }); }); modelBuilder.Entity("Backend.Domain.Category", b => @@ -445,118 +237,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Categories"); - - b.HasData( - new - { - Id = 1, - Description = "Die größte Auszeichnung des Jahres.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 1, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 2, - Description = "Events, Konzerte und 3D-Shows.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 1, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 3, - Description = "Der lustigste oder emotionalste Clip des Jahres.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 1, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 4, - Description = "Die aktivste und freundlichste Community.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "Beste Community", - SeasonId = 1, - Slug = "beste-community", - SortOrder = 4 - }, - new - { - Id = 5, - Description = "Archivkategorie 2025.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 2, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 6, - Description = "Archivkategorie 2025.", - GroupName = "Performance", - MaxNomineesPerUser = 3, - Name = "Bestes Live Event", - SeasonId = 2, - Slug = "bestes-live-event", - SortOrder = 2 - }, - new - { - Id = 7, - Description = "Archivkategorie 2025.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 2, - Slug = "clip-des-jahres", - SortOrder = 3 - }, - new - { - Id = 8, - Description = "Archivkategorie 2024.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 3, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }, - new - { - Id = 9, - Description = "Archivkategorie 2024.", - GroupName = "Clips & Highlights", - MaxNomineesPerUser = 3, - Name = "Clip des Jahres", - SeasonId = 3, - Slug = "clip-des-jahres", - SortOrder = 2 - }, - new - { - Id = 10, - Description = "Archivkategorie 2023.", - GroupName = "Main Awards", - MaxNomineesPerUser = 3, - Name = "VTuber des Jahres", - SeasonId = 4, - Slug = "vtuber-des-jahres", - SortOrder = 1 - }); }); modelBuilder.Entity("Backend.Domain.ClipSubmission", b => @@ -770,36 +450,6 @@ namespace Backend.Migrations b.HasIndex("SeasonId", "StreamerIdentityId", "CategoryGroupName"); b.ToTable("Nominations"); - - b.HasData( - new - { - Id = 1, - CandidateText = "Hoshimi Miyu", - CategoryGroupName = "", - 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", - TrackerStatus = "pending", - TrackingFlagsJson = "[]", - TrackingReviewStatus = "clear" - }, - new - { - Id = 2, - CandidateText = "Kurainu 3D Live", - CategoryGroupName = "", - 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", - TrackerStatus = "pending", - TrackingFlagsJson = "[]", - TrackingReviewStatus = "clear" - }); }); modelBuilder.Entity("Backend.Domain.RiskFlag", b => @@ -896,6 +546,11 @@ namespace Backend.Migrations b.Property("IsCurrent") .HasColumnType("boolean"); + b.Property("IsDemo") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + b.Property("Name") .IsRequired() .HasMaxLength(160) @@ -919,11 +574,6 @@ namespace Backend.Migrations b.Property("ShowStartsAt") .HasColumnType("time without time zone"); - b.Property("ShowStreamUrl") - .IsRequired() - .HasMaxLength(400) - .HasColumnType("character varying(400)"); - b.Property("SubcategoryTemplatesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -936,6 +586,13 @@ namespace Backend.Migrations b.Property("VotingStartsAt") .HasColumnType("date"); + b.Property("WinnersPublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WinnersPublishedByTwitchId") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + b.Property("WorkflowRulesJson") .IsRequired() .ValueGeneratedOnAdd() @@ -951,88 +608,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("Seasons"); - - b.HasData( - new - { - Id = 1, - CurrentPhase = "Community Voting", - IsCommunityOnly = true, - IsCurrent = true, - Name = "VTuber Star Awards 2026", - NominationEndsAt = new DateOnly(2026, 5, 31), - NominationStartsAt = new DateOnly(2026, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2026, 6, 30), - VotingStartsAt = new DateOnly(2026, 6, 1), - WorkflowRulesJson = "[]", - Year = 2026 - }, - new - { - Id = 2, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2025", - NominationEndsAt = new DateOnly(2025, 5, 31), - NominationStartsAt = new DateOnly(2025, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2025, 6, 30), - VotingStartsAt = new DateOnly(2025, 6, 1), - WorkflowRulesJson = "[]", - Year = 2025 - }, - new - { - Id = 3, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2024", - NominationEndsAt = new DateOnly(2024, 5, 31), - NominationStartsAt = new DateOnly(2024, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2024, 6, 30), - VotingStartsAt = new DateOnly(2024, 6, 1), - WorkflowRulesJson = "[]", - Year = 2024 - }, - new - { - Id = 4, - CurrentPhase = "Archived", - IsCommunityOnly = true, - IsCurrent = false, - Name = "VTuber Star Awards 2023", - NominationEndsAt = new DateOnly(2023, 5, 31), - NominationStartsAt = new DateOnly(2023, 5, 1), - 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", - SubcategoryTemplatesJson = "[]", - VotingEndsAt = new DateOnly(2023, 6, 30), - VotingStartsAt = new DateOnly(2023, 6, 1), - WorkflowRulesJson = "[]", - Year = 2023 - }); }); modelBuilder.Entity("Backend.Domain.ShowactApplication", b => @@ -1134,6 +709,14 @@ namespace Backend.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("AwardsSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("AwardsSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("ClipAdminMenuVisible") .HasColumnType("boolean"); @@ -1321,6 +904,70 @@ namespace Backend.Migrations .HasColumnType("boolean") .HasDefaultValue(true); + b.Property("StreamBannerCompletedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerCompletedEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerCompletedText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerCompletedTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerEyebrow") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerLiveButtonUrl") + .IsRequired() + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("StreamBannerLockedButtonLabel") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("StreamBannerText") + .IsRequired() + .HasColumnType("text"); + + b.Property("StreamBannerTitle") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("StreamBannerUseCompletedContent") + .HasColumnType("boolean"); + + b.Property("SubcategoriesSectionDescription") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubcategoriesSectionTitle") + .IsRequired() + .HasColumnType("text"); + b.Property("TrackingReviewNotes") .IsRequired() .HasColumnType("text"); @@ -1368,61 +1015,6 @@ namespace Backend.Migrations b.HasKey("Id"); b.ToTable("SiteSettings"); - - b.HasData( - new - { - Id = 1, - ClipAdminMenuVisible = true, - ClipReviewEnabled = true, - ClipSubmissionDisabledMessage = "Clip-Einreichungen sind aktuell geschlossen.", - ClipSubmissionsEnabled = false, - ContactContent = "Kontakt zum Award-Team\nDu hast Fragen zur Nominierung, zum Voting, zu Clips oder zur Show? Schreib dem Team über die hinterlegte Kontaktseite.\n\nDatenschutzfragen\nFür Datenschutzanfragen nutze bitte die Datenschutz-E-Mail aus diesem Footer.\n\nCommunity & Kooperationen\nSocial Links und Partneranfragen werden vom Admin-Team gepflegt und koordiniert.", - 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", - ImprintContent = "Anbieter\nVTuber Star Awards, vertreten durch Jayuhime.\n\nKontakt\nNutze für organisatorische Fragen bitte die Kontaktseite oder die hinterlegte Kontaktadresse.\n\nHinweis\nDieses Impressum ist ein redaktioneller Platzhalter und sollte vor dem Livegang mit den tatsächlichen Anbieterangaben ersetzt werden.", - 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", - NominationLinkBlacklistJson = "[{\"Url\":\"https://kick.com/\"},{\"Url\":\"https://www.twitch.tv/\"},{\"Url\":\"https://www.youtube.com/\"}]", - 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 = "[{\"key\":\"resubmitted_ballot\",\"label\":\"Ballot erneut gespeichert\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert.\"},{\"key\":\"rapid_vote_updates\",\"label\":\"Voting-Burst\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest.\"},{\"key\":\"resubmitted_nomination\",\"label\":\"Nominierung erneut eingereicht\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"low\",\"description\":\"Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert.\"},{\"key\":\"rapid_nomination_burst\",\"label\":\"Nominierungs-Burst\",\"enabled\":true,\"threshold\":10,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet.\"},{\"key\":\"duplicate_clip_submission\",\"label\":\"Doppelter Clip\",\"enabled\":true,\"threshold\":1,\"windowMinutes\":360,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht.\"},{\"key\":\"rapid_clip_burst\",\"label\":\"Clip-Burst\",\"enabled\":true,\"threshold\":5,\"windowMinutes\":10,\"severity\":\"high\",\"description\":\"Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet.\"},{\"key\":\"rapid_login_ip\",\"label\":\"Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen.\"},{\"key\":\"rapid_demo_login_ip\",\"label\":\"Demo-Login-Burst pro IP\",\"enabled\":true,\"threshold\":3,\"windowMinutes\":15,\"severity\":\"medium\",\"description\":\"Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen.\"}]", - SessionIdleTimeoutHours = 3, - ShareDiscordUrl = "", - ShareXUrl = "", - ShowactApplicationDisabledMessage = "Showact-Bewerbungen sind aktuell geschlossen.", - ShowactApplicationsEnabled = false, - ShowactFormSchemaJson = "[]", - ShowactsContent = "Informationen zu Showact-Bewerbungen, Ablauf und Kontakt für die Award-Show.", - ShowactsUrl = "https://vtuber-star-awards.de/showacts", - 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}]", - SponsorsContent = "Sponsoren & Partner\nHier können Sponsor:innen, Medienpartner, Community-Partner und Supporter des VTuber Star Awards vorgestellt werden.\n\nPartner werden im Rahmen der Show und auf den öffentlichen Kontaktflächen genannt, sobald sie final bestätigt sind.", - SponsorsUrl = "https://vtuber-star-awards.de/partner", - SponsorsVisible = true, - TrackingReviewNotes = "Fallback-Quellen für manuelle Reviews:\\n- SullyGnome\\n- Twitch-Kanal direkt\\n\\nNutze diese Notizen für Edge Cases und manuelle Tier-Entscheidungen.", - TrackingRulesJson = "{\"source\":{\"providerKey\":\"twitchtracker\",\"baseUrl\":\"https://twitchtracker.com/api\",\"notesSummary\":\"TwitchTracker Basic API liefert aktuell Channel-Summary-Daten fuer 30 Tage. Andere Zeitfenster bleiben konfigurierbar, werden aber als manueller Review-Fall markiert.\",\"showManualReviewNotesInReview\":true},\"importantMetrics\":[{\"key\":\"avg_viewers\",\"label\":\"Avg Viewer\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Durchschnittliche Viewer fuer den gewaehlten Zeitraum.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"avg_viewers\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"tracker_status\",\"label\":\"Tracker-Status\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Zeigt, ob der TwitchTracker-Lookup sauber aufgeloest werden konnte.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":false,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"tracker_status\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"tracker_checked_at\",\"label\":\"Letzter Tracker-Check\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Zeitpunkt der letzten automatischen Datenaufloesung.\",\"requiredForAutoClassification\":true,\"showInReview\":true,\"showInAdminSummary\":false,\"manualOverrideAllowed\":false,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"tracker_checked_at\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null}],\"optionalMetrics\":[{\"key\":\"hours_streamed\",\"label\":\"Hours Streamed\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Gesamte Streamstunden im gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"hours_streamed\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"hours_watched\",\"label\":\"Hours Watched\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Gesamte Watch Time fuer den gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"hours_watched\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"peak_viewers\",\"label\":\"Peak Viewer\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Hoechster gleichzeitiger Zuschauerwert im Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"peak_viewers\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"followers_gained\",\"label\":\"Follower Growth\",\"enabled\":true,\"sourceSupport\":\"auto\",\"description\":\"Follower-Zuwachs im gewaehlten Zeitraum.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"30d\",\"autoSupportedWindowKeys\":[\"30d\"],\"providerFieldKey\":\"followers_gained\",\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"category_fit\",\"label\":\"Category Fit\",\"enabled\":false,\"sourceSupport\":\"context_only\",\"description\":\"Admin-Einschaetzung, ob die Person inhaltlich zur Unterkategorie passt.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":false,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[],\"providerFieldKey\":null,\"topCount\":null,\"minPrimaryCategorySharePercent\":null,\"minPrimaryCategoryHours\":null,\"maxDistinctCategoriesBeforeFlag\":null,\"ignoredCategories\":[],\"matchAwardCategoryAgainstTopCategories\":false,\"flagIfAwardCategoryNotInTopX\":false,\"flagIfCategorySpreadTooWide\":false,\"flagIfNoCategoryContextAvailable\":false,\"minValue\":null,\"maxValue\":null},{\"key\":\"top_categories_context\",\"label\":\"Top Categories Context\",\"enabled\":false,\"sourceSupport\":\"context_only\",\"description\":\"Manueller Kontext aus zuletzt meistgestreamten Kategorien oder Games des Channels.\",\"requiredForAutoClassification\":false,\"showInReview\":true,\"showInAdminSummary\":true,\"manualOverrideAllowed\":true,\"windowKey\":\"90d\",\"autoSupportedWindowKeys\":[],\"providerFieldKey\":null,\"topCount\":5,\"minPrimaryCategorySharePercent\":60,\"minPrimaryCategoryHours\":20,\"maxDistinctCategoriesBeforeFlag\":6,\"ignoredCategories\":[\"Just Chatting\",\"Special Events\"],\"matchAwardCategoryAgainstTopCategories\":true,\"flagIfAwardCategoryNotInTopX\":true,\"flagIfCategorySpreadTooWide\":true,\"flagIfNoCategoryContextAvailable\":true,\"minValue\":null,\"maxValue\":null}],\"flags\":[{\"key\":\"tracker_unresolved\",\"label\":\"Tracker-Link nicht aufloesbar\",\"enabled\":true,\"severity\":\"high\",\"description\":\"Der Stream-Link konnte nicht sauber in einen Twitch-Channel aufgeloest werden.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"unsupported_platform\",\"label\":\"Plattform nicht unterstuetzt\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die Nominierung zeigt auf eine Plattform ohne automatische TwitchTracker-Daten.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"no_tracker_data\",\"label\":\"Keine Tracker-Daten\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"TwitchTracker hat keinen belastbaren Summary-Wert geliefert.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"missing_required_metric\",\"label\":\"Pflichtmetrik fehlt\",\"enabled\":true,\"severity\":\"high\",\"description\":\"Mindestens eine aktivierte Pflichtmetrik fuer die Auto-Klassifizierung fehlt.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":true,\"adminNoteRequiredOnOverride\":false},{\"key\":\"manual_review_required\",\"label\":\"Manuelle Pruefung noetig\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die Auto-Daten reichen nicht fuer eine sichere Review-Entscheidung.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"low_confidence_small_channel\",\"label\":\"Low Confidence Small Channel\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Kleine Kanaele koennen manuell tiefer geprueft werden.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"insufficient_activity_context\",\"label\":\"Zu wenig Aktivitaetskontext\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Ohne zusaetzliche Kontextdaten wie Streamstunden ist die Bewertung unsicher.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"category_fit_needs_review\",\"label\":\"Category Fit manuell pruefen\",\"enabled\":false,\"severity\":\"low\",\"description\":\"Unterkategorie muss inhaltlich manuell bestaetigt werden.\",\"autoTriggerEnabled\":false,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false},{\"key\":\"unsupported_metric_window\",\"label\":\"Gewaehltes Zeitfenster nicht auto-verfuegbar\",\"enabled\":true,\"severity\":\"medium\",\"description\":\"Die aktuelle TwitchTracker API liefert diese Metrik nicht fuer das konfigurierte Zeitfenster.\",\"autoTriggerEnabled\":true,\"requiresManualReview\":true,\"blocksApproval\":false,\"adminNoteRequiredOnOverride\":false}]}", - TwitchAuthManagedByDatabase = false, - TwitchClientId = "", - TwitchClientSecret = "", - TwitchRedirectUri = "", - TwitchScope = "", - ViewerStatsProviderBaseUrl = "https://twitchtracker.com/api", - WorkflowRulesJson = "[{\"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.\"},{\"key\":\"recommended_nominators_per_subcategory\",\"label\":\"Empfohlene Nominierer pro Unterkategorie\",\"enabled\":true,\"limit\":4,\"mode\":\"warn\",\"description\":\"Zeigt in der Kategorie-\\u00DCbersicht an, ab wann eine Unterkategorie nominierungsseitig gut getragen ist. Diese Regel blockiert nichts.\"}]" - }); }); modelBuilder.Entity("Backend.Domain.Sponsor", b => @@ -1721,24 +1313,6 @@ namespace Backend.Migrations .IsUnique(); b.ToTable("VoteBallots"); - - b.HasData( - new - { - Id = 1, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_1" - }, - new - { - Id = 2, - SeasonId = 1, - Status = "submitted", - SubmittedAt = new DateTimeOffset(new DateTime(2026, 6, 11, 12, 5, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), - SubmittedByTwitchId = "twitch_vote_2" - }); }); modelBuilder.Entity("Backend.Domain.VoteEntry", b => @@ -1767,36 +1341,6 @@ namespace Backend.Migrations b.HasIndex("CategoryId"); b.ToTable("VoteEntries"); - - b.HasData( - new - { - Id = 1, - BallotId = 1, - CandidateId = 1, - CategoryId = 1 - }, - new - { - Id = 2, - BallotId = 1, - CandidateId = 4, - CategoryId = 2 - }, - new - { - Id = 3, - BallotId = 2, - CandidateId = 2, - CategoryId = 1 - }, - new - { - Id = 4, - BallotId = 2, - CandidateId = 6, - CategoryId = 3 - }); }); modelBuilder.Entity("Backend.Domain.AwardResult", b => @@ -1869,6 +1413,12 @@ namespace Backend.Migrations .HasForeignKey("CandidateId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Backend.Domain.Season", null) + .WithMany() + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Candidate"); }); diff --git a/Backend/Migrations/InitialCreate.manual.sql b/Backend/Migrations/InitialCreate.manual.sql deleted file mode 100644 index 9658792..0000000 --- a/Backend/Migrations/InitialCreate.manual.sql +++ /dev/null @@ -1,159 +0,0 @@ -CREATE TABLE "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -CREATE TABLE "Seasons" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "Year" integer NOT NULL, - "Name" character varying(160) NOT NULL, - "IsCurrent" boolean NOT NULL, - "IsCommunityOnly" boolean NOT NULL, - "CurrentPhase" character varying(60) NOT NULL, - "NominationStartsAt" date NOT NULL, - "NominationEndsAt" date NOT NULL, - "VotingStartsAt" date NOT NULL, - "VotingEndsAt" date NOT NULL, - "ReviewStartsAt" date NOT NULL, - "ReviewEndsAt" date NOT NULL, - "ShowDate" date NOT NULL -); - -CREATE UNIQUE INDEX "IX_Seasons_Year" ON "Seasons" ("Year"); - -CREATE TABLE "Categories" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL REFERENCES "Seasons" ("Id") ON DELETE CASCADE, - "GroupName" character varying(80) NOT NULL, - "Name" character varying(120) NOT NULL, - "Slug" text NOT NULL, - "Description" character varying(400) NOT NULL, - "SortOrder" integer NOT NULL, - "MaxNomineesPerUser" integer NOT NULL -); - -CREATE UNIQUE INDEX "IX_Categories_SeasonId_Slug" ON "Categories" ("SeasonId", "Slug"); - -CREATE TABLE "VoteBallots" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL REFERENCES "Seasons" ("Id") ON DELETE CASCADE, - "SubmittedByTwitchId" character varying(120) NOT NULL, - "Status" character varying(30) NOT NULL, - "SubmittedAt" timestamp with time zone NOT NULL -); - -CREATE INDEX "IX_VoteBallots_SeasonId" ON "VoteBallots" ("SeasonId"); - -CREATE TABLE "Candidates" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL REFERENCES "Seasons" ("Id") ON DELETE CASCADE, - "CategoryId" integer NOT NULL REFERENCES "Categories" ("Id") ON DELETE CASCADE, - "DisplayName" character varying(120) NOT NULL, - "ChannelSlug" character varying(120) NOT NULL, - "Platform" character varying(40) NOT NULL -); - -CREATE INDEX "IX_Candidates_CategoryId" ON "Candidates" ("CategoryId"); -CREATE INDEX "IX_Candidates_SeasonId" ON "Candidates" ("SeasonId"); - -CREATE TABLE "Nominations" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL REFERENCES "Seasons" ("Id") ON DELETE CASCADE, - "CategoryId" integer NOT NULL REFERENCES "Categories" ("Id") ON DELETE CASCADE, - "SubmittedByTwitchId" character varying(120) NOT NULL, - "CandidateId" integer NULL REFERENCES "Candidates" ("Id"), - "CandidateText" character varying(120) NULL, - "CreatedAt" timestamp with time zone NOT NULL -); - -CREATE INDEX "IX_Nominations_CandidateId" ON "Nominations" ("CandidateId"); -CREATE INDEX "IX_Nominations_CategoryId" ON "Nominations" ("CategoryId"); -CREATE INDEX "IX_Nominations_SeasonId" ON "Nominations" ("SeasonId"); - -CREATE TABLE "Results" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "SeasonId" integer NOT NULL REFERENCES "Seasons" ("Id") ON DELETE CASCADE, - "CandidateId" integer NOT NULL REFERENCES "Candidates" ("Id") ON DELETE CASCADE, - "CategoryName" character varying(120) NOT NULL -); - -CREATE INDEX "IX_Results_CandidateId" ON "Results" ("CandidateId"); -CREATE INDEX "IX_Results_SeasonId" ON "Results" ("SeasonId"); - -CREATE TABLE "VoteEntries" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "BallotId" integer NOT NULL REFERENCES "VoteBallots" ("Id") ON DELETE CASCADE, - "CategoryId" integer NOT NULL REFERENCES "Categories" ("Id") ON DELETE CASCADE, - "CandidateId" integer NOT NULL REFERENCES "Candidates" ("Id") ON DELETE CASCADE -); - -CREATE INDEX "IX_VoteEntries_BallotId" ON "VoteEntries" ("BallotId"); -CREATE INDEX "IX_VoteEntries_CandidateId" ON "VoteEntries" ("CandidateId"); -CREATE INDEX "IX_VoteEntries_CategoryId" ON "VoteEntries" ("CategoryId"); - -INSERT INTO "Seasons" ("Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year") VALUES -(1, 'Community Voting', true, true, 'VTuber Star Awards 2026', '2026-05-31', '2026-05-01', '2026-07-10', '2026-07-01', '2026-07-20', '2026-06-30', '2026-06-01', 2026), -(2, 'Archived', true, false, 'VTuber Star Awards 2025', '2025-05-31', '2025-05-01', '2025-07-10', '2025-07-01', '2025-07-20', '2025-06-30', '2025-06-01', 2025), -(3, 'Archived', true, false, 'VTuber Star Awards 2024', '2024-05-31', '2024-05-01', '2024-07-10', '2024-07-01', '2024-07-20', '2024-06-30', '2024-06-01', 2024), -(4, 'Archived', true, false, 'VTuber Star Awards 2023', '2023-05-31', '2023-05-01', '2023-07-10', '2023-07-01', '2023-07-20', '2023-06-30', '2023-06-01', 2023); - -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") VALUES -(1, 'Die groesste Auszeichnung des Jahres.', 'Main Awards', 3, 'VTuber des Jahres', 1, 'vtuber-des-jahres', 1), -(2, 'Events, Konzerte und 3D-Shows.', 'Performance', 3, 'Bestes Live Event', 1, 'bestes-live-event', 2), -(3, 'Der lustigste oder emotionalste Clip des Jahres.', 'Clips & Highlights', 3, 'Clip des Jahres', 1, 'clip-des-jahres', 3), -(4, 'Die aktivste und freundlichste Community.', 'Main Awards', 3, 'Beste Community', 1, 'beste-community', 4), -(5, 'Archivkategorie 2025.', 'Main Awards', 3, 'VTuber des Jahres', 2, 'vtuber-des-jahres', 1), -(6, 'Archivkategorie 2025.', 'Performance', 3, 'Bestes Live Event', 2, 'bestes-live-event', 2), -(7, 'Archivkategorie 2025.', 'Clips & Highlights', 3, 'Clip des Jahres', 2, 'clip-des-jahres', 3), -(8, 'Archivkategorie 2024.', 'Main Awards', 3, 'VTuber des Jahres', 3, 'vtuber-des-jahres', 1), -(9, 'Archivkategorie 2024.', 'Clips & Highlights', 3, 'Clip des Jahres', 3, 'clip-des-jahres', 2), -(10, 'Archivkategorie 2023.', 'Main Awards', 3, 'VTuber des Jahres', 4, 'vtuber-des-jahres', 1); - -INSERT INTO "VoteBallots" ("Id", "SeasonId", "Status", "SubmittedAt", "SubmittedByTwitchId") VALUES -(1, 1, 'submitted', '2026-06-11T12:00:00+00:00', 'twitch_vote_1'), -(2, 1, 'submitted', '2026-06-11T12:05:00+00:00', 'twitch_vote_2'); - -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") VALUES -(1, 1, '@hoshimimiyu', 'Hoshimi Miyu', 'Twitch', 1), -(2, 1, '@kurainu', 'Kurainu', 'Twitch', 1), -(3, 1, '@shiroch', 'Shiro Ch.', 'Twitch', 1), -(4, 2, '@kurainu', 'Kurainu 3D Live', 'Twitch', 1), -(5, 2, '@aoisakura', 'Aoi Sakura Showcase', 'YouTube', 1), -(6, 3, '@pyonkichikingdom', 'Pyonkichi Kingdom', 'Twitch', 1), -(7, 4, '@moonrelay', 'Moonrelay', 'Twitch', 1), -(8, 5, '@hoshimimiyu', 'Hoshimi Miyu', 'Twitch', 2), -(9, 6, '@kurainu', 'Kurainu 3D Live', 'Twitch', 2), -(10, 7, '@pyonkichikingdom', 'Pyonkichi Kingdom', 'Twitch', 2), -(11, 8, '@aoisakura', 'Aoi Sakura', 'YouTube', 3), -(12, 9, '@starbyte', 'Starbyte', 'Twitch', 3), -(13, 10, '@tenshivox', 'Tenshi Vox', 'Twitch', 4); - -INSERT INTO "Nominations" ("Id", "CandidateId", "CandidateText", "CategoryId", "CreatedAt", "SeasonId", "SubmittedByTwitchId") VALUES -(1, NULL, 'Hoshimi Miyu', 1, '2026-06-10T13:00:00+00:00', 1, 'twitch_hoshi'), -(2, NULL, 'Kurainu 3D Live', 2, '2026-06-10T14:00:00+00:00', 1, 'twitch_kurainu'); - -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") VALUES -(1, 8, 'VTuber des Jahres', 2), -(2, 9, 'Bestes Live Event', 2), -(3, 10, 'Clip des Jahres', 2), -(4, 11, 'VTuber des Jahres', 3), -(5, 12, 'Clip des Jahres', 3), -(6, 13, 'VTuber des Jahres', 4); - -INSERT INTO "VoteEntries" ("Id", "BallotId", "CandidateId", "CategoryId") VALUES -(1, 1, 1, 1), -(2, 1, 4, 2), -(3, 2, 2, 1), -(4, 2, 6, 3); - -SELECT setval(pg_get_serial_sequence('"Seasons"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "Seasons"; -SELECT setval(pg_get_serial_sequence('"Categories"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "Categories"; -SELECT setval(pg_get_serial_sequence('"VoteBallots"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "VoteBallots"; -SELECT setval(pg_get_serial_sequence('"Candidates"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "Candidates"; -SELECT setval(pg_get_serial_sequence('"Nominations"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "Nominations"; -SELECT setval(pg_get_serial_sequence('"Results"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "Results"; -SELECT setval(pg_get_serial_sequence('"VoteEntries"', 'Id'), COALESCE(MAX("Id"), 1), true) FROM "VoteEntries"; - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260617060000_InitialCreate', '8.0.11'); diff --git a/Backend/Migrations/InitialCreate.sql b/Backend/Migrations/InitialCreate.sql deleted file mode 100644 index e096097..0000000 --- a/Backend/Migrations/InitialCreate.sql +++ /dev/null @@ -1,258 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; - -CREATE TABLE "Seasons" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "Year" integer NOT NULL, - "Name" character varying(160) NOT NULL, - "IsCurrent" boolean NOT NULL, - "IsCommunityOnly" boolean NOT NULL, - "CurrentPhase" character varying(60) NOT NULL, - "NominationStartsAt" date NOT NULL, - "NominationEndsAt" date NOT NULL, - "VotingStartsAt" date NOT NULL, - "VotingEndsAt" date NOT NULL, - "ReviewStartsAt" date NOT NULL, - "ReviewEndsAt" date NOT NULL, - "ShowDate" date NOT NULL, - CONSTRAINT "PK_Seasons" PRIMARY KEY ("Id") -); - -CREATE TABLE "Categories" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "SeasonId" integer NOT NULL, - "GroupName" character varying(80) NOT NULL, - "Name" character varying(120) NOT NULL, - "Slug" text NOT NULL, - "Description" character varying(400) NOT NULL, - "SortOrder" integer NOT NULL, - "MaxNomineesPerUser" integer NOT NULL, - CONSTRAINT "PK_Categories" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Categories_Seasons_SeasonId" FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id") ON DELETE CASCADE -); - -CREATE TABLE "VoteBallots" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "SeasonId" integer NOT NULL, - "SubmittedByTwitchId" character varying(120) NOT NULL, - "Status" character varying(30) NOT NULL, - "SubmittedAt" timestamp with time zone NOT NULL, - CONSTRAINT "PK_VoteBallots" PRIMARY KEY ("Id"), - CONSTRAINT "FK_VoteBallots_Seasons_SeasonId" FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id") ON DELETE CASCADE -); - -CREATE TABLE "Candidates" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "SeasonId" integer NOT NULL, - "CategoryId" integer NOT NULL, - "DisplayName" character varying(120) NOT NULL, - "ChannelSlug" character varying(120) NOT NULL, - "Platform" character varying(40) NOT NULL, - CONSTRAINT "PK_Candidates" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Candidates_Categories_CategoryId" FOREIGN KEY ("CategoryId") REFERENCES "Categories" ("Id") ON DELETE CASCADE, - CONSTRAINT "FK_Candidates_Seasons_SeasonId" FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id") ON DELETE CASCADE -); - -CREATE TABLE "Nominations" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "SeasonId" integer NOT NULL, - "CategoryId" integer NOT NULL, - "SubmittedByTwitchId" character varying(120) NOT NULL, - "CandidateId" integer, - "CandidateText" character varying(120), - "CreatedAt" timestamp with time zone NOT NULL, - CONSTRAINT "PK_Nominations" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Nominations_Candidates_CandidateId" FOREIGN KEY ("CandidateId") REFERENCES "Candidates" ("Id"), - CONSTRAINT "FK_Nominations_Categories_CategoryId" FOREIGN KEY ("CategoryId") REFERENCES "Categories" ("Id") ON DELETE CASCADE, - CONSTRAINT "FK_Nominations_Seasons_SeasonId" FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id") ON DELETE CASCADE -); - -CREATE TABLE "Results" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "SeasonId" integer NOT NULL, - "CandidateId" integer NOT NULL, - "CategoryName" character varying(120) NOT NULL, - CONSTRAINT "PK_Results" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Results_Candidates_CandidateId" FOREIGN KEY ("CandidateId") REFERENCES "Candidates" ("Id") ON DELETE CASCADE, - CONSTRAINT "FK_Results_Seasons_SeasonId" FOREIGN KEY ("SeasonId") REFERENCES "Seasons" ("Id") ON DELETE CASCADE -); - -CREATE TABLE "VoteEntries" ( - "Id" integer GENERATED BY DEFAULT AS IDENTITY, - "BallotId" integer NOT NULL, - "CategoryId" integer NOT NULL, - "CandidateId" integer NOT NULL, - CONSTRAINT "PK_VoteEntries" PRIMARY KEY ("Id"), - CONSTRAINT "FK_VoteEntries_Candidates_CandidateId" FOREIGN KEY ("CandidateId") REFERENCES "Candidates" ("Id") ON DELETE CASCADE, - CONSTRAINT "FK_VoteEntries_Categories_CategoryId" FOREIGN KEY ("CategoryId") REFERENCES "Categories" ("Id") ON DELETE CASCADE, - CONSTRAINT "FK_VoteEntries_VoteBallots_BallotId" FOREIGN KEY ("BallotId") REFERENCES "VoteBallots" ("Id") ON DELETE CASCADE -); - -INSERT INTO "Seasons" ("Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year") -VALUES (1, 'Community Voting', TRUE, TRUE, 'VTuber Star Awards 2026', DATE '2026-05-31', DATE '2026-05-01', DATE '2026-07-10', DATE '2026-07-01', DATE '2026-07-20', DATE '2026-06-30', DATE '2026-06-01', 2026); -INSERT INTO "Seasons" ("Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year") -VALUES (2, 'Archived', TRUE, FALSE, 'VTuber Star Awards 2025', DATE '2025-05-31', DATE '2025-05-01', DATE '2025-07-10', DATE '2025-07-01', DATE '2025-07-20', DATE '2025-06-30', DATE '2025-06-01', 2025); -INSERT INTO "Seasons" ("Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year") -VALUES (3, 'Archived', TRUE, FALSE, 'VTuber Star Awards 2024', DATE '2024-05-31', DATE '2024-05-01', DATE '2024-07-10', DATE '2024-07-01', DATE '2024-07-20', DATE '2024-06-30', DATE '2024-06-01', 2024); -INSERT INTO "Seasons" ("Id", "CurrentPhase", "IsCommunityOnly", "IsCurrent", "Name", "NominationEndsAt", "NominationStartsAt", "ReviewEndsAt", "ReviewStartsAt", "ShowDate", "VotingEndsAt", "VotingStartsAt", "Year") -VALUES (4, 'Archived', TRUE, FALSE, 'VTuber Star Awards 2023', DATE '2023-05-31', DATE '2023-05-01', DATE '2023-07-10', DATE '2023-07-01', DATE '2023-07-20', DATE '2023-06-30', DATE '2023-06-01', 2023); - -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (1, 'Die groesste Auszeichnung des Jahres.', 'Main Awards', 3, 'VTuber des Jahres', 1, 'vtuber-des-jahres', 1); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (2, 'Events, Konzerte und 3D-Shows.', 'Performance', 3, 'Bestes Live Event', 1, 'bestes-live-event', 2); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (3, 'Der lustigste oder emotionalste Clip des Jahres.', 'Clips & Highlights', 3, 'Clip des Jahres', 1, 'clip-des-jahres', 3); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (4, 'Die aktivste und freundlichste Community.', 'Main Awards', 3, 'Beste Community', 1, 'beste-community', 4); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (5, 'Archivkategorie 2025.', 'Main Awards', 3, 'VTuber des Jahres', 2, 'vtuber-des-jahres', 1); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (6, 'Archivkategorie 2025.', 'Performance', 3, 'Bestes Live Event', 2, 'bestes-live-event', 2); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (7, 'Archivkategorie 2025.', 'Clips & Highlights', 3, 'Clip des Jahres', 2, 'clip-des-jahres', 3); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (8, 'Archivkategorie 2024.', 'Main Awards', 3, 'VTuber des Jahres', 3, 'vtuber-des-jahres', 1); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (9, 'Archivkategorie 2024.', 'Clips & Highlights', 3, 'Clip des Jahres', 3, 'clip-des-jahres', 2); -INSERT INTO "Categories" ("Id", "Description", "GroupName", "MaxNomineesPerUser", "Name", "SeasonId", "Slug", "SortOrder") -VALUES (10, 'Archivkategorie 2023.', 'Main Awards', 3, 'VTuber des Jahres', 4, 'vtuber-des-jahres', 1); - -INSERT INTO "VoteBallots" ("Id", "SeasonId", "Status", "SubmittedAt", "SubmittedByTwitchId") -VALUES (1, 1, 'submitted', TIMESTAMPTZ '2026-06-11T12:00:00+00:00', 'twitch_vote_1'); -INSERT INTO "VoteBallots" ("Id", "SeasonId", "Status", "SubmittedAt", "SubmittedByTwitchId") -VALUES (2, 1, 'submitted', TIMESTAMPTZ '2026-06-11T12:05:00+00:00', 'twitch_vote_2'); - -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (1, 1, '@hoshimimiyu', 'Hoshimi Miyu', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (2, 1, '@kurainu', 'Kurainu', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (3, 1, '@shiroch', 'Shiro Ch.', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (4, 2, '@kurainu', 'Kurainu 3D Live', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (5, 2, '@aoisakura', 'Aoi Sakura Showcase', 'YouTube', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (6, 3, '@pyonkichikingdom', 'Pyonkichi Kingdom', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (7, 4, '@moonrelay', 'Moonrelay', 'Twitch', 1); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (8, 5, '@hoshimimiyu', 'Hoshimi Miyu', 'Twitch', 2); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (9, 6, '@kurainu', 'Kurainu 3D Live', 'Twitch', 2); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (10, 7, '@pyonkichikingdom', 'Pyonkichi Kingdom', 'Twitch', 2); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (11, 8, '@aoisakura', 'Aoi Sakura', 'YouTube', 3); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (12, 9, '@starbyte', 'Starbyte', 'Twitch', 3); -INSERT INTO "Candidates" ("Id", "CategoryId", "ChannelSlug", "DisplayName", "Platform", "SeasonId") -VALUES (13, 10, '@tenshivox', 'Tenshi Vox', 'Twitch', 4); - -INSERT INTO "Nominations" ("Id", "CandidateId", "CandidateText", "CategoryId", "CreatedAt", "SeasonId", "SubmittedByTwitchId") -VALUES (1, NULL, 'Hoshimi Miyu', 1, TIMESTAMPTZ '2026-06-10T13:00:00+00:00', 1, 'twitch_hoshi'); -INSERT INTO "Nominations" ("Id", "CandidateId", "CandidateText", "CategoryId", "CreatedAt", "SeasonId", "SubmittedByTwitchId") -VALUES (2, NULL, 'Kurainu 3D Live', 2, TIMESTAMPTZ '2026-06-10T14:00:00+00:00', 1, 'twitch_kurainu'); - -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (1, 8, 'VTuber des Jahres', 2); -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (2, 9, 'Bestes Live Event', 2); -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (3, 10, 'Clip des Jahres', 2); -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (4, 11, 'VTuber des Jahres', 3); -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (5, 12, 'Clip des Jahres', 3); -INSERT INTO "Results" ("Id", "CandidateId", "CategoryName", "SeasonId") -VALUES (6, 13, 'VTuber des Jahres', 4); - -INSERT INTO "VoteEntries" ("Id", "BallotId", "CandidateId", "CategoryId") -VALUES (1, 1, 1, 1); -INSERT INTO "VoteEntries" ("Id", "BallotId", "CandidateId", "CategoryId") -VALUES (2, 1, 4, 2); -INSERT INTO "VoteEntries" ("Id", "BallotId", "CandidateId", "CategoryId") -VALUES (3, 2, 2, 1); -INSERT INTO "VoteEntries" ("Id", "BallotId", "CandidateId", "CategoryId") -VALUES (4, 2, 6, 3); - -CREATE INDEX "IX_Candidates_CategoryId" ON "Candidates" ("CategoryId"); - -CREATE INDEX "IX_Candidates_SeasonId" ON "Candidates" ("SeasonId"); - -CREATE UNIQUE INDEX "IX_Categories_SeasonId_Slug" ON "Categories" ("SeasonId", "Slug"); - -CREATE INDEX "IX_Nominations_CandidateId" ON "Nominations" ("CandidateId"); - -CREATE INDEX "IX_Nominations_CategoryId" ON "Nominations" ("CategoryId"); - -CREATE INDEX "IX_Nominations_SeasonId" ON "Nominations" ("SeasonId"); - -CREATE INDEX "IX_Results_CandidateId" ON "Results" ("CandidateId"); - -CREATE INDEX "IX_Results_SeasonId" ON "Results" ("SeasonId"); - -CREATE UNIQUE INDEX "IX_Seasons_Year" ON "Seasons" ("Year"); - -CREATE INDEX "IX_VoteBallots_SeasonId" ON "VoteBallots" ("SeasonId"); - -CREATE INDEX "IX_VoteEntries_BallotId" ON "VoteEntries" ("BallotId"); - -CREATE INDEX "IX_VoteEntries_CandidateId" ON "VoteEntries" ("CandidateId"); - -CREATE INDEX "IX_VoteEntries_CategoryId" ON "VoteEntries" ("CategoryId"); - -SELECT setval( - pg_get_serial_sequence('"Seasons"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "Seasons") + 1, - nextval(pg_get_serial_sequence('"Seasons"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"Categories"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "Categories") + 1, - nextval(pg_get_serial_sequence('"Categories"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"VoteBallots"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "VoteBallots") + 1, - nextval(pg_get_serial_sequence('"VoteBallots"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"Candidates"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "Candidates") + 1, - nextval(pg_get_serial_sequence('"Candidates"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"Nominations"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "Nominations") + 1, - nextval(pg_get_serial_sequence('"Nominations"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"Results"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "Results") + 1, - nextval(pg_get_serial_sequence('"Results"', 'Id'))), - false); -SELECT setval( - pg_get_serial_sequence('"VoteEntries"', 'Id'), - GREATEST( - (SELECT MAX("Id") FROM "VoteEntries") + 1, - nextval(pg_get_serial_sequence('"VoteEntries"', 'Id'))), - false); - -INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") -VALUES ('20260617060000_InitialCreate', '8.0.11'); - -COMMIT; - diff --git a/Backend/README.md b/Backend/README.md index c4274ed..889f503 100644 --- a/Backend/README.md +++ b/Backend/README.md @@ -22,15 +22,8 @@ The API reads its connection string from: - environment variable `VTSA_POSTGRES` - environment variable `ConnectionStrings__Postgres` -Presentation/demo data is controlled separately: - -```text -VTSA_SEED_MODE=demo -``` - -- `demo`, `presentation` or `sample`: seed local presentation data. -- `none`, `off`, `disabled` or an unset value in Production: do not seed presentation data. -- `Backend/appsettings.Development.json` defaults to `demo`; `Backend/appsettings.json` defaults to `none`. +Demo content is inserted through explicit demo-data migrations, not runtime startup seeding. +Before production, create or activate a real season and delete any season marked as demo. The default app configuration migration contains only non-secret placeholders; restore private Twitch/demo/team credentials from environment variables or a local ignored backup. If Docker is available locally, start a dev database from the repository root with: @@ -50,13 +43,13 @@ dotnet build Create a migration: ```bash -dotnet ef migrations add InitialCreate +dotnet ef migrations add ``` -Generate a SQL migration script: +Generate a SQL migration script when deployment needs a reviewed artifact: ```bash -dotnet ef migrations script 0 20260617060000_InitialCreate --output Migrations/InitialCreate.sql +dotnet ef migrations script --output Migrations/migration.sql ``` Apply migrations once PostgreSQL is running: @@ -65,12 +58,6 @@ Apply migrations once PostgreSQL is running: dotnet ef database update ``` -Fallback bootstrap if `dotnet ef` is not usable in the current environment: - -```bash -psql "$VTSA_POSTGRES" -f Migrations/InitialCreate.manual.sql -``` - Run the API: ```bash diff --git a/Backend/Security/AdminPermissionCatalog.cs b/Backend/Security/AdminPermissionCatalog.cs index f770a2f..9eeee73 100644 --- a/Backend/Security/AdminPermissionCatalog.cs +++ b/Backend/Security/AdminPermissionCatalog.cs @@ -15,6 +15,7 @@ public static class AdminPermissionCatalog public const string Risk = "risk"; public const string Audit = "audit"; public const string Analytics = "analytics"; + public const string Voting = "voting"; public const string Winners = "winners"; public const string Content = "content"; public const string Settings = "settings"; @@ -31,6 +32,7 @@ public static class AdminPermissionCatalog Risk, Audit, Analytics, + Voting, Winners, Content, Settings, @@ -46,6 +48,7 @@ public static class AdminPermissionCatalog Candidates, Clips, Analytics, + Voting, Winners, ]; @@ -59,9 +62,9 @@ public static class AdminPermissionCatalog AdminRoles.Owner => AllPermissionKeys, AdminRoles.Creator => AllPermissionKeys, AdminRoles.Admin => AllPermissionKeys, - AdminRoles.Member => [Dashboard, Nominations, Categories, Candidates, Clips, Content], + AdminRoles.Member => [Dashboard, Nominations, Categories, Candidates, Clips, Voting, Content], AdminRoles.Reviewer => [Dashboard, Nominations, Clips, Risk, Audit], - AdminRoles.OrganizationTeam => [Dashboard, Content, Analytics, Winners, Settings], + AdminRoles.OrganizationTeam => [Dashboard, Content, Analytics, Voting, Winners, Settings], AdminRoles.ContentAdmin => [Content, Settings], _ => [], }; @@ -86,6 +89,14 @@ public static class AdminPermissionCatalog permissions.Add(Settings); } + var hasAnalytics = permissions.Contains(Analytics, StringComparer.OrdinalIgnoreCase); + var hasWinners = permissions.Contains(Winners, StringComparer.OrdinalIgnoreCase); + if ((hasAnalytics || hasWinners) + && !permissions.Contains(Voting, StringComparer.OrdinalIgnoreCase)) + { + permissions.Add(Voting); + } + return permissions .OrderBy(item => item) .ToArray(); diff --git a/Backend/appsettings.Development.json b/Backend/appsettings.Development.json index f6c765a..0f767cf 100644 --- a/Backend/appsettings.Development.json +++ b/Backend/appsettings.Development.json @@ -10,9 +10,6 @@ "ConnectionStrings": { "Postgres": "Host=localhost;Port=5433;Database=vtuber_star_awards_dev;Username=vtsa_dev;Password=change-me-local-only" }, - "SeedData": { - "Mode": "demo" - }, "DemoAdmin": { "Enabled": true, "Login": "jayuhime_admin", diff --git a/Backend/appsettings.json b/Backend/appsettings.json index 8359efd..543d3aa 100644 --- a/Backend/appsettings.json +++ b/Backend/appsettings.json @@ -5,9 +5,6 @@ "ConnectionStrings": { "Postgres": "" }, - "SeedData": { - "Mode": "none" - }, "DemoAdmin": { "Enabled": false, "Login": "", diff --git a/docs/CHECKLISTS.md b/docs/CHECKLISTS.md index dfecf0e..2e86f96 100644 --- a/docs/CHECKLISTS.md +++ b/docs/CHECKLISTS.md @@ -13,6 +13,7 @@ For workflow details, see [workflow.md](workflow.md). For code standards, see - [ ] Relevant docs and existing implementation were read. - [ ] Frontend/backend contract and source of truth are identified. - [ ] Permission, workflow, empty, loading, error, and disabled states are handled. +- [ ] Large touched files were checked against [maintainability-backlog.md](maintainability-backlog.md). - [ ] Docs are updated when behavior, setup, or architecture changed. - [ ] Frontend build, backend build, and targeted manual checks are run. diff --git a/docs/PROJECT.md b/docs/PROJECT.md index 0409470..989468d 100644 --- a/docs/PROJECT.md +++ b/docs/PROJECT.md @@ -136,6 +136,8 @@ settings, deployment, and responsive UI changes. | [workflow.md](workflow.md) | Day-to-day delivery flow. | | [branching.md](branching.md) | Branch and commit policy. | | [release-process.md](release-process.md) | Release, deploy, smoke test, and rollback expectations. | +| [end-to-end-smoke.md](end-to-end-smoke.md) | Repeatable local/browser smoke path for nomination, voting, winners, and permissions. | +| [maintainability-backlog.md](maintainability-backlog.md) | Large-file and boundary risks to reduce in focused refactor passes. | | [../DESIGN.md](../DESIGN.md) | Product visual language and UI implementation guidance. | | [workflow-feedback-plan.md](workflow-feedback-plan.md) | Product feedback implementation plan for awards workflow improvements. | diff --git a/docs/dashboard-plan.md b/docs/dashboard-plan.md new file mode 100644 index 0000000..a5a7ed2 --- /dev/null +++ b/docs/dashboard-plan.md @@ -0,0 +1,191 @@ +# Dashboard & Admin-Panel — Analyse + Plan + +> Status: **Umgesetzt.** Dieses Dokument beschreibt die Analyse, die umgesetzte +> Dashboard-Readiness-Ausrichtung und die noch bewusst zurückgestellte Phase 2. +> Das Dashboard ist saisonbezogen, bleibt read-only und verlinkt in die Fachseiten. + +--- + +## 0. Wichtigster Befund vorweg: gespaltene Datenquelle + +Das Dashboard mischt **zwei verschiedene Saison-Quellen**, und das ist die Wurzel der meisten Probleme: + +| Sektion | Datenquelle | Reagiert auf Jahr-Wechsel im Toolbar? | +|---|---|---| +| Metric-Cards (Nominierungen, Stimmen…) | `store.admin.metrics` → Backend `/dashboard` → **immer `IsCurrent`-Saison** | ❌ Nein | +| Top-Kategorien, Aktivitäten | `store.admin` (gleiche Quelle) | ❌ Nein | +| Jahreszahlen, Checks, Priority, Toolbar-Stats | `store.adminSeasonDetail` → **ausgewählte Saison** | ✅ Ja | + +Wenn der Host im Toolbar das Jahr wechselt, ändern sich **die Hälfte der Kacheln nicht**. +„Stimmen gesamt" (Hero) und „Stimmen gesamt" (Jahreszahlen) können unterschiedliche Werte +zeigen. Das muss vereinheitlicht werden, **bevor** irgendetwas Neues draufkommt. + +**Umgesetzt:** Die ausgewählte Saison (`adminSeasonDetail`) ist die führende +Dashboard-Quelle. `/api/admin/dashboard` akzeptiert optional `seasonId`; ohne +`seasonId` bleibt das alte `IsCurrent`-Verhalten kompatibel. + +--- + +## 1. Ist-Zustand: bestehende Komponenten + +**`AdminDashboardView.vue`** — Orchestrator, lädt alles aus `useAdminDashboardOverview()` +und arrangiert 6 Sektionen in Grids. Sauber, dünn, gut. + +**`AdminPageHeader`** — nur Eyebrow „Dashboard" + Icon. Keine Begrüßung, kein Kontext. + +**`AdminSeasonToolbar`** — Jahr-Auswahl + Phasen-Pill (nur Text) + 3 Mini-Stats +(Kategorien/Kandidaten/Reviews). Zeigt `isCurrent` als „Öffentlich sichtbar". +**Es fehlen alle Datumsangaben** (Nominierungsstart, Voting-Ende, Show-Termin), +obwohl sie in `adminSeasonDetail` vorliegen. + +**`AdminDashboardHeroSection`** — „Live-Lage" als generierter Fließband-Satz + Status-Badge ++ Metric-Cards mit `Quelle: VoteEntries-Tabelle`. Die Quell-Labels sind entwicklersprachlich, +nicht host-tauglich. + +**`AdminDashboardPrioritySection`** („Was zuerst?") — 4 Quick-Links (Reviews, Risiko, +Kategorien, Kandidaten) mit Permission-Filter. Gut gebaut. Aber statisch: zeigt immer +dieselben 4, unabhängig von der Phase. + +**`AdminDashboardChecksSection`** — 3 Betriebs-Checks (Kategorien ohne Kandidaten, +Review-Backlog, Risk Flags) mit Deep-Links und ok/warn/danger. Stärkste Sektion, +weil handlungsorientiert. + +**`AdminDashboardYearTotalsSection`** — 6 reine Zahlen. Überschneidet sich inhaltlich +stark mit den Hero-Metric-Cards (Nominierungen, Stimmen, Kategorien, Reviews, Risiko +tauchen doppelt auf). + +**`AdminDashboardTopCategoriesSection`** — Top 5 nach Stimmen mit Balken. +**Während der Nominierungsphase nutzlos** (Stimmen = 0). + +**`AdminDashboardActivitySection`** — hart auf 3 Audit-Einträge gedeckelt, +**kein Link zum vollen Audit-Log**. + +--- + +## 2. Personas: was Host vs. Admin wirklich brauchen + +**Der Host (Jayuhime)** denkt in der **Timeline der Show**, nicht in Tabellen: +- „Wo stehen wir gerade, und wie lange noch?" (Countdown bis Phasen-Ende / Show) +- „Stimmt die gespeicherte Phase mit dem Zeitplan überein?" +- „Ist die öffentliche Seite bereit? Stream-Link gesetzt, keine Wartung an?" +- „Sind wir bereit, Gewinner zu verkünden?" +- „Was zeigt die Community gerade?" (Beteiligung wächst) + +**Der Admin / das Team** denkt **operativ**: +- „Was liegt in meiner Queue?" (Reviews, Risk, Clips) +- „Wo klemmt es?" (leere Kategorien, Backlog-Verteilung) +- „Wer hat zuletzt was geändert?" (Audit) + +Das aktuelle Dashboard bedient **fast nur die Admin-Sicht**. Die Host-Sicht +(Timeline, Bereitschaft, Public-Health) fehlt fast komplett. + +--- + +## 3. Verbesserungswürdig (bestehende Komponenten) + +1. **Datenquelle vereinheitlichen** (siehe §0) — Metrics/Top-Kategorien/Aktivitäten auf + die gewählte Saison umstellen. Empfehlung: gewählte Saison als einzige Quelle. +2. **Quell-Labels host-freundlich machen** — „Quelle: VoteEntries-Tabelle" → weg damit + oder „Aktualisiert aus dem Live-Voting". +3. **Redundanz Hero ↔ Jahreszahlen auflösen** — eine der beiden Zahlen-Wände streichen; + Hero = Live/Aktion, Jahreszahlen = Summen. +4. **Top-Kategorien phasenabhängig** — in Nominierungsphase „Top nach Nominierungen" + statt nach Stimmen zeigen. +5. **Priority-Liste phasenabhängig priorisieren** — in Show-Vorbereitung „Gewinner setzen" + nach oben, in Nominierung „Reviews" nach oben. +6. **Aktivitäten** — auf 5–6 erhöhen + „Alles ansehen"-Link zum Audit-Log. + +--- + +## 4. Was fehlt — neue Komponenten (mit Aufbau) + +### 4.1 `AdminDashboardTimelineStrip.vue` — Phasen-Timeline mit Countdown +- **Wofür:** Die zentrale Host-Frage „Wo stehen wir, wie lange noch?" auf einen Blick. +- **Was es macht:** Zeigt die 4 Phasen (Nominierung → Voting → Aufbereitung → Show) als + horizontalen Strip mit Datumsspannen; die aktive Phase ist hervorgehoben; ein großer + Countdown zeigt „noch X Tage bis Voting-Ende" bzw. „bis zur Show". Warnt sichtbar, + wenn die gespeicherte Phase vom Zeitplan abweicht (die Logik existiert bereits in + `AdminSeasonPhaseSwitcher` als `autoPhase` — wiederverwenden). +- **Aufbau:** Eigene Computed in `useAdminDashboardOverview` (`timelinePhases`, + `activeCountdown`, `phaseMismatch`) gespeist aus + `adminSeasonDetail.*StartsAt/*EndsAt/showDate`; Datums-/Zustandsmapping aus + `Common/SeasonMappings.cs` (`ResolveTimelineState`) spiegeln. Reine Props-Komponente, + `` mit 4 Phasen-Segmenten (analog zu `HomeTimelineSection` der Landingpage, + gleiche Farb-Token). Optional Deep-Link zu `/admin/years` für Phasenwechsel. + +### 4.2 `AdminDashboardReadinessCard.vue` — Show-Bereitschaft +- **Wofür:** „Sind wir bereit, live zu gehen / Gewinner zu verkünden?" +- **Was es macht:** Checkliste aus `votingWorkspace.summary`: votedSubcategories / + readySubcategories / **winnerSetSubcategories** vs. totalSubcategories, plus + „Show-Datum gesetzt", „Stream-URL gesetzt", „alle Gewinner gesetzt". Ein Fortschrittsring + „12 / 14 Unterkategorien gewinnerbereit". +- **Aufbau:** Computed `readinessItems` (Label, erfüllt-bool, Deep-Link). Quelle: + `adminSeasonDetail.votingWorkspace.summary` (bereits vorhanden, heute ungenutzt im + Dashboard!) + `showDate`/Stream-Banner-Link. `` mit Fortschrittsbalken + Liste mit + Häkchen/Warnungen. Nur sichtbar/relevant ab Voting-Phase. + +### 4.3 `AdminDashboardPublicHealthCard.vue` — Öffentliche Seite +- **Wofür:** Der Host muss sehen, was die Community sieht. +- **Was es macht:** Ampel für: Saison öffentlich (`isCurrent`), Wartungsmodus an/aus, + Stream-Link vorhanden, Pflicht-Content (Impressum/Datenschutz) gepflegt. Plus + „Landingpage ansehen"-Button. +- **Aufbau:** Zieht aus `adminOptionalFeatureSettings` / Operational-Settings (Wartung) + + `adminSiteSettings` (Content-Lücken). `` mit Status-Zeilen, Deep-Links nach + `/admin/content` und `/admin/settings/access`. Permission-gated auf `content`/`settings`. + +### 4.4 `AdminDashboardQueueCard.vue` — vereinte Team-Queue +- **Wofür:** Admins/Reviewer wollen „meine offenen Aufgaben" inkl. **Clips**, die heute + komplett fehlen. +- **Was es macht:** Zählt Reviews offen, Risk offen, **Clips pending** in einer Karte mit + je Deep-Link und Badge. Ersetzt/erweitert die heutige Priority-Sektion um den Clip-Strang. +- **Aufbau:** Computed `queueItems` aus `pendingNominations.length`, `getRiskMetricValue`, + `clipSubmissions.filter(pending)`; jeweils Permission-gated (Clip nur wenn + `clipAdminMenuVisible`). Listen-`` wie `AdminDashboardPrioritySection`, + wiederverwendbares Item-Markup. + +### 4.5 `AdminDashboardParticipationTrend.vue` (Phase 2) — Beteiligungsverlauf +- **Wofür:** „Wächst die Beteiligung?" — Motivation/Story für den Host. +- **Was es macht:** Mini-Sparkline Nominierungen/Stimmen über die letzten Tage. +- **Aufbau:** **Benötigt neues Backend** (Zeitreihe, heute liefert `/dashboard` nur Totals). + Daher klar als Phase-2 markieren, nicht im ersten Wurf. + +--- + +## 5. Was bewusst NICHT ins Dashboard kommt + +- **Tiefen-Analytics / Kategorie-Health-Matrix** → bleibt in `/admin/analytics` + (existiert dort bereits vollständig). Dashboard nur verlinken. +- **Voll-Editierbarkeit** (Phasen umschalten, Texte ändern, Gewinner setzen) → bleibt in + den Fachseiten. Dashboard ist **read + deep-link**, kein Editor. Ausnahme: höchstens + ein „Phase aktivieren"-Shortcut. +- **Volles Audit-Log mit Filtern** → bleibt in `/admin/users-logs`. Dashboard zeigt nur + die letzten 5 + Link. +- **Team-/Rollenverwaltung, Tracking-Rules, DB-Checks** → reine Settings, kein Tagesgeschäft. +- **Echtzeit-Sparkline** im ersten Release (Backend fehlt). + +--- + +## 6. Empfohlene Ziel-Struktur (Reihenfolge = Sichtbarkeit) + +``` +1. PageHeader + Begrüßung („Hi Jayuhime") + Datum +2. SeasonToolbar (+ Datumsspannen ergänzen) +3. TimelineStrip + Countdown ← NEU, Host-Anker +4. [ Hero/Live-Lage | Queue-Card ] ← Queue NEU (inkl. Clips) +5. ChecksSection (beibehalten) +6. [ ReadinessCard | PublicHealthCard ] ← beide NEU +7. [ YearTotals (entschlackt) | TopCategories (phasen-aware) ] +8. ActivitySection (5 Einträge + Link) +``` + +**Reihenfolge der Umsetzung:** +1. **Datenquelle vereinheitlichen** (§0) — Fundament, blockiert alles andere. +2. **TimelineStrip + Toolbar-Datumsangaben** — größter Host-Mehrwert, Daten schon da. +3. **Queue-Card (mit Clips) + Readiness-Card** — füllt die echten Lücken, Daten schon da. +4. **PublicHealth-Card** — kleiner, aber wertvoll. +5. **Entschlacken** (Redundanz Hero/Jahreszahlen, phasen-aware Top-Kategorien). +6. **Phase 2:** Beteiligungs-Sparkline (braucht Backend-Zeitreihe). + +> **Umgesetzt:** TimelineStrip, Readiness und Queue nutzen bestehende +> `adminSeasonDetail`-Daten. Der Dashboard-Endpoint ist zusätzlich saisonfähig, +> damit Metriken und Top-Kategorien beim Jahrwechsel konsistent bleiben. diff --git a/docs/end-to-end-smoke.md b/docs/end-to-end-smoke.md new file mode 100644 index 0000000..cb8e7c4 --- /dev/null +++ b/docs/end-to-end-smoke.md @@ -0,0 +1,136 @@ +# End-To-End Smoke Standard + +This checklist defines the repeatable smoke path for the core awards workflow. +It complements the build checks in `docs/workflow.md` and should be used after +changes to nominations, categories, candidates, voting, winners, permissions, +or public landing-page workflow. + +## Scope + +Core workflow: + +```text +Nomination -> Admin preparation -> Candidate -> Voting -> Winner preparation -> Winner/archive +``` + +Public nomination is grouped by main category (`Category.GroupName`). Voting and +winners stay on concrete subcategory `Category` rows. + +## Automated Local Smoke + +Run this against a local backend. If an admin session token is available, the +script also checks admin endpoints. + +```bash +scripts/smoke-local.sh +ADMIN_SESSION_TOKEN= scripts/smoke-local.sh +``` + +Expected local defaults: + +- Backend: `http://127.0.0.1:5084` +- Frontend: `http://127.0.0.1:5173` +- Database: PostgreSQL on `localhost:5433` + +The script checks: + +- `/api/health` +- `/api/health/database` +- `/api/public/overview` +- current-season category structure +- legacy parent labels are not exposed by the public category endpoint +- optional admin season/team endpoints when `ADMIN_SESSION_TOKEN` is set + +## Manual Browser Smoke + +Use the local frontend with backend running as a pair. + +### Public Nomination + +- Open the public landing page. +- Start nomination. +- Confirm only main categories are selectable. +- Submit 1, 2, and 3 links in one main category. +- Confirm duplicate links in the same main category are blocked. +- Confirm empty categories can be skipped. +- Confirm Twitch links get tracker status when possible. +- Confirm non-Twitch links are accepted and need manual tier choice. + +### Admin Review And Candidate Preparation + +- Open `/admin/nominations`. +- Confirm nominations are grouped by main category and streamer identity. +- Confirm the tracker tier suggestion is visible when available. +- Promote a Twitch nomination using the suggested tier. +- Promote a non-Twitch nomination with manual tier override. +- Confirm candidate `NominationTally` reflects grouped nominations. +- Confirm candidate clip metadata requires a link when winner clip is mandatory. + +### Voting + +- Open the public voting modal. +- Confirm categories show as main category with subcategories. +- Switch category and subcategory without the modal jumping or resizing. +- Vote in at least one subcategory. +- Save vote again and confirm this is treated as vote editing. +- Confirm missing-vote warning appears before completion when applicable. + +### Admin Voting Workspace + +- Open `/admin/voting`. +- Confirm the page appears only for roles with `voting`, `analytics`, or + `winners` effective access. +- Confirm the tree groups by main category, with subcategories as children. +- Confirm leaderboard order defaults to most votes first. +- Confirm vote count, candidate count, open reviews, clip status, readiness, + ties or close races, and soft "few nominators" warnings are visible. +- Use quick links to candidates, nominations/review, and winners. + +### Winners And Archive + +- Open `/admin/winners`. +- Confirm it focuses on final winner assignment and landing-page release readiness. +- Set or clear a winner in a subcategory. +- Confirm `max. 1x winner` identity guard is visible/enforced. +- Confirm missing winner clip blocks or warns according to workflow settings. +- Confirm publishing is blocked while winners, reviews, or blocking workflow + rules are incomplete. +- Publish the year and confirm current winners appear on the landing page while + the previous landing-page winner year moves into the archive list. +- Confirm current landing-page winners are not listed as an archive year at the + same time. +- Open the public archive modal and confirm winner clip/video embeds render. + +### Permissions + +- Open `/admin/team`. +- Confirm permission catalog includes current admin pages: + Dashboard, Jahre, Nominierungen, Kategorien, Kandidaten, Clips, Landingpage, + Risiko, Audit-Log, Analytics, Voting, Gewinner, Einstellungen, Team. +- Confirm roles with `analytics` or `winners` still get effective `voting` + access for navigation and route guards. +- Confirm write actions still fail for read-only organization-team access. + +### Responsive Checks + +Check public voting, admin voting, admin winners, and admin categories at: + +- `360px` +- `390px` +- `768px` +- desktop + +In the browser console: + +```js +document.documentElement.scrollWidth <= window.innerWidth +``` + +The expression must be `true` unless a deliberate horizontal data table is in a +contained scroll area. + +## Cleanup + +Temporary local nominations, candidates, votes, clips, and winners may be +created for smoke validation. Remove test artifacts before finishing if they +would pollute future manual checks. diff --git a/docs/maintainability-backlog.md b/docs/maintainability-backlog.md new file mode 100644 index 0000000..fa09c10 --- /dev/null +++ b/docs/maintainability-backlog.md @@ -0,0 +1,59 @@ +# Maintainability Backlog + +This backlog tracks large-file and boundary risks that should be reduced in +small, focused follow-up changes. It is intentionally separate from feature +plans so stabilization work does not become a hidden refactor. + +## Current Priority + +1. `Backend/Endpoints/AdminSiteSettingsEndpoints.cs` + - Risk: settings, content, operational toggles, OAuth/demo handling, and + mapping live in one endpoint file. + - Next split: move mapping/snapshot/change-detection helpers into a focused + support file or service. + +2. `frontend/src/components/admin/useAdminSeasonManager.ts` + - Risk: season forms, phase gateways, readiness facts, dashboard links, and + navigation helpers are coupled in one composable. + - Next split: extract phase-gateway/readiness builders into a pure + TypeScript helper with unit-test-friendly functions. + +3. `frontend/src/components/home/HomeExtrasSection.vue` + - Risk: public extras, showact/sponsor presentation, and responsive layout + are harder to reason about in one component. + - Next split: separate showact, sponsor, and footer/content blocks into + focused presentational components. + +4. `Backend/Endpoints/AdminSeasonDetailEndpoints.cs` + - Risk: one endpoint assembles categories, nominations, voting workspace, + winners, clips, rules, and mappings. + - Next split: move voting-workspace assembly into an admin voting service + and keep the endpoint as orchestration. + +5. `frontend/src/views/admin/AdminTeamView.vue` + - Risk: member table, role matrix, modals, permission explanations, and + actions share one route-level view. + - Next split: extract the permission matrix and member editor into separate + components. + +## Audit Command + +Run: + +```bash +scripts/file-size-audit.sh +``` + +Default warning threshold is 500 lines. Override with: + +```bash +MAX_LINES=650 scripts/file-size-audit.sh +``` + +## Refactor Rules + +- Do not combine these refactors with unrelated feature work. +- Prefer pure helper extraction before behavior changes. +- Keep route-level Vue views thin. +- Keep backend endpoint files focused on HTTP shape and orchestration. +- Re-run frontend/backend builds after any extraction. diff --git a/docs/release-process.md b/docs/release-process.md index 7848703..ac8c4d1 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -73,7 +73,8 @@ Minimum automated smoke signals: Add manual smoke checks for the changed workflow, especially for admin, auth/permissions, voting, nomination, clip review, showact, sponsor, or content -management changes. +management changes. Use [end-to-end-smoke.md](end-to-end-smoke.md) as the +standard core-workflow smoke checklist. ## Rollback And Recovery diff --git a/docs/workflow-feedback-plan.md b/docs/workflow-feedback-plan.md index eadf400..1d3ff84 100644 --- a/docs/workflow-feedback-plan.md +++ b/docs/workflow-feedback-plan.md @@ -154,6 +154,7 @@ Ziel: Auswertung und Gewinnerdarstellung folgen den internen Regeln. Umsetzung: - Gewinner pro Unterkategorie verwalten. +- Gewinner-Speichern bleibt intern; ein separater jahresweiter Freigabe-Button veroeffentlicht Gewinner auf der Landingpage und schiebt den bisherigen Landingpage-Jahrgang ins Archiv. - Guard fuer "eine Person gewinnt maximal ein Mal" einplanen. - Konfigurierbare Regel "Gewinner braucht Clip-Link" einplanen; Standard blockiert Gewinner ohne gepflegte YouTube-/Twitch-Compilation. - Guard oder Warnung fuer "eine Person maximal zwei Mal nominiert" einplanen. @@ -164,6 +165,7 @@ Umsetzung: Akzeptanz: - Admins koennen Gewinner nicht versehentlich doppelt vergeben, ohne Warnung oder bewusste Bestaetigung. +- Admins koennen ein vollstaendiges Award-Jahr bewusst veroeffentlichen; erst danach ersetzt es den vorherigen Landingpage-Gewinnerjahrgang. - Admins koennen steuern, ob Gewinner ohne Clip-Link blockiert oder nur gewarnt werden. - Archiv zeigt vorhandene Gewinner-Clips eingebettet an und bleibt bei fehlenden Clips stabil. - Countdown ist auf Desktop und Mobile gut lesbar. diff --git a/docs/workflow.md b/docs/workflow.md index 4c23fdf..c0296d6 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -87,6 +87,10 @@ dotnet build Backend/Backend.csproj --configuration Release git diff --check ``` +For core nomination, category, voting, winner, permission, or archive changes, +also run the repeatable smoke path in +[end-to-end-smoke.md](end-to-end-smoke.md). + Add targeted validation by risk: - API checks for backend behavior; diff --git a/frontend/src/components/admin/AdminCategoryGroupModal.vue b/frontend/src/components/admin/AdminCategoryGroupModal.vue index 585f09e..9eb005e 100644 --- a/frontend/src/components/admin/AdminCategoryGroupModal.vue +++ b/frontend/src/components/admin/AdminCategoryGroupModal.vue @@ -3,7 +3,7 @@ :open="open" size="lg" :title="title" - subtitle="Diese Hauptkategorie bekommt automatisch alle globalen Unterkategorien der ausgewaehlten Season." + subtitle="Diese Hauptkategorie bekommt automatisch alle globalen Unterkategorien der ausgewaehlten Season. Die Beschreibung erscheint auf der Landingpage in der Awards-Karte." @close="$emit('close')" >
@@ -43,11 +43,11 @@