feat: cron worker for renewal notifs
This commit is contained in:
64
services/notifications.py
Normal file
64
services/notifications.py
Normal file
@@ -0,0 +1,64 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user