Add viewer-range categories, nomination tracking, dynamic showact form, session timeout, share URLs, and workflow-per-season

Features:
- Category viewer ranges + subcategory templates (admin group modal, tree workspace)
- Nomination enrichment via TwitchTracker API (NominationEnrichmentService,
  TwitchTrackerViewerStatsProvider) with admin tracking rules editor
- Nomination group tracker: CategoryGroupName as primary identifier,
  CategoryId stays as nullable legacy field; StreamerIdentity table
- Dynamic showact application form builder (AdminShowactFormBuilder,
  ShowactApplicationSchedule)
- Session idle timeout setting (AdminSessionTimeoutCard)
- Share URLs for X and Discord (SiteSettings, public extras)
- Workflow rules now stored per season (falls back to global SiteSettings)
- New admin routes: settings/access, settings/workflows, tracking-rules
- New admin review workspace with subcategory tabs
- AdminCategoriesView rebuilt with group/subcategory modals

Migrations (all additive):
- AddShareUrls, AddShowactDynamicForm, AddCategoryViewerRanges,
  AddSessionIdleTimeoutSettings, AddSeasonSubcategoryTemplates,
  AddNominationGroupTrackerIdentity, AddShowactApplicationSchedule,
  AddSeasonWorkflowRulesJson

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-06-28 23:32:21 +02:00
parent 4b2c5fa15d
commit b53c7fb736
178 changed files with 28933 additions and 1908 deletions
@@ -19,6 +19,15 @@ public static class OperationalTablesBootstrapper
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 '[]';
@@ -34,12 +43,30 @@ public static class OperationalTablesBootstrapper
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;
@@ -55,6 +82,9 @@ public static class OperationalTablesBootstrapper
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 '';
@@ -70,6 +100,9 @@ public static class OperationalTablesBootstrapper
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,
@@ -254,6 +287,95 @@ public static class OperationalTablesBootstrapper
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';
@@ -269,6 +391,36 @@ public static class OperationalTablesBootstrapper
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,