import asyncio from datetime import UTC, datetime, timedelta from sqlalchemy import select from sqlalchemy.dialects.postgresql import insert from config import cfg from db.models import ServiceNotification, Subscription from db.session import UnitOfWork, async_session from schemas.enums import NotificationType async def collect_subscription_notifications(uow: UnitOfWork, now: datetime | None = None) -> int: session = uow.session now = now or datetime.now(UTC) periods = ( (NotificationType.SEVEN_DAYS, now + timedelta(days=3), now + timedelta(days=7)), (NotificationType.THREE_DAYS, now + timedelta(days=1), now + timedelta(days=3)), (NotificationType.ONE_DAY, now, now + timedelta(days=1)), (NotificationType.EXPIRED, None, now), ) created = 0 for notification_type, starts_at, ends_at in periods: conditions = [Subscription.expires_at <= ends_at] if starts_at is not None: conditions.append(Subscription.expires_at > starts_at) subscriptions = await session.scalars(select(Subscription).where(*conditions)) rows = [ { "subscription_id": subscription.id, "notify_type": notification_type, "sub_expires_at": subscription.expires_at, } for subscription in subscriptions ] if not rows: continue stmt = insert(ServiceNotification).values(rows) stmt = stmt.on_conflict_do_nothing(constraint="uq_subscription_notification") result = await session.execute(stmt) created += result.rowcount or 0 await uow.commit() return created async def run_subscription_notifications() -> None: interval = cfg.notification_scan_interval * 60 while True: try: async with async_session() as session, UnitOfWork(session) as uow: await collect_subscription_notifications(uow) except Exception: # A failed iteration must not stop subsequent notification checks. pass await asyncio.sleep(interval)