This commit is contained in:
2026-02-18 13:38:18 +05:00
commit 11780384e0
19 changed files with 813 additions and 0 deletions

17
api/route/default.py Normal file
View File

@@ -0,0 +1,17 @@
from fastapi import APIRouter
import toml
from pathlib import Path
router = APIRouter()
@router.get("/")
async def root():
version = "dev"
pyproject_path = Path(__file__).parent.parent.parent / "pyproject.toml"
if pyproject_path.exists():
try:
data = toml.load(pyproject_path)
version = data.get("project", {}).get("version", "dev")
except Exception:
pass
return {"status": "ok", "msg": "VPN Bot API", "version": version}

94
api/route/subscription.py Normal file
View File

@@ -0,0 +1,94 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional, List
from api.db.subscription import (
create_subscription,
get_subscription_by_user,
get_subscription_by_uuid,
update_subscription,
delete_subscription,
list_subscriptions,
extend_subscription_by_user,
)
import uuid
from datetime import datetime, timedelta
router = APIRouter(prefix="/subscription", tags=["subscription"])
class SubscriptionInfo(BaseModel):
user_id: str
uuid: str
until: datetime
active: bool
subscription_link: Optional[str] = None
connect_link: Optional[str] = None
class SubscriptionCreate(BaseModel):
user_id: str
months: int
subscription_link: Optional[str] = None
connect_link: Optional[str] = None
class SubscriptionUpdate(BaseModel):
until: Optional[datetime]
user_id: Optional[str]
active: Optional[bool] = None
subscription_link: Optional[str] = None
connect_link: Optional[str] = None
@router.get("/{user_id}", response_model=Optional[SubscriptionInfo])
async def get_user_subscription(user_id: str):
sub = await get_subscription_by_user(user_id)
if not sub:
return None
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until)
@router.post("/new", response_model=SubscriptionInfo)
async def create_subscription_endpoint(data: SubscriptionCreate):
sub_id = str(uuid.uuid4())
until = datetime.now() + timedelta(days=30 * data.months)
sub = await create_subscription(data.user_id, sub_id, until, subscription_link=data.subscription_link, connect_link=data.connect_link, active=True)
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until)
@router.post("/extend", response_model=SubscriptionInfo)
async def extend_user_subscription(data: SubscriptionCreate):
sub = await extend_subscription_by_user(data.user_id, data.months)
if not sub:
raise HTTPException(status_code=404, detail="Subscription not found")
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until, active=sub.active, subscription_link=sub.subscription_link, connect_link=sub.connect_link)
@router.get("/by-uuid/{sub_uuid}", response_model=Optional[SubscriptionInfo])
async def get_subscription_uuid(sub_uuid: str):
sub = await get_subscription_by_uuid(sub_uuid)
if not sub:
raise HTTPException(status_code=404, detail="Subscription not found")
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until, active=sub.active, subscription_link=sub.subscription_link, connect_link=sub.connect_link)
@router.patch("/{sub_uuid}", response_model=SubscriptionInfo)
async def update_subscription_endpoint(sub_uuid: str, data: SubscriptionUpdate):
sub = await update_subscription(sub_uuid, until=data.until, user_id=data.user_id, active=data.active, subscription_link=data.subscription_link, connect_link=data.connect_link)
if not sub:
raise HTTPException(status_code=404, detail="Subscription not found")
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until, active=sub.active, subscription_link=sub.subscription_link, connect_link=sub.connect_link)
@router.delete("/{sub_uuid}")
async def delete_subscription_endpoint(sub_uuid: str):
ok = await delete_subscription(sub_uuid)
if not ok:
raise HTTPException(status_code=404, detail="Subscription not found")
return {"ok": True}
@router.get("/", response_model=List[SubscriptionInfo])
async def list_all_subscriptions():
subs = await list_subscriptions()
return [SubscriptionInfo(user_id=s.user_id, uuid=s.uuid, until=s.until) for s in subs]

107
api/route/users.py Normal file
View File

@@ -0,0 +1,107 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional, List
from api.db.user import create_user, get_user, get_user_by_telegram, update_user, delete_user, list_users
from api.db.subscription import (
create_subscription,
get_subscription_by_user,
update_subscription,
delete_subscription,
)
from api.route.subscription import SubscriptionInfo
router = APIRouter(prefix="/users", tags=["users"])
class UserOut(BaseModel):
id: str
telegram_id: str
name: Optional[str]
subscription: Optional[SubscriptionInfo] = None
class UserCreate(BaseModel):
telegram_id: str
name: Optional[str]
class UserUpdate(BaseModel):
name: Optional[str]
@router.post("/", response_model=UserOut)
async def api_create_user(data: UserCreate):
existing = await get_user_by_telegram(data.telegram_id)
if existing:
raise HTTPException(status_code=400, detail="User already exists")
u = await create_user(data.telegram_id, data.name)
return UserOut(id=u.id, telegram_id=u.telegram_id, name=u.name)
@router.get("/{user_id}", response_model=UserOut)
async def api_get_user(user_id: str, expand: Optional[str] = None):
u = await get_user(user_id)
if not u:
raise HTTPException(status_code=404, detail="User not found")
out = UserOut(id=u.id, telegram_id=u.telegram_id, name=u.name)
if expand == "subscription":
sub = await get_subscription_by_user(u.id)
if sub:
out.subscription = SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until, active=sub.active, subscription_link=sub.subscription_link, connect_link=sub.connect_link)
return out
@router.post("/{user_id}/subscription", response_model=SubscriptionInfo)
async def api_create_user_subscription(user_id: str, data: dict):
# expected keys: months, subscription_link (opt), connect_link (opt)
months = int(data.get("months", 1))
subscription_link = data.get("subscription_link")
connect_link = data.get("connect_link")
import uuid as _uuid
sub_id = str(_uuid.uuid4())
from datetime import datetime, timedelta
until = datetime.now() + timedelta(days=30 * months)
sub = await create_subscription(user_id, sub_id, until, subscription_link=subscription_link, connect_link=connect_link, active=True)
return SubscriptionInfo(user_id=sub.user_id, uuid=sub.uuid, until=sub.until, active=sub.active, subscription_link=sub.subscription_link, connect_link=sub.connect_link)
@router.patch("/{user_id}/subscription", response_model=SubscriptionInfo)
async def api_update_user_subscription(user_id: str, data: dict):
sub = await get_subscription_by_user(user_id)
if not sub:
raise HTTPException(status_code=404, detail="Subscription not found for user")
updated = await update_subscription(sub.uuid, until=data.get("until"), user_id=data.get("user_id"), active=data.get("active"), subscription_link=data.get("subscription_link"), connect_link=data.get("connect_link"))
return SubscriptionInfo(user_id=updated.user_id, uuid=updated.uuid, until=updated.until, active=updated.active, subscription_link=updated.subscription_link, connect_link=updated.connect_link)
@router.delete("/{user_id}/subscription")
async def api_delete_user_subscription(user_id: str):
sub = await get_subscription_by_user(user_id)
if not sub:
raise HTTPException(status_code=404, detail="Subscription not found for user")
ok = await delete_subscription(sub.uuid)
if not ok:
raise HTTPException(status_code=500, detail="Failed to delete subscription")
return {"ok": True}
@router.get("/", response_model=List[UserOut])
async def api_list_users():
users = await list_users()
return [UserOut(id=u.id, telegram_id=u.telegram_id, name=u.name) for u in users]
@router.patch("/{user_id}", response_model=UserOut)
async def api_update_user(user_id: str, data: UserUpdate):
u = await update_user(user_id, name=data.name)
if not u:
raise HTTPException(status_code=404, detail="User not found")
return UserOut(id=u.id, telegram_id=u.telegram_id, name=u.name)
@router.delete("/{user_id}")
async def api_delete_user(user_id: str):
ok = await delete_user(user_id)
if not ok:
raise HTTPException(status_code=404, detail="User not found")
return {"ok": True}