65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
import asyncio
|
|
from datetime import UTC, datetime, timedelta
|
|
from typing import TYPE_CHECKING
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.dialects.postgresql import insert
|
|
|
|
from config import cfg
|
|
from db.models import ServiceNotification, Subscription
|
|
from db.session import async_session
|
|
from schemas.enums import NotificationType
|
|
|
|
if TYPE_CHECKING:
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
async def collect_subscription_notifications(
|
|
session: "AsyncSession", now: datetime | None = None
|
|
) -> int:
|
|
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 session.commit()
|
|
return created
|
|
|
|
|
|
async def run_subscription_notifications() -> None:
|
|
interval = cfg.notification_scan_interval * 60
|
|
while True:
|
|
try:
|
|
async with async_session() as session:
|
|
await collect_subscription_notifications(session)
|
|
except Exception:
|
|
# A failed iteration must not stop subsequent notification checks.
|
|
pass
|
|
await asyncio.sleep(interval)
|