feat: cron worker for renewal notifs

This commit is contained in:
2026-08-20 21:28:43 +07:00
parent 0a58a41930
commit b39cae8046
6 changed files with 161 additions and 4 deletions

64
services/notifications.py Normal file
View 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)