From 53d148f9bc8410095a863ef1e9703558952ce54f Mon Sep 17 00:00:00 2001 From: luke Date: Sat, 19 Sep 2026 14:39:19 -0400 Subject: [PATCH 1/2] feat(feed): add per-league separation override --- frontend/src/api/settings.ts | 2 + .../src/components/PerLeagueChannelConfig.tsx | 43 ++++++++++++++++++- teamarr/api/routes/subscription.py | 5 +++ .../event_group_processor/matching.py | 17 ++++++-- .../event_group_processor/processor.py | 14 +++++- teamarr/database/migrations/__init__.py | 2 + teamarr/database/migrations/versioned.py | 7 +++ teamarr/database/schema.sql | 4 +- teamarr/database/subscription.py | 17 ++++++-- tests/migrations/test_migration_helpers.py | 19 ++++++++ tests/test_feed_separation.py | 7 +++ tests/test_matchup_order_setting.py | 7 +++ 12 files changed, 134 insertions(+), 10 deletions(-) diff --git a/frontend/src/api/settings.ts b/frontend/src/api/settings.ts index a397fcbb3..a6bf69e19 100644 --- a/frontend/src/api/settings.ts +++ b/frontend/src/api/settings.ts @@ -660,6 +660,7 @@ export interface SubscriptionLeagueConfig { channel_group_mode: string | null matchup_order: string | null // "auto" | "away_first" | "home_first"; null = global setting included_divisions: string[] | null // #811: null = every division ESPN files under the league + feed_separation_enabled: boolean | null // #862: null = global setting } export interface LeagueDivision { @@ -689,6 +690,7 @@ export async function upsertLeagueConfig( channel_group_mode?: string | null matchup_order?: string | null included_divisions?: string[] | null + feed_separation_enabled?: boolean | null } ): Promise { return api.put(`/league-configs/${encodeURIComponent(leagueCode)}`, data) diff --git a/frontend/src/components/PerLeagueChannelConfig.tsx b/frontend/src/components/PerLeagueChannelConfig.tsx index 74e88f096..16166d4b9 100644 --- a/frontend/src/components/PerLeagueChannelConfig.tsx +++ b/frontend/src/components/PerLeagueChannelConfig.tsx @@ -55,6 +55,7 @@ function LeagueConfigRow({ channel_group_mode?: string | null matchup_order?: string | null included_divisions?: string[] | null + feed_separation_enabled?: boolean | null }) => Promise onClear: () => Promise }) { @@ -62,6 +63,7 @@ function LeagueConfigRow({ const [localGroupId, setLocalGroupId] = useState(null) const [localGroupMode, setLocalGroupMode] = useState(null) const [localMatchupOrder, setLocalMatchupOrder] = useState(null) + const [localFeedSeparationEnabled, setLocalFeedSeparationEnabled] = useState(null) // #811: the divisions still ingested. Held as the checked set (null config = // all of them), so the UI never has to special-case "no override". const [localDivisions, setLocalDivisions] = useState([]) @@ -84,12 +86,14 @@ function LeagueConfigRow({ setLocalGroupId(config.channel_group_id) setLocalGroupMode(config.channel_group_mode) setLocalMatchupOrder(config.matchup_order ?? null) + setLocalFeedSeparationEnabled(config.feed_separation_enabled) setLocalDivisions(config.included_divisions ?? divisions.map((d) => d.key)) } else if (isExpanded && !config) { setLocalProfileIds([]) setLocalGroupId(null) setLocalGroupMode(null) setLocalMatchupOrder(null) + setLocalFeedSeparationEnabled(null) setLocalDivisions(divisions.map((d) => d.key)) } } @@ -134,6 +138,7 @@ function LeagueConfigRow({ channel_group_id: localGroupId, channel_group_mode: localGroupMode, matchup_order: localMatchupOrder, + feed_separation_enabled: localFeedSeparationEnabled, // All of them is stored as "no override" so the selection still means // everything if ESPN's slate gains a division later. included_divisions: @@ -178,6 +183,13 @@ function LeagueConfigRow({ {matchupOrderSummary} + + + {config?.feed_separation_enabled === null || config?.feed_separation_enabled === undefined + ? "Default" + : config.feed_separation_enabled ? "Enabled" : "Disabled"} + + {hasOverride && (