API/ remnawave 1.6.12/ admins in database and more
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
from typing import AsyncGenerator
|
||||
import hashlib
|
||||
|
||||
from fastapi import Depends, HTTPException, Header, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from database import async_session_maker
|
||||
from database.models import Admin
|
||||
|
||||
|
||||
async def get_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with async_session_maker() as session:
|
||||
yield session
|
||||
|
||||
|
||||
def hash_token(token: str) -> str:
|
||||
return hashlib.sha256(token.encode()).hexdigest()
|
||||
|
||||
|
||||
async def verify_admin_token(
|
||||
admin_id: int = Query(..., alias="tg_id"),
|
||||
token: str = Header(..., alias="X-Token"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> Admin:
|
||||
hashed = hash_token(token)
|
||||
result = await session.execute(
|
||||
select(Admin).where(Admin.tg_id == admin_id, Admin.token == hashed)
|
||||
)
|
||||
admin = result.scalar_one_or_none()
|
||||
if not admin:
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
return admin
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
from fastapi import FastAPI
|
||||
from api.routes import users, keys, coupons, servers, tariffs
|
||||
|
||||
app = FastAPI(
|
||||
title="SoloBot API (preAlpha)",
|
||||
version="0.1",
|
||||
docs_url="/api/docs",
|
||||
redoc_url="/api/redoc",
|
||||
openapi_url="/api/openapi.json"
|
||||
)
|
||||
|
||||
|
||||
app.include_router(users.router, prefix="/api/users", tags=["Users"])
|
||||
app.include_router(keys.router, prefix="/api/keys", tags=["Keys"])
|
||||
app.include_router(coupons.router, prefix="/api/coupons", tags=["Coupons"])
|
||||
app.include_router(servers.router, prefix="/api/servers", tags=["Servers"])
|
||||
app.include_router(tariffs.router, prefix="/api/tariffs", tags=["Tariffs"])
|
||||
|
||||
|
||||
@app.get("/api", include_in_schema=False)
|
||||
async def root():
|
||||
return {"message": "Welcome to SoloBot API. Docs: /api/docs"}
|
||||
@@ -0,0 +1,124 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm.attributes import InstrumentedAttribute
|
||||
from typing import Type, Union, Any
|
||||
|
||||
from api.depends import get_session, verify_admin_token
|
||||
from database.models import Admin
|
||||
|
||||
|
||||
def _cast_identifier_type(field: InstrumentedAttribute, value: Union[int, str]):
|
||||
column_type = type(field.property.columns[0].type).__name__
|
||||
if column_type in ("Integer", "BigInteger"):
|
||||
return int(value)
|
||||
return value
|
||||
|
||||
|
||||
def generate_crud_router(
|
||||
*,
|
||||
model: Type,
|
||||
schema_response: Type,
|
||||
schema_create: Type,
|
||||
schema_update: Type,
|
||||
identifier_field: str = "tg_id",
|
||||
parameter_name: str = "tg_id",
|
||||
extra_get_by_email: bool = False,
|
||||
enabled_methods: list[str] = ("get_all", "get_one", "get_by_email", "create", "update", "delete")
|
||||
) -> APIRouter:
|
||||
router = APIRouter()
|
||||
|
||||
if "get_all" in enabled_methods:
|
||||
@router.get("/", response_model=list[schema_response])
|
||||
async def get_all(
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(model))
|
||||
return result.scalars().all()
|
||||
|
||||
if "get_by_email" in enabled_methods and extra_get_by_email:
|
||||
@router.get("/by_email", response_model=schema_response)
|
||||
async def get_by_email(
|
||||
email: str = Query(...),
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
result = await session.execute(select(model).where(model.email == email))
|
||||
obj = result.scalar_one_or_none()
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail="Not found by email")
|
||||
return obj
|
||||
|
||||
if "get_one" in enabled_methods:
|
||||
@router.get(f"/{{{parameter_name}}}", response_model=schema_response)
|
||||
async def get_one(
|
||||
value: Union[int, str] = Path(..., alias=parameter_name),
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
field = getattr(model, identifier_field)
|
||||
casted = _cast_identifier_type(field, value)
|
||||
result = await session.execute(select(model).where(field == casted))
|
||||
obj = result.scalar_one_or_none()
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail=f"{model.__name__} not found")
|
||||
return obj
|
||||
|
||||
if "create" in enabled_methods:
|
||||
@router.post("/", response_model=schema_response)
|
||||
async def create(
|
||||
payload: schema_create, # type: ignore
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
data = payload.dict(exclude_unset=True)
|
||||
if "days" in data and data["days"] == 0:
|
||||
data["days"] = None
|
||||
obj = model(**data)
|
||||
session.add(obj)
|
||||
await session.commit()
|
||||
await session.refresh(obj)
|
||||
return obj
|
||||
|
||||
if "update" in enabled_methods:
|
||||
@router.patch(f"/{{{parameter_name}}}", response_model=schema_response)
|
||||
async def update(
|
||||
payload: schema_update, # type: ignore
|
||||
value: Union[int, str] = Path(..., alias=parameter_name),
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
field = getattr(model, identifier_field)
|
||||
casted = _cast_identifier_type(field, value)
|
||||
result = await session.execute(select(model).where(field == casted))
|
||||
obj = result.scalar_one_or_none()
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail=f"{model.__name__} not found")
|
||||
|
||||
for k, v in payload.dict(exclude_unset=True).items():
|
||||
setattr(obj, k, v)
|
||||
|
||||
await session.commit()
|
||||
await session.refresh(obj)
|
||||
return obj
|
||||
|
||||
if "delete" in enabled_methods:
|
||||
@router.delete(f"/{{{parameter_name}}}", response_model=dict)
|
||||
async def delete(
|
||||
value: Union[int, str] = Path(..., alias=parameter_name),
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
field = getattr(model, identifier_field)
|
||||
casted = _cast_identifier_type(field, value)
|
||||
result = await session.execute(select(model).where(field == casted))
|
||||
obj = result.scalar_one_or_none()
|
||||
if not obj:
|
||||
raise HTTPException(status_code=404, detail=f"{model.__name__} not found")
|
||||
|
||||
await session.delete(obj)
|
||||
await session.commit()
|
||||
return {"detail": f"{model.__name__} deleted"}
|
||||
|
||||
return router
|
||||
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
from api.routes.base_crud import generate_crud_router
|
||||
from api.schemas import CouponBase, CouponResponse, CouponUpdate
|
||||
from database.models import Coupon
|
||||
|
||||
router: APIRouter = generate_crud_router(
|
||||
model=Coupon,
|
||||
schema_response=CouponResponse,
|
||||
schema_create=CouponBase,
|
||||
schema_update=CouponUpdate,
|
||||
identifier_field="code",
|
||||
parameter_name="code",
|
||||
enabled_methods=["get_all", "get_one", "create", "update", "delete"]
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
from fastapi import Depends, HTTPException, Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
from database.models import Key, Admin
|
||||
from api.schemas.keys import KeyBase, KeyResponse, KeyUpdate
|
||||
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
|
||||
|
||||
router = generate_crud_router(
|
||||
model=Key,
|
||||
schema_response=KeyResponse,
|
||||
schema_create=KeyBase,
|
||||
schema_update=KeyUpdate,
|
||||
identifier_field="tg_id",
|
||||
extra_get_by_email=True,
|
||||
enabled_methods=["get_all", "get_one", "get_by_email"]
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/by_email/{email}", response_model=dict)
|
||||
async def delete_key_by_email(
|
||||
email: str = Path(..., description="Email клиента"),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
admin: Admin = Depends(verify_admin_token),
|
||||
):
|
||||
result = await session.execute(select(Key).where(Key.email == email))
|
||||
db_key = result.scalar_one_or_none()
|
||||
|
||||
if not db_key:
|
||||
raise HTTPException(status_code=404, detail="Ключ не найден")
|
||||
|
||||
try:
|
||||
await delete_key_from_cluster(
|
||||
session=session,
|
||||
email=db_key.email,
|
||||
client_id=db_key.client_id,
|
||||
cluster_id=db_key.server_id,
|
||||
)
|
||||
await session.delete(db_key)
|
||||
await session.commit()
|
||||
logger.info(f"[API] Ключ удалён: {db_key.client_id}")
|
||||
return {"message": "Ключ успешно удалён"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[API] Ошибка при удалении ключа: {e}")
|
||||
raise HTTPException(status_code=500, detail="Ошибка при удалении ключа")
|
||||
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
from api.routes.base_crud import generate_crud_router
|
||||
from api.schemas import ServerBase, ServerResponse, ServerUpdate
|
||||
from database.models import Server
|
||||
|
||||
router: APIRouter = generate_crud_router(
|
||||
model=Server,
|
||||
schema_response=ServerResponse,
|
||||
schema_create=ServerBase,
|
||||
schema_update=ServerUpdate,
|
||||
identifier_field="server_name",
|
||||
parameter_name="server_name",
|
||||
enabled_methods=["get_all", "get_one", "create", "update", "delete"]
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
from fastapi import APIRouter
|
||||
from api.routes.base_crud import generate_crud_router
|
||||
from api.schemas import TariffBase, TariffResponse, TariffUpdate
|
||||
from database.models import Tariff
|
||||
|
||||
router: APIRouter = generate_crud_router(
|
||||
model=Tariff,
|
||||
schema_response=TariffResponse,
|
||||
schema_create=TariffBase,
|
||||
schema_update=TariffUpdate,
|
||||
identifier_field="name",
|
||||
parameter_name="name",
|
||||
enabled_methods=["get_all", "get_one", "create", "update", "delete"]
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
from fastapi import Depends, HTTPException, Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from api.routes.base_crud import generate_crud_router
|
||||
from api.schemas.users import UserBase, UserResponse, UserUpdate
|
||||
from database.models import User, Key
|
||||
from api.depends import get_session, verify_admin_token
|
||||
from handlers.keys.key_utils import delete_key_from_cluster
|
||||
from database import get_servers, delete_user_data
|
||||
from logger import logger
|
||||
import asyncio
|
||||
|
||||
|
||||
router = generate_crud_router(
|
||||
model=User,
|
||||
schema_response=UserResponse,
|
||||
schema_create=UserBase,
|
||||
schema_update=UserUpdate,
|
||||
identifier_field="tg_id",
|
||||
enabled_methods=["get_all", "get_one", "get_by_email", "create", "update"]
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{tg_id}", response_model=dict)
|
||||
async def delete_user(
|
||||
tg_id: int = Path(..., description="Telegram ID пользователя"),
|
||||
admin=Depends(verify_admin_token),
|
||||
session: AsyncSession = Depends(get_session),
|
||||
):
|
||||
try:
|
||||
result = await session.execute(
|
||||
select(Key.email, Key.client_id).where(Key.tg_id == tg_id)
|
||||
)
|
||||
key_records = result.all()
|
||||
|
||||
async def delete_keys_from_servers():
|
||||
try:
|
||||
servers = await get_servers(session=session)
|
||||
tasks = []
|
||||
for email, client_id in key_records:
|
||||
for cluster_id in servers:
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id, session))
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
except Exception as e:
|
||||
logger.error(f"[DELETE] Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}")
|
||||
|
||||
await delete_keys_from_servers()
|
||||
await delete_user_data(session, tg_id)
|
||||
|
||||
return {"detail": f"Пользователь {tg_id} и его ключи успешно удалены."}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[DELETE] Ошибка при удалении пользователя {tg_id}: {e}")
|
||||
raise HTTPException(status_code=500, detail="Ошибка при удалении пользователя")
|
||||
@@ -0,0 +1,8 @@
|
||||
from .users import UserBase, UserResponse, UserUpdate
|
||||
from .keys import KeyResponse, KeyDetailsResponse
|
||||
from .tariffs import TariffResponse, TariffUpdate, TariffBase
|
||||
from .servers import ServerResponse, ServerBase, ServerUpdate
|
||||
from .coupons import CouponResponse, CouponUsageResponse, CouponBase, CouponUpdate
|
||||
from .referrals import ReferralResponse
|
||||
from .gifts import GiftResponse, GiftUsageResponse
|
||||
from .misc import ManualBanResponse, TemporaryDataResponse, BlockedUserResponse, TrackingSourceResponse, PaymentResponse, NotificationResponse
|
||||
@@ -0,0 +1,54 @@
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class CouponBase(BaseModel):
|
||||
code: str
|
||||
amount: Optional[int] = Field(default=None)
|
||||
usage_limit: int
|
||||
usage_count: int = 0
|
||||
is_used: bool = False
|
||||
days: Optional[int] = Field(default=None)
|
||||
|
||||
@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
|
||||
|
||||
if has_amount == has_days:
|
||||
raise ValueError("Coupon must have exactly one of: 'amount' or 'days'")
|
||||
return self
|
||||
|
||||
|
||||
class CouponResponse(CouponBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class CouponUpdate(BaseModel):
|
||||
code: Optional[str] = None
|
||||
amount: Optional[int] = None
|
||||
usage_limit: Optional[int] = None
|
||||
usage_count: Optional[int] = None
|
||||
is_used: Optional[bool] = None
|
||||
days: Optional[int] = Field(default=None)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_amount_or_days(self) -> "CouponUpdate":
|
||||
if self.amount is None and self.days is None:
|
||||
return self
|
||||
if self.amount is not None and self.days is not None:
|
||||
raise ValueError("Specify only one of: 'amount' or 'days'")
|
||||
return self
|
||||
|
||||
|
||||
class CouponUsageResponse(BaseModel):
|
||||
coupon_id: int
|
||||
user_id: int
|
||||
used_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,32 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class GiftBase(BaseModel):
|
||||
sender_tg_id: int
|
||||
recipient_tg_id: Optional[int] = None
|
||||
selected_months: int
|
||||
expiry_time: datetime
|
||||
gift_link: str
|
||||
is_used: bool = False
|
||||
is_unlimited: bool = False
|
||||
max_usages: Optional[int] = None
|
||||
tariff_id: Optional[int] = None
|
||||
|
||||
|
||||
class GiftResponse(GiftBase):
|
||||
gift_id: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class GiftUsageResponse(BaseModel):
|
||||
gift_id: str
|
||||
tg_id: int
|
||||
used_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,71 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class KeyBase(BaseModel):
|
||||
tg_id: int
|
||||
client_id: str
|
||||
email: Optional[str] = None
|
||||
created_at: Optional[int] = None
|
||||
expiry_time: int
|
||||
key: Optional[str] = None
|
||||
server_id: Optional[str] = None
|
||||
remnawave_link: Optional[str] = None
|
||||
tariff_id: Optional[int] = None
|
||||
is_frozen: Optional[bool] = False
|
||||
alias: Optional[str] = None
|
||||
notified: Optional[bool] = False
|
||||
notified_24h: Optional[bool] = False
|
||||
|
||||
|
||||
class KeyResponse(KeyBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class KeyDetailsResponse(BaseModel):
|
||||
key: Optional[str]
|
||||
remnawave_link: Optional[str]
|
||||
server_id: Optional[str]
|
||||
created_at: Optional[int]
|
||||
expiry_time: Optional[int]
|
||||
client_id: str
|
||||
tg_id: int
|
||||
email: Optional[str]
|
||||
is_frozen: bool
|
||||
balance: float
|
||||
alias: Optional[str]
|
||||
expiry_date: str
|
||||
days_left_message: str
|
||||
link: Optional[str]
|
||||
cluster_name: Optional[str]
|
||||
location_name: Optional[str]
|
||||
tariff_id: Optional[int]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class KeyUpdate(BaseModel):
|
||||
email: Optional[str] = None
|
||||
expiry_time: Optional[int] = None
|
||||
key: Optional[str] = None
|
||||
server_id: Optional[str] = None
|
||||
remnawave_link: Optional[str] = None
|
||||
tariff_id: Optional[int] = None
|
||||
is_frozen: Optional[bool] = None
|
||||
alias: Optional[str] = None
|
||||
notified: Optional[bool] = None
|
||||
notified_24h: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,105 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Union, Literal
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PaymentBase(BaseModel):
|
||||
tg_id: int
|
||||
amount: float
|
||||
payment_system: str
|
||||
status: Literal["success", "pending", "failed"]
|
||||
|
||||
|
||||
class PaymentResponse(PaymentBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReferralResponse(BaseModel):
|
||||
referred_tg_id: int
|
||||
referrer_tg_id: int
|
||||
reward_issued: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class NotificationResponse(BaseModel):
|
||||
tg_id: int
|
||||
notification_type: str
|
||||
last_notification_time: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class GiftBase(BaseModel):
|
||||
sender_tg_id: int
|
||||
recipient_tg_id: Optional[int] = None
|
||||
selected_months: int
|
||||
expiry_time: datetime
|
||||
gift_link: str
|
||||
is_used: bool = False
|
||||
is_unlimited: bool = False
|
||||
max_usages: Optional[int] = None
|
||||
tariff_id: Optional[int] = None
|
||||
|
||||
|
||||
class GiftResponse(GiftBase):
|
||||
gift_id: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class GiftUsageResponse(BaseModel):
|
||||
gift_id: str
|
||||
tg_id: int
|
||||
used_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ManualBanResponse(BaseModel):
|
||||
tg_id: int
|
||||
banned_at: datetime
|
||||
reason: str
|
||||
banned_by: int
|
||||
until: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TemporaryDataResponse(BaseModel):
|
||||
tg_id: int
|
||||
state: str
|
||||
data: dict
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class BlockedUserResponse(BaseModel):
|
||||
tg_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TrackingSourceResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
code: str
|
||||
type: str
|
||||
created_by: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ReferralResponse(BaseModel):
|
||||
referred_tg_id: int
|
||||
referrer_tg_id: int
|
||||
reward_issued: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,36 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class ServerBase(BaseModel):
|
||||
cluster_name: str
|
||||
server_name: str
|
||||
api_url: str
|
||||
subscription_url: Optional[str] = None
|
||||
inbound_id: str
|
||||
panel_type: str
|
||||
max_keys: Optional[int] = None
|
||||
tariff_group: str
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class ServerResponse(ServerBase):
|
||||
id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ServerUpdate(BaseModel):
|
||||
cluster_name: Optional[str] = None
|
||||
server_name: Optional[str] = None
|
||||
api_url: Optional[str] = None
|
||||
subscription_url: Optional[str] = None
|
||||
inbound_id: Optional[str] = None
|
||||
panel_type: Optional[str] = None
|
||||
max_keys: Optional[int] = None
|
||||
tariff_group: Optional[str] = None
|
||||
enabled: Optional[bool] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,38 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
|
||||
class TariffBase(BaseModel):
|
||||
name: str
|
||||
group_code: str
|
||||
duration_days: int
|
||||
price_rub: int
|
||||
traffic_limit: Optional[int] = None
|
||||
device_limit: Optional[int] = None
|
||||
is_active: bool = True
|
||||
subgroup_title: Optional[str] = None
|
||||
|
||||
|
||||
class TariffResponse(TariffBase):
|
||||
id: int
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class TariffUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
group_code: Optional[str] = None
|
||||
duration_days: Optional[int] = None
|
||||
price_rub: Optional[int] = None
|
||||
traffic_limit: Optional[int] = None
|
||||
device_limit: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
subgroup_title: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,37 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
tg_id: int
|
||||
username: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
language_code: Optional[str] = None
|
||||
is_bot: Optional[bool] = False
|
||||
balance: float = 0.0
|
||||
trial: int = 0
|
||||
source_code: Optional[str] = None
|
||||
|
||||
|
||||
class UserResponse(UserBase):
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
username: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
language_code: Optional[str] = None
|
||||
is_bot: Optional[bool] = None
|
||||
balance: Optional[float] = None
|
||||
trial: Optional[int] = None
|
||||
source_code: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
Reference in New Issue
Block a user