- FastAPI REST API with JWT auth - aiogram 3 Telegram bot with admin middleware - APScheduler daily tasks (expiry, reminders, revoke, sync) - SQLAlchemy 2 async ORM with Alembic migrations - Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs) - VPN provider abstraction with MockProvider - Stats service with revenue/subscription analytics - Docker Compose (PostgreSQL + Redis + app) - Healthcheck endpoint
49 lines
1.7 KiB
Python
49 lines
1.7 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
from loguru import logger
|
|
|
|
from app.database import async_session_factory
|
|
from app.models.payment import PaymentStatus
|
|
from app.repositories.notification import NotificationRepository
|
|
from app.repositories.payment import PaymentRepository
|
|
from app.repositories.tariff import TariffRepository
|
|
from app.repositories.user import UserRepository
|
|
from app.services.notification import NotificationService
|
|
|
|
|
|
async def send_reminders() -> None:
|
|
logger.info("[Scheduler] Sending subscription expiry reminders")
|
|
|
|
async with async_session_factory() as session:
|
|
payment_repo = PaymentRepository(session)
|
|
tariff_repo = TariffRepository(session)
|
|
user_repo = UserRepository(session)
|
|
notification_service = NotificationService(
|
|
NotificationRepository(session), user_repo
|
|
)
|
|
|
|
confirmed = await payment_repo.get_by_status(PaymentStatus.CONFIRMED)
|
|
now = datetime.now(timezone.utc)
|
|
reminders_sent = 0
|
|
|
|
for payment in confirmed:
|
|
tariff = await tariff_repo.get(payment.tariff_id)
|
|
if tariff is None:
|
|
continue
|
|
|
|
start = payment.paid_at or payment.created_at
|
|
end = start + timedelta(days=tariff.duration_days)
|
|
remaining = (end - now).days
|
|
|
|
result = await notification_service.send_expiry_reminder(
|
|
user_id=payment.user_id,
|
|
remaining_days=remaining,
|
|
tariff_name=tariff.name,
|
|
)
|
|
if result is not None:
|
|
reminders_sent += 1
|
|
|
|
logger.info(
|
|
"[Scheduler] Sent {} expiry reminders", reminders_sent
|
|
)
|