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
+48 -3
View File
@@ -14,15 +14,19 @@ import type {
AdminSiteSettingsResponse,
AdminSponsorItem,
AdminTeamResponse,
AdminTrackingRulesResponse,
AdminWorkflowRulesResponse,
ApproveNominationPayload,
AddNominationLinkBlacklistEntryPayload,
BulkResolveRiskFlagsPayload,
CreateTeamMemberPayload,
CreateSeasonPayload,
ReopenRejectedNominationPayload,
RejectNominationPayload,
ResolveRiskFlagPayload,
SetAwardResultPayload,
UpdateNominationTrackingReviewPayload,
UpdateSeasonSubcategoryTemplatesPayload,
TeamMemberPasswordResponse,
UpdateClipStatusPayload,
UpdateOptionalFeatureSettingsPayload,
@@ -32,11 +36,15 @@ import type {
UpdateNominationLinkBlacklistPayload,
UpdateSeasonPayload,
UpdateSiteSettingsPayload,
UpdateTrackingReviewNotesPayload,
UpdateTrackingRulesPayload,
UpdateTrackingSourcePayload,
UpdateTeamMemberPayload,
UpdateTeamRolesPayload,
UpdateWorkflowRulesPayload,
UpsertCandidatePayload,
UpsertCategoryPayload,
UpsertCategoryGroupPayload,
UpsertSponsorPayload,
} from '../../types/awards'
import { requestJson } from '../http'
@@ -93,9 +101,16 @@ export const adminApi = {
getAdminRiskRules: () => requestJson<AdminRiskRulesResponse>('/api/admin/risk-rules'),
updateAdminRiskRules: (payload: UpdateRiskRulesPayload) =>
requestJson<AdminRiskRulesResponse>('/api/admin/risk-rules', jsonRequest('PUT', payload)),
getAdminWorkflowRules: () => requestJson<AdminWorkflowRulesResponse>('/api/admin/workflow-rules'),
updateAdminWorkflowRules: (payload: UpdateWorkflowRulesPayload) =>
requestJson<AdminWorkflowRulesResponse>('/api/admin/workflow-rules', jsonRequest('PUT', payload)),
getAdminWorkflowRules: (seasonId: number) => requestJson<AdminWorkflowRulesResponse>(`/api/admin/seasons/${seasonId}/workflow-rules`),
updateAdminWorkflowRules: (seasonId: number, payload: UpdateWorkflowRulesPayload) =>
requestJson<AdminWorkflowRulesResponse>(`/api/admin/seasons/${seasonId}/workflow-rules`, jsonRequest('PUT', payload)),
getAdminTrackingRules: () => requestJson<AdminTrackingRulesResponse>('/api/admin/tracking-rules'),
updateAdminTrackingRules: (payload: UpdateTrackingRulesPayload) =>
requestJson<AdminTrackingRulesResponse>('/api/admin/tracking-rules', jsonRequest('PUT', payload)),
updateAdminTrackingSource: (payload: UpdateTrackingSourcePayload) =>
requestJson<AdminTrackingRulesResponse>('/api/admin/tracking-rules/source', jsonRequest('PUT', payload)),
updateAdminTrackingReviewNotes: (payload: UpdateTrackingReviewNotesPayload) =>
requestJson<AdminTrackingRulesResponse>('/api/admin/tracking-rules/notes', jsonRequest('PUT', payload)),
getAdminSeasons: () => requestJson<AdminSeasonListItem[]>('/api/admin/seasons'),
getAdminSeasonDetail: (seasonId: number) =>
requestJson<AdminSeasonDetailResponse>(`/api/admin/seasons/${seasonId}`),
@@ -157,6 +172,26 @@ export const adminApi = {
requestJson<{ deleted: boolean; categoryId: number }>(`/api/admin/categories/${categoryId}`, {
method: 'DELETE',
}),
updateAdminSeasonSubcategoryTemplates: (seasonId: number, payload: UpdateSeasonSubcategoryTemplatesPayload) =>
requestJson<{ saved: boolean; seasonId: number; templateCount: number }>(
`/api/admin/seasons/${seasonId}/subcategory-templates`,
jsonRequest('PUT', payload),
),
createAdminCategoryGroup: (seasonId: number, payload: UpsertCategoryGroupPayload) =>
requestJson<{ saved: boolean; seasonId: number; groupName: string }>(
`/api/admin/seasons/${seasonId}/category-groups`,
jsonRequest('POST', payload),
),
updateAdminCategoryGroup: (seasonId: number, groupName: string, payload: UpsertCategoryGroupPayload) =>
requestJson<{ saved: boolean; seasonId: number; groupName: string }>(
`/api/admin/seasons/${seasonId}/category-groups/${encodeURIComponent(groupName)}`,
jsonRequest('PUT', payload),
),
deleteAdminCategoryGroup: (seasonId: number, groupName: string) =>
requestJson<{ deleted: boolean; seasonId: number; groupName: string }>(
`/api/admin/seasons/${seasonId}/category-groups/${encodeURIComponent(groupName)}`,
{ method: 'DELETE' },
),
createAdminCandidate: (seasonId: number, payload: UpsertCandidatePayload) =>
requestJson<{ saved: boolean; candidateId: number }>(`/api/admin/seasons/${seasonId}/candidates`, jsonRequest('POST', payload)),
updateAdminCandidate: (candidateId: number, payload: UpsertCandidatePayload) =>
@@ -190,6 +225,16 @@ export const adminApi = {
`/api/admin/nominations/${nominationId}/reject`,
jsonRequest('POST', payload),
),
reopenRejectedAdminNomination: (nominationId: number, payload: ReopenRejectedNominationPayload = {}) =>
requestJson<{ saved: boolean; nominationId: number; reopened: boolean }>(
`/api/admin/nominations/${nominationId}/reopen`,
jsonRequest('POST', payload),
),
updateNominationTrackingReview: (nominationId: number, payload: UpdateNominationTrackingReviewPayload) =>
requestJson<{ saved: boolean; nominationId: number; status: string }>(
`/api/admin/nominations/${nominationId}/tracking-review`,
jsonRequest('POST', payload),
),
getAdminNominationLinkBlacklist: () =>
requestJson<AdminNominationLinkBlacklistResponse>('/api/admin/nominations/link-blacklist'),
updateAdminNominationLinkBlacklist: (payload: UpdateNominationLinkBlacklistPayload) =>