Files
remnawave-minishop/db/dal/subscription_dal.py
T

219 lines
8.3 KiB
Python

import logging
from typing import Optional, List, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy import update, delete, func, and_, or_
from sqlalchemy.orm import selectinload
from datetime import datetime, timezone, timedelta
from db.models import Subscription, User
async def get_active_subscription_by_user_id(
session: AsyncSession,
user_id: int,
panel_user_uuid: Optional[str] = None) -> Optional[Subscription]:
stmt = select(Subscription).where(
Subscription.user_id == user_id, Subscription.is_active == True,
Subscription.end_date > datetime.now(timezone.utc))
if panel_user_uuid:
stmt = stmt.where(Subscription.panel_user_uuid == panel_user_uuid)
stmt = stmt.order_by(Subscription.end_date.desc())
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def get_subscription_by_panel_subscription_uuid(
session: AsyncSession, panel_sub_uuid: str) -> Optional[Subscription]:
stmt = select(Subscription).where(
Subscription.panel_subscription_uuid == panel_sub_uuid)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def create_subscription(session: AsyncSession,
sub_data: Dict[str, Any]) -> Subscription:
from .user_dal import get_user_by_id
if "user_id" not in sub_data or sub_data["user_id"] is None:
raise ValueError(
"user_id is required to create a subscription directly.")
user = await get_user_by_id(session, sub_data["user_id"])
if not user:
raise ValueError(
f"User with id {sub_data['user_id']} not found for creating subscription."
)
new_sub = Subscription(**sub_data)
session.add(new_sub)
await session.flush()
await session.refresh(new_sub)
logging.info(
f"Subscription {new_sub.subscription_id} created for user {new_sub.user_id}"
)
return new_sub
async def update_subscription(
session: AsyncSession, subscription_id: int,
update_data: Dict[str, Any]) -> Optional[Subscription]:
sub = await session.get(Subscription, subscription_id)
if sub:
for key, value in update_data.items():
setattr(sub, key, value)
await session.flush()
await session.refresh(sub)
return sub
async def upsert_subscription(session: AsyncSession,
sub_payload: Dict[str, Any]) -> Subscription:
panel_sub_uuid = sub_payload.get("panel_subscription_uuid")
if not panel_sub_uuid:
raise ValueError("panel_subscription_uuid is required for upsert.")
existing_sub = await get_subscription_by_panel_subscription_uuid(
session, panel_sub_uuid)
if existing_sub:
logging.info(
f"Updating existing subscription {existing_sub.subscription_id} by panel_sub_uuid {panel_sub_uuid}"
)
for key, value in sub_payload.items():
if hasattr(existing_sub, key):
setattr(existing_sub, key, value)
await session.flush()
await session.refresh(existing_sub)
return existing_sub
else:
logging.info(
f"Creating new subscription with panel_sub_uuid {panel_sub_uuid}")
if sub_payload.get(
"user_id") is None and "panel_user_uuid" not in sub_payload:
raise ValueError(
"For a new subscription without user_id, panel_user_uuid is required."
)
if "end_date" not in sub_payload:
raise ValueError("Missing 'end_date' for new subscription.")
if sub_payload.get("user_id") is not None:
from .user_dal import get_user_by_id
user = await get_user_by_id(session, sub_payload["user_id"])
if not user:
raise ValueError(
f"User {sub_payload['user_id']} not found for new subscription with panel_uuid {panel_sub_uuid}."
)
new_sub = Subscription(**sub_payload)
session.add(new_sub)
await session.flush()
await session.refresh(new_sub)
return new_sub
async def deactivate_other_active_subscriptions(
session: AsyncSession, panel_user_uuid: str,
current_panel_subscription_uuid: Optional[str]):
stmt = (update(Subscription).where(
Subscription.panel_user_uuid == panel_user_uuid,
Subscription.is_active == True,
).values(is_active=False, status_from_panel="INACTIVE_BY_BOT_SYNC"))
if current_panel_subscription_uuid:
stmt = stmt.where(Subscription.panel_subscription_uuid !=
current_panel_subscription_uuid)
result = await session.execute(stmt)
if result.rowcount > 0:
logging.info(
f"Deactivated {result.rowcount} other active subscriptions for panel_user_uuid {panel_user_uuid}."
)
async def update_subscription_end_date(
session: AsyncSession, subscription_id: int,
new_end_date: datetime) -> Optional[Subscription]:
return await update_subscription(
session, subscription_id, {
"end_date": new_end_date,
"last_notification_sent": None,
"is_active": True,
"status_from_panel": "ACTIVE_EXTENDED_BY_BOT"
})
async def has_any_subscription_for_user(session: AsyncSession,
user_id: int) -> bool:
stmt = select(Subscription.subscription_id).where(
Subscription.user_id == user_id).limit(1)
result = await session.execute(stmt)
return result.scalar_one_or_none() is not None
async def get_subscriptions_near_expiration(
session: AsyncSession, days_threshold: int) -> List[Subscription]:
now_utc = datetime.now(timezone.utc)
threshold_date = now_utc + timedelta(days=days_threshold)
stmt = (select(Subscription).join(Subscription.user).where(
Subscription.is_active == True,
Subscription.skip_notifications == False,
Subscription.end_date > now_utc,
Subscription.end_date <= threshold_date,
or_(
Subscription.last_notification_sent == None,
func.date(Subscription.last_notification_sent)
< func.date(now_utc))).order_by(
Subscription.end_date.asc()).options(
selectinload(Subscription.user)))
result = await session.execute(stmt)
return result.scalars().all()
async def update_subscription_notification_time(
session: AsyncSession, subscription_id: int,
notification_time: datetime) -> Optional[Subscription]:
return await update_subscription(
session, subscription_id,
{"last_notification_sent": notification_time})
async def get_user_active_subscription_end_date_str(
session: AsyncSession, user_id: int) -> Optional[str]:
stmt = (select(Subscription.end_date).where(
Subscription.user_id == user_id, Subscription.is_active == True,
Subscription.end_date > datetime.now(timezone.utc)).order_by(
Subscription.end_date.desc()).limit(1))
result = await session.execute(stmt)
end_date_obj = result.scalar_one_or_none()
return end_date_obj.strftime('%Y-%m-%d') if end_date_obj else None
async def find_subscription_for_notification_update(
session: AsyncSession, user_id: int,
subscription_end_date_to_match: datetime) -> Optional[Subscription]:
if subscription_end_date_to_match.tzinfo is None:
subscription_end_date_to_match = subscription_end_date_to_match.replace(
tzinfo=timezone.utc)
stmt = select(Subscription).where(
Subscription.user_id == user_id, Subscription.is_active == True,
Subscription.end_date
>= subscription_end_date_to_match - timedelta(seconds=1),
Subscription.end_date
<= subscription_end_date_to_match + timedelta(seconds=1)).limit(1)
result = await session.execute(stmt)
return result.scalar_one_or_none()
async def set_skip_notifications_for_provider(
session: AsyncSession, user_id: int, provider: str,
skip: bool) -> int:
stmt = (update(Subscription).where(
Subscription.user_id == user_id,
Subscription.is_active == True,
Subscription.provider == provider).values(skip_notifications=skip))
result = await session.execute(stmt)
return result.rowcount