api 0.2.0/ minor fixes

This commit is contained in:
Vladless
2025-07-18 20:13:39 +03:00
parent 8354aac254
commit b16072d6e0
9 changed files with 67 additions and 22 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ from api.routes import users, keys, coupons, servers, tariffs, gifts, referrals,
app = FastAPI(
title="SoloBot API (preAlpha)",
version="0.1.2",
version="0.2.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json"
+32 -3
View File
@@ -1,16 +1,16 @@
from fastapi import Depends, HTTPException, Path, Body
from fastapi import Depends, HTTPException, Path, Body, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from datetime import datetime
from database.models import Key, Admin, Tariff
from api.schemas.keys import KeyBase, KeyResponse, KeyUpdate
from api.schemas.keys import KeyBase, KeyResponse, KeyUpdate, KeyCreateRequest
from api.routes.base_crud import generate_crud_router
from api.depends import get_session, verify_admin_token
from handlers.keys.key_utils import delete_key_from_cluster
from logger import logger
from handlers.keys.key_utils import renew_key_in_cluster
from handlers.keys.key_utils import renew_key_in_cluster, create_key_on_cluster
router = generate_crud_router(
model=Key,
@@ -119,3 +119,32 @@ async def edit_key_by_email(
except Exception as e:
logger.error(f"[API] Ошибка при обновлении ключа: {e}")
raise HTTPException(status_code=500, detail="Ошибка при обновлении ключа")
@router.post("/create", response_model=dict, status_code=status.HTTP_201_CREATED)
async def create_key_api(
payload: KeyCreateRequest = Body(...),
session: AsyncSession = Depends(get_session),
admin: Admin = Depends(verify_admin_token),
):
logger.info(f"[API] Запрос на создание ключа: {payload.dict()}")
try:
await create_key_on_cluster(
cluster_id=payload.cluster_id,
tg_id=payload.tg_id,
client_id=payload.client_id,
email=payload.email or f"{payload.tg_id}_key",
expiry_timestamp=payload.expiry_timestamp,
plan=payload.tariff_id,
session=session,
remnawave_link=payload.remnawave_link,
hwid_limit=payload.hwid_limit,
traffic_limit_bytes=payload.traffic_limit_bytes,
is_trial=payload.is_trial or False,
)
return {"message": "Ключ успешно создан"}
except Exception as e:
logger.error(f"[API] Ошибка при создании ключа: {e}")
raise HTTPException(status_code=500, detail="Ошибка при создании ключа")
+5 -3
View File
@@ -13,10 +13,12 @@ class CouponBase(BaseModel):
@model_validator(mode="after")
def check_exactly_one_of_amount_or_days(self) -> "CouponBase":
has_amount = getattr(self, "amount", None) is not None
has_days = getattr(self, "days", None) is not None
has_amount = self.amount not in (None, 0)
has_days = self.days is not None
if has_amount == has_days:
if has_amount and has_days:
raise ValueError("Coupon must have exactly one of: 'amount' or 'days'")
if not has_amount and not has_days:
raise ValueError("Coupon must have exactly one of: 'amount' or 'days'")
return self
+13 -7
View File
@@ -1,4 +1,4 @@
from pydantic import BaseModel
from pydantic import BaseModel, Field
from typing import Optional
@@ -63,9 +63,15 @@ class KeyUpdate(BaseModel):
class KeyCreateRequest(BaseModel):
tg_id: int
cluster_id: str
tariff_id: int
email: Optional[str] = None
alias: Optional[str] = None
remnawave_link: Optional[str] = None
tg_id: int = Field(..., description="Telegram ID пользователя")
cluster_id: str = Field(..., description="Имя кластера или сервера")
tariff_id: int = Field(..., description="ID тарифа из базы данных")
client_id: str = Field(..., description="UUID клиента (уникальный)")
expiry_timestamp: int = Field(..., description="Срок окончания в миллисекундах")
email: Optional[str] = Field(None, description="Условное имя подписки")
alias: Optional[str] = Field(None, description="пользовательское имя")
remnawave_link: Optional[str] = Field(None, description="Ссылка на подписку Remnawave")
hwid_limit: Optional[int] = Field(None, description="Ограничение по HWID")
traffic_limit_bytes: Optional[int] = Field(None, description="Ограничение трафика в байтах")
is_trial: Optional[bool] = Field(False, description="Флаг триального ключа")
+11 -4
View File
@@ -6,6 +6,7 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, Message, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.exceptions import TelegramBadRequest
from sqlalchemy import distinct, func, select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -84,10 +85,16 @@ def parse_message_buttons(text: str) -> tuple[str, InlineKeyboardMarkup | None]:
IsAdminFilter(),
)
async def handle_sender(callback_query: CallbackQuery):
await callback_query.message.edit_text(
text="✍️ Выберите группу пользователей для рассылки:",
reply_markup=build_sender_kb(),
)
try:
await callback_query.message.edit_text(
text="✍️ Выберите группу пользователей для рассылки:",
reply_markup=build_sender_kb(),
)
except TelegramBadRequest as e:
if "message is not modified" in str(e):
logger.debug("[Sender] Сообщение не изменено, Telegram отклонил редактирование")
else:
raise
@router.callback_query(
+1 -1
View File
@@ -1341,7 +1341,7 @@ async def change_expiry_time(
tariff = result.first()
if tariff:
traffic_limit = int(tariff[0]) if tariff[0] is not None else 0
device_limit = int(tariff[1]) if tariff[1] is not None else None
device_limit = int(tariff[1]) if tariff[1] is not None else 0
servers = await get_servers(session=session)
+1 -1
View File
@@ -235,7 +235,7 @@ async def handle_key_extension(
if key.tariff_id:
tariff = await get_tariff_by_id(session, key.tariff_id)
total_gb = int(tariff["traffic_limit"]) if tariff and tariff.get("traffic_limit") else 0
device_limit = int(tariff["device_limit"]) if tariff and tariff.get("device_limit") else None
device_limit = int(tariff["device_limit"]) if tariff and tariff.get("device_limit") else 0
await renew_key_in_cluster(
cluster_id=key.server_id,
+1 -1
View File
@@ -79,7 +79,7 @@ async def key_cluster_mode(
is_trial = data.get("is_trial", False)
device_limit = 0
traffic_limit_gb = None
traffic_limit_gb = 0
if is_trial:
device_limit = TRIAL_CONFIG.get("hwid_limit", 0)
+2 -1
View File
@@ -132,6 +132,7 @@ async def create_key_on_cluster(
user_data["shortUuid"] = short_uuid
if hwid_limit is not None:
user_data["hwidDeviceLimit"] = hwid_limit
logger.info(f"[Key Creation] Данные для создания клиента в Remnawave: {user_data}")
result = await remna.create_user(user_data)
if not result:
@@ -236,7 +237,7 @@ async def create_client_on_server(
sub_id = unique_email
total_gb_value = 0
device_limit_value = None
device_limit_value = 0
if is_trial:
total_gb_value = TRIAL_CONFIG.get("traffic_limit_gb", 0)