refactor(autorenewal): clean up imports and simplify error handling
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -227,4 +227,4 @@ AUTORENEWAL_INSUFFICIENT_FUNDS = (
|
|||||||
AUTORENEWAL_SUCCESS = (
|
AUTORENEWAL_SUCCESS = (
|
||||||
"<b>✅ Подписка продлена автоматически. Списано <code>{price}₽</code>.</b>"
|
"<b>✅ Подписка продлена автоматически. Списано <code>{price}₽</code>.</b>"
|
||||||
"Новая дата окончания: <code>{end_date}</code>."
|
"Новая дата окончания: <code>{end_date}</code>."
|
||||||
)
|
)
|
||||||
|
|||||||
10
config.py
10
config.py
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user