api 0.2.0/ minor fixes
This commit is contained in:
+1
-1
@@ -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
@@ -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="Ошибка при создании ключа")
|
||||
@@ -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
@@ -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="Флаг триального ключа")
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user