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
+71 -1
View File
@@ -55,11 +55,27 @@ public sealed record AdminAuditEntriesResponse(
public sealed record AdminNominationReviewItemDto(
int Id,
int CategoryId,
int? CategoryId,
string CategoryGroupName,
string CategoryName,
string SubmittedByTwitchId,
string CandidateText,
string? StreamUrl,
string? ResolvedChannel,
string? ResolvedPlatform,
int? AvgViewers,
int? SuggestedCategoryId,
string? SuggestedCategoryName,
int? StreamerIdentityId,
string TrackerStatus,
DateTimeOffset? TrackerCheckedAt,
string TrackingReviewStatus,
bool RequiresManualReview,
AdminTrackingFlagHitDto[] TrackingFlags,
AdminTrackingMetricStateDto[] TrackingMetrics,
string? TrackingReviewNote,
string? TrackingReviewedByTwitchId,
DateTimeOffset? TrackingReviewedAt,
string Status,
DateTimeOffset CreatedAt,
int? CandidateId,
@@ -68,6 +84,32 @@ public sealed record AdminNominationReviewItemDto(
string? ReviewedByTwitchId,
DateTimeOffset? ReviewedAt);
public sealed record AdminNominationReviewGroupDto(
int Id,
int[] NominationIds,
string CategoryGroupName,
string DisplayName,
string? StreamUrl,
string? ResolvedChannel,
string? ResolvedPlatform,
int? AvgViewers,
int? SuggestedCategoryId,
string? SuggestedCategoryName,
int? StreamerIdentityId,
string TrackerStatus,
DateTimeOffset? TrackerCheckedAt,
string TrackingReviewStatus,
bool RequiresManualReview,
AdminTrackingFlagHitDto[] TrackingFlags,
AdminTrackingMetricStateDto[] TrackingMetrics,
string? TrackingReviewNote,
string? TrackingReviewedByTwitchId,
DateTimeOffset? TrackingReviewedAt,
int NominationTally,
int UniqueSubmitterCount,
DateTimeOffset FirstSubmittedAt,
DateTimeOffset LastSubmittedAt);
public sealed record AdminClipSubmissionItemDto(
int Id,
int? CategoryId,
@@ -87,10 +129,17 @@ public sealed record ApproveNominationRequest(
string? DisplayName,
string? ChannelSlug,
string? Platform,
int? CategoryId,
string? ReviewNote);
public sealed record RejectNominationRequest(string? ReviewNote);
public sealed record ReopenRejectedNominationRequest(string? ReviewNote);
public sealed record UpdateNominationTrackingReviewRequest(
string Status,
string? ReviewNote);
public sealed record AdminNominationLinkBlacklistEntryDto(string Url);
public sealed record AdminNominationLinkBlacklistResponse(AdminNominationLinkBlacklistEntryDto[] Entries);
@@ -136,3 +185,24 @@ public sealed record AdminWorkflowRuleDto(
public sealed record AdminWorkflowRulesResponse(AdminWorkflowRuleDto[] Rules);
public sealed record UpdateWorkflowRulesRequest(AdminWorkflowRuleDto[] Rules);
public sealed record AdminTrackingFlagHitDto(
string Key,
string Label,
string Severity,
string Description,
bool RequiresManualReview,
bool BlocksApproval,
bool AdminNoteRequiredOnOverride);
public sealed record AdminTrackingMetricStateDto(
string Key,
string Label,
bool Required,
string SourceSupport,
bool Present,
string Value,
string Description,
string WindowKey,
string WindowLabel,
bool AutoWindowSupported);
+27
View File
@@ -16,14 +16,25 @@ public sealed record AdminCategoryItemDto(
string Description,
int SortOrder,
int MaxNomineesPerUser,
int? ViewerRangeMin,
int? ViewerRangeMax,
int CandidateCount);
public sealed record AdminSubcategoryTemplateDto(
string Name,
string Slug,
int SortOrder,
int? ViewerRangeMin,
int? ViewerRangeMax);
public sealed record AdminCandidateItemDto(
int Id,
int CategoryId,
int? StreamerIdentityId,
string DisplayName,
string ChannelSlug,
string Platform,
int NominationTally,
string AcceptanceStatus,
string? AcceptanceNote,
string? ClipCompilationUrl,
@@ -36,6 +47,7 @@ public sealed record AdminAwardResultItemDto(
int CategoryId,
string CategoryName,
int CandidateId,
int? StreamerIdentityId,
string CandidateDisplayName,
string CandidateChannelSlug,
string CandidatePlatform);
@@ -56,10 +68,14 @@ public sealed record AdminSeasonDetailResponse(
DateOnly ReviewEndsAt,
DateOnly ShowDate,
TimeOnly ShowStartsAt,
IEnumerable<AdminSubcategoryTemplateDto> SubcategoryTemplates,
IEnumerable<AdminCategoryItemDto> Categories,
IEnumerable<AdminCandidateItemDto> Candidates,
IEnumerable<AdminNominationReviewItemDto> PendingNominations,
IEnumerable<AdminNominationReviewGroupDto> PendingNominationGroups,
IEnumerable<AdminNominationReviewItemDto> ReviewedNominations,
string TrackingReviewNotes,
bool ShowTrackingReviewNotes,
IEnumerable<AdminAwardResultItemDto> Results,
IEnumerable<AdminClipSubmissionItemDto> ClipSubmissions);
@@ -102,6 +118,17 @@ public sealed record UpsertCategoryRequest(
string Slug,
string Description,
int SortOrder,
int MaxNomineesPerUser,
int? ViewerRangeMin,
int? ViewerRangeMax);
public sealed record UpdateSeasonSubcategoryTemplatesRequest(
AdminSubcategoryTemplateDto[] Templates);
public sealed record UpsertCategoryGroupRequest(
string GroupName,
string Description,
int SortOrder,
int MaxNomineesPerUser);
public sealed record UpsertCandidateRequest(
@@ -4,6 +4,8 @@ public sealed record AdminSiteSettingsResponse(
string HostDisplayName,
string HostTagline,
string NewsletterUrl,
string ShareXUrl,
string ShareDiscordUrl,
string PrivacyEmail,
string PrivacyPolicyContent,
string? PrivacyPolicyUpdatedBy,
@@ -17,12 +19,15 @@ public sealed record AdminSiteSettingsResponse(
string ShowactsUrl,
string ShowactsContent,
IEnumerable<PublicSocialLinkDto> SocialLinks,
IEnumerable<FaqItemDto> Faq);
IEnumerable<FaqItemDto> Faq,
string ShowactFormSchemaJson);
public sealed record UpdateSiteSettingsRequest(
string HostDisplayName,
string HostTagline,
string NewsletterUrl,
string ShareXUrl,
string ShareDiscordUrl,
string PrivacyEmail,
string PrivacyPolicyContent,
string ImprintUrl,
@@ -34,7 +39,8 @@ public sealed record UpdateSiteSettingsRequest(
string ShowactsUrl,
string ShowactsContent,
PublicSocialLinkDto[] SocialLinks,
FaqItemDto[] Faq);
FaqItemDto[] Faq,
string? ShowactFormSchemaJson = null);
public sealed record AdminOperationalSettingsResponse(
bool DemoLoginManagedByDatabase,
@@ -49,6 +55,7 @@ public sealed record AdminOperationalSettingsResponse(
bool TwitchClientSecretSet,
string TwitchRedirectUri,
string TwitchScope,
int SessionIdleTimeoutHours,
bool MaintenanceModeEnabled,
string MaintenanceTitle,
string MaintenanceMessage);
@@ -59,6 +66,9 @@ public sealed record AdminOptionalFeatureSettingsResponse(
bool ClipAdminMenuVisible,
string ClipSubmissionDisabledMessage,
bool ShowactApplicationsEnabled,
DateOnly? ShowactApplicationStartsAt,
DateOnly? ShowactApplicationEndsAt,
bool ShowactApplicationsOpenNow,
string ShowactApplicationDisabledMessage,
bool SponsorsVisible);
@@ -68,6 +78,8 @@ public sealed record UpdateOptionalFeatureSettingsRequest(
bool ClipAdminMenuVisible,
string ClipSubmissionDisabledMessage,
bool ShowactApplicationsEnabled,
DateOnly? ShowactApplicationStartsAt,
DateOnly? ShowactApplicationEndsAt,
string ShowactApplicationDisabledMessage,
bool SponsorsVisible);
@@ -81,6 +93,71 @@ public sealed record UpdateOperationalSettingsRequest(
string? TwitchClientSecret,
string TwitchRedirectUri,
string TwitchScope,
int SessionIdleTimeoutHours,
bool MaintenanceModeEnabled,
string MaintenanceTitle,
string MaintenanceMessage);
public sealed record AdminTrackingSourceDto(
string ProviderKey,
string ProviderLabel,
string BaseUrl,
string NotesSummary,
bool ShowManualReviewNotesInReview);
public sealed record AdminTrackingMetricRuleDto(
string Key,
string Label,
bool Enabled,
string SourceSupport,
string Description,
bool RequiredForAutoClassification,
bool ShowInReview,
bool ShowInAdminSummary,
bool ManualOverrideAllowed,
string WindowKey,
string[] AutoSupportedWindowKeys,
string? ProviderFieldKey,
int? TopCount,
int? MinPrimaryCategorySharePercent,
int? MinPrimaryCategoryHours,
int? MaxDistinctCategoriesBeforeFlag,
string[] IgnoredCategories,
bool MatchAwardCategoryAgainstTopCategories,
bool FlagIfAwardCategoryNotInTopX,
bool FlagIfCategorySpreadTooWide,
bool FlagIfNoCategoryContextAvailable,
int? MinValue,
int? MaxValue);
public sealed record AdminTrackingFlagRuleDto(
string Key,
string Label,
bool Enabled,
string Severity,
string Description,
bool AutoTriggerEnabled,
bool RequiresManualReview,
bool BlocksApproval,
bool AdminNoteRequiredOnOverride);
public sealed record AdminTrackingRulesResponse(
AdminTrackingSourceDto Source,
AdminTrackingMetricRuleDto[] ImportantMetrics,
AdminTrackingMetricRuleDto[] OptionalMetrics,
AdminTrackingFlagRuleDto[] Flags,
string ManualReviewNotes);
public sealed record UpdateTrackingRulesRequest(
AdminTrackingSourceDto Source,
AdminTrackingMetricRuleDto[] ImportantMetrics,
AdminTrackingMetricRuleDto[] OptionalMetrics,
AdminTrackingFlagRuleDto[] Flags,
string ManualReviewNotes);
public sealed record UpdateTrackingSourceRequest(
AdminTrackingSourceDto Source);
public sealed record UpdateTrackingReviewNotesRequest(
string ManualReviewNotes,
bool ShowManualReviewNotesInReview);
+1
View File
@@ -32,6 +32,7 @@ public sealed record AuthSessionDto(
string DisplayName,
string Role,
IEnumerable<string> PermissionKeys,
int SessionIdleTimeoutHours,
bool MustChangePassword = false,
string? TeamLogin = null,
string? BoundTwitchUserId = null,
+11 -9
View File
@@ -36,16 +36,18 @@ public sealed record ShowactApplicationDto(
string Status,
string? ReviewNote,
DateTimeOffset CreatedAt,
DateTimeOffset? ReviewedAt);
DateTimeOffset? ReviewedAt,
string FieldResponsesJson);
public sealed record CreateShowactApplicationRequest(
string ArtistName,
string ContactEmail,
string ContactDiscord,
string PlatformUrl,
string PerformanceType,
string Description,
string TechnicalNotes,
string ReferenceUrl);
string? ArtistName = null,
string? ContactEmail = null,
string? ContactDiscord = null,
string? PlatformUrl = null,
string? PerformanceType = null,
string? Description = null,
string? TechnicalNotes = null,
string? ReferenceUrl = null,
string? FieldResponsesJson = null);
public sealed record UpdateShowactStatusRequest(string Status, string? ReviewNote);
+6 -1
View File
@@ -50,6 +50,8 @@ public sealed record PublicSiteContentDto(
string HostDisplayName,
string HostTagline,
string NewsletterUrl,
string ShareXUrl,
string ShareDiscordUrl,
string PrivacyEmail,
string PrivacyPolicyContent,
IEnumerable<PublicSocialLinkDto> SocialLinks,
@@ -66,8 +68,11 @@ public sealed record PublicFeatureFlagsDto(
bool ClipReviewEnabled,
string ClipSubmissionDisabledMessage,
bool ShowactApplicationsEnabled,
DateOnly? ShowactApplicationStartsAt,
DateOnly? ShowactApplicationEndsAt,
string ShowactApplicationDisabledMessage,
bool SponsorsVisible);
bool SponsorsVisible,
string ShowactFormSchemaJson);
public sealed record OverviewResponse(
int SeasonId,
@@ -17,6 +17,8 @@ public sealed record PublicCategoryDetailDto(
string GroupName,
string Description,
int MaxNomineesPerUser,
int? ViewerRangeMin,
int? ViewerRangeMax,
IEnumerable<CandidateSummaryDto> Candidates);
public sealed record SeasonCategoriesResponse(
@@ -1,7 +1,8 @@
namespace Backend.Contracts;
public sealed record UserNominationStateDto(
int CategoryId,
int? CategoryId,
string CategoryGroupName,
string[] Nominees);
public sealed record UserVoteStateDto(
+2 -1
View File
@@ -6,7 +6,8 @@ public sealed record NominationEntryRequest(
public sealed record CreateNominationRequest(
int Year,
int CategoryId,
int? CategoryId,
string? CategoryGroupName,
string TwitchUserId,
string[]? Nominees,
NominationEntryRequest[]? Nominations);