refactor(autorenewal): clean up imports and simplify error handling

This commit is contained in:
2026-05-18 17:58:34 +07:00
parent a9a650f37b
commit 1228ec8b56
9 changed files with 19 additions and 34 deletions

View File

@@ -10,9 +10,7 @@ from bot.texts import (
CALLBACK_FALLBACK, CALLBACK_FALLBACK,
FAQ_TEXT, FAQ_TEXT,
GENERAL_PROCESSING, GENERAL_PROCESSING,
GENERAL_SUCCESS,
NO_SUBSCRIPTION, NO_SUBSCRIPTION,
NOTHING_PLACEHOLDER,
) )
from db.models.users import User from db.models.users import User
from misc import utils from misc import utils

View File

@@ -1,4 +1,5 @@
import asyncio import asyncio
import contextlib
import logging import logging
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
@@ -21,6 +22,7 @@ from services.renewal_service import RenewalService
from services.user_service import UserService from services.user_service import UserService
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
contextlib.suppress(asyncio.CancelledError)
async def main(): async def main():
@@ -78,10 +80,7 @@ async def main():
await dp.start_polling(bot) await dp.start_polling(bot)
finally: finally:
scheduler_task.cancel() scheduler_task.cancel()
try: await scheduler_task
await scheduler_task
except asyncio.CancelledError:
pass
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from sqlalchemy.ext.asyncio import async_sessionmaker
from services.renewal_service import RenewalService from services.renewal_service import RenewalService

View File

@@ -1,5 +1,5 @@
from dotenv import load_dotenv from dotenv import load_dotenv
from pydantic import Field, SecretStr from pydantic import Field
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
load_dotenv(override=True) load_dotenv(override=True)
@@ -10,11 +10,11 @@ class Settings(BaseSettings):
"postgresql+asyncpg://malenia:skibidi@localhost:5432/malenia_db", alias="POSTGRES_URL" "postgresql+asyncpg://malenia:skibidi@localhost:5432/malenia_db", alias="POSTGRES_URL"
) )
bot_token: SecretStr = Field(alias="BOT_TOKEN") bot_token: str = Field(alias="BOT_TOKEN")
admins: list[int] = Field(alias="ADMINS") admins: list[int] = Field(alias="ADMINS")
pally_shop_id: SecretStr = Field(alias="PALLY_SHOP_ID") pally_shop_id: str = Field(alias="PALLY_SHOP_ID")
pally_token: SecretStr = Field(alias="PALLY_TOKEN") pally_token: str = Field(alias="PALLY_TOKEN")
referal_bonus: int = Field(10, alias="REFERAL_BONUS") referal_bonus: int = Field(10, alias="REFERAL_BONUS")
deposit_threshold: int = Field(50) deposit_threshold: int = Field(50)
@@ -23,7 +23,7 @@ class Settings(BaseSettings):
remnawave_url: str = Field(alias="REMNAWAVE_URL") remnawave_url: str = Field(alias="REMNAWAVE_URL")
subscription_url: str = Field(alias="SUBSCRIPTION_URL") subscription_url: str = Field(alias="SUBSCRIPTION_URL")
remnawave_token: SecretStr = Field(alias="REMNAWAVE_TOKEN") remnawave_token: str = Field(alias="REMNAWAVE_TOKEN")
min_devices: int = Field(3, alias="MIN_DEVICES") min_devices: int = Field(3, alias="MIN_DEVICES")
max_devices: int = Field(12, alias="MAX_DEVICES") max_devices: int = Field(12, alias="MAX_DEVICES")

View File

@@ -1,4 +1,4 @@
from datetime import datetime, timezone from datetime import UTC, datetime, timedelta
from sqlalchemy import select, update from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -10,13 +10,12 @@ class SubscriptionRepository:
async def get_subscriptions_for_autorenewal( async def get_subscriptions_for_autorenewal(
self, session: AsyncSession, *, days_before_expiry: int = 1 self, session: AsyncSession, *, days_before_expiry: int = 1
) -> list[Subscription]: ) -> list[Subscription]:
from datetime import timedelta
threshold = datetime.now(timezone.utc) + timedelta(days=days_before_expiry) threshold = datetime.now(UTC) + timedelta(days=days_before_expiry)
stmt = ( stmt = (
select(Subscription) select(Subscription)
.where( .where(
Subscription.autorenew == True, Subscription.autorenew == True, # noqa: E712
Subscription.status == SubscriptionStatus.ACTIVE, Subscription.status == SubscriptionStatus.ACTIVE,
Subscription.end_date <= threshold, Subscription.end_date <= threshold,
) )

View File

@@ -1,6 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from aiogram import Bot
from remnawave import RemnawaveSDK from remnawave import RemnawaveSDK
from misc.pally import PallyClient from misc.pally import PallyClient

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
from pydantic import BaseModel from pydantic import BaseModel
class NewUserDTO(BaseModel): class NewUserDTO(BaseModel):
id: int id: int
referal: int | None = None referal: int | None = None

View File

@@ -13,6 +13,7 @@ from misc import utils
from repositories.subscriptions import SubscriptionRepository from repositories.subscriptions import SubscriptionRepository
from repositories.users import UserRepository from repositories.users import UserRepository
from schemas.subscriptions import InternalSquads, SubscriptionPlan, SubscriptionStatus from schemas.subscriptions import InternalSquads, SubscriptionPlan, SubscriptionStatus
from services import rw as rw_service
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -48,22 +49,14 @@ class RenewalService:
return utils.calculate_price(plan) return utils.calculate_price(plan)
async def _renew_subscription_in_rw(self, subscription: Subscription) -> None: async def _renew_subscription_in_rw(self, subscription: Subscription) -> None:
from services import rw as rw_service
rw_user = await rw_service.get_user_by_telegram_id(self.rw_sdk, subscription.user_id) rw_user = await rw_service.get_user_by_telegram_id(self.rw_sdk, subscription.user_id)
if not rw_user: if not rw_user:
raise RenewalServiceError(f"RW user not found for user_id={subscription.user_id}") raise RenewalServiceError(f"RW user not found for user_id={subscription.user_id}")
duration = datetime.timedelta(days=subscription.duration_days)
squads = [InternalSquads.DEFAULT] squads = [InternalSquads.DEFAULT]
if subscription.whitelists: if subscription.whitelists:
squads.append(InternalSquads.WHITELISTS) squads.append(InternalSquads.WHITELISTS)
if rw_user.expire_at > datetime.datetime.now(datetime.UTC):
new_expire = rw_user.expire_at + duration
else:
new_expire = datetime.datetime.now(datetime.UTC) + duration
await rw_service.add_days(self.rw_sdk, rw_user.uuid, subscription.duration_days) await rw_service.add_days(self.rw_sdk, rw_user.uuid, subscription.duration_days)
await rw_service.update_squads(self.rw_sdk, rw_user.uuid, squads) await rw_service.update_squads(self.rw_sdk, rw_user.uuid, squads)
await rw_service.set_hwid_limit(self.rw_sdk, rw_user.uuid, subscription.devices) await rw_service.set_hwid_limit(self.rw_sdk, rw_user.uuid, subscription.devices)
@@ -95,10 +88,7 @@ class RenewalService:
await self.subscription_repository.update_subscription_autorenew( await self.subscription_repository.update_subscription_autorenew(
session, user_id, False session, user_id, False
) )
await self._notify_user( await self._notify_user(user_id, AUTORENEWAL_INSUFFICIENT_FUNDS)
user_id,
AUTORENEWAL_INSUFFICIENT_FUNDS
)
return False return False
result = await self.user_repository.decrease_user_balance( result = await self.user_repository.decrease_user_balance(
@@ -143,7 +133,7 @@ class RenewalService:
user_id, user_id,
AUTORENEWAL_SUCCESS.format( AUTORENEWAL_SUCCESS.format(
price=price, price=price,
end_date=new_end_date.strftime('%d.%m.%Y'), end_date=new_end_date.strftime("%d.%m.%Y"),
), ),
) )
@@ -171,9 +161,7 @@ class RenewalService:
else: else:
results["failed"] += 1 results["failed"] += 1
except Exception: except Exception:
logger.exception( logger.exception("Error processing autorenewal for user %d", subscription.user_id)
"Error processing autorenewal for user %d", subscription.user_id
)
results["failed"] += 1 results["failed"] += 1
logger.info( logger.info(