"""Flagged channel service for YouTube synchronization. Manages the tblPlaylistSynchronizationFlaggedChannel table. Provides whitelist/blacklist lookups for YouTube channel IDs. Mirrors Sony.Filtr.PlaylistSynchronization.FlaggedChannelsManager from .NET. Note: The .NET version caches results in Redis (DistributedCacheHandler). This implementation uses a simple module-level in-memory dict with a TTL (default 5 minutes) to avoid repeated DB hits during a sync sweep. """ import logging import time from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import select from playlist_sync.models.flagged_channel import ChannelFlagType, FlaggedChannel logger = logging.getLogger("playlist_sync.flagged_channels") _CACHE_TTL_SECONDS = 300 # 5 minutes _cache: dict[str, tuple[list[FlaggedChannel], float]] = {} _CACHE_KEY = "flagged_channels" async def _get_all(session: AsyncSession) -> list[FlaggedChannel]: """Load all flagged channels, using TTL cache to reduce DB load.""" now = time.monotonic() cached = _cache.get(_CACHE_KEY) if cached and (now - cached[1]) < _CACHE_TTL_SECONDS: return cached[0] result = await session.execute(select(FlaggedChannel)) channels = list(result.scalars().all()) _cache[_CACHE_KEY] = (channels, now) return channels async def get_whitelisted_channels(session: AsyncSession) -> list[str]: """Return all whitelisted YouTube channel IDs. Mirrors: FlaggedChannelsManager.GetWhitelistedChannels() """ channels = await _get_all(session) return [c.channel_id for c in channels if c.flag_type == ChannelFlagType.Whitelist] async def get_blacklisted_channels(session: AsyncSession) -> list[str]: """Return all blacklisted YouTube channel IDs. Mirrors: FlaggedChannelsManager.GetBlacklistedChannels() """ channels = await _get_all(session) return [c.channel_id for c in channels if c.flag_type == ChannelFlagType.Blacklist] def clear_cache() -> None: """Invalidate the in-memory channel cache (call after add/delete operations).""" _cache.clear() async def add_channel( channel_id: str, flag_type: ChannelFlagType, session: AsyncSession ) -> FlaggedChannel: """Add a channel to the whitelist or blacklist.""" # Check for existing entry result = await session.execute( select(FlaggedChannel).where( FlaggedChannel.channel_id == channel_id, FlaggedChannel.flag_type == flag_type, ) ) existing = result.scalar_one_or_none() if existing: return existing channel = FlaggedChannel(channel_id=channel_id, flag_type=int(flag_type)) session.add(channel) await session.commit() await session.refresh(channel) clear_cache() return channel async def delete_channel( channel_id: str, flag_type: ChannelFlagType, session: AsyncSession ) -> bool: """Remove a channel from the whitelist or blacklist. Returns True if deleted.""" result = await session.execute( select(FlaggedChannel).where( FlaggedChannel.channel_id == channel_id, FlaggedChannel.flag_type == flag_type, ) ) existing = result.scalar_one_or_none() if not existing: return False await session.delete(existing) await session.commit() clear_cache() return True