"""Author RAG Chatbot SaaS — Subscription Expiry Check Task. Runs daily at 8am UTC. Sends warning emails at: - 7 days before expiry - 1 day before expiry """ import structlog from app.tasks.celery_app import celery_app logger = structlog.get_logger(__name__) @celery_app.task(name="app.tasks.expiry_check_task.check_expiring_subscriptions") def check_expiring_subscriptions(): """Check all active subscriptions for upcoming expiry and send alerts.""" import asyncio asyncio.run(_run()) async def _run(): from datetime import datetime, timedelta, timezone from sqlalchemy import select from app.dependencies import _get_session_factory from app.models.client_access import ClientAccess from app.models.user import User from app.services.email_service import EmailService email = EmailService() now = datetime.now(timezone.utc).replace(tzinfo=None) # SQLite stores naive datetimes warning_days = [7, 1] async with _get_session_factory()() as db: result = await db.execute( select(ClientAccess, User) .join(User, User.id == ClientAccess.author_id) .where( ClientAccess.is_revoked == False, ClientAccess.expires_at > now, User.notify_subscription_expiry == True, ) ) records = result.all() sent = 0 for access, user in records: expires = access.expires_at.replace(tzinfo=None) if access.expires_at.tzinfo else access.expires_at days_left = (expires - now).days if days_left in warning_days: try: email.send_subscription_expiry_warning( to=user.email, name=user.full_name or user.email, days_remaining=days_left, expires_at=access.expires_at.strftime("%B %d, %Y"), ) sent += 1 except Exception as e: logger.warning("Expiry warning email failed", user_id=user.id, error=str(e)) logger.info("Expiry check complete", warnings_sent=sent)