Update WebApp

This commit is contained in:
2026-01-09 22:21:26 +03:00
parent cc272a9753
commit 32d0f98a6e
9 changed files with 1056 additions and 512 deletions

View File

@@ -8,7 +8,8 @@ import logging
import json
from database import db
from config import PLANS
from config import PLANS, CONFIG
from marzban import marzban
# Setup logging
logging.basicConfig(level=logging.INFO)
@@ -30,7 +31,6 @@ async def startup():
@app.get("/api/plans")
async def get_plans():
# Convert PLANS dict to list for easier frontend consumption
plans_list = []
for pid, p in PLANS.items():
plans_list.append({
@@ -45,32 +45,55 @@ from pydantic import BaseModel
class BuyPlanRequest(BaseModel):
user_id: int
plan_id: str
promo_code: str = None
class PromoCheckRequest(BaseModel):
code: str
@app.post("/api/check-promo")
async def check_promo_code(req: PromoCheckRequest):
promo = await db.get_promo_code(req.code)
if not promo:
return JSONResponse(status_code=404, content={"error": "Invalid or expired promo code"})
return {
"code": promo["code"],
"discount": promo["discount"],
"bonus_days": promo["bonus_days"],
"is_unlimited": promo["is_unlimited"],
"description": f"Discount {promo['discount']}%" + (f" + {promo['bonus_days']} Days" if promo['bonus_days'] else "")
}
@app.post("/api/create-invoice")
async def create_invoice(req: BuyPlanRequest, request: Request):
bot = getattr(request.app.state, "bot", None)
if not bot:
return JSONResponse(status_code=500, content={"error": "Bot instance not initialized in app state"})
return JSONResponse(status_code=500, content={"error": "Bot instance not initialized"})
plan = PLANS.get(req.plan_id)
if not plan:
return JSONResponse(status_code=404, content={"error": "Plan not found"})
# Determine price in Stars (XTR). Assuming Plan Price in config is in RUB, need conversion or direct usage.
# Telegram Stars usually 1 Star ~= 0.013 USD? Or direct mapping.
# User's bot code uses currency="XTR" and prices=[LabeledPrice(..., amount=final_price)].
# Usually amount is in smallest units? XTR amount is integer number of stars.
# Assuming config price IS stars or directly usable.
price = plan['price']
# Validating Promo
discount = 0
if req.promo_code:
promo = await db.get_promo_code(req.promo_code)
if promo:
discount = promo['discount']
final_price = int(price * (100 - discount) / 100)
if final_price < 1: final_price = 1
try:
invoice_link = await bot.create_invoice_link(
title=f"Sub: {plan['name']}",
description=f"{plan['data_limit']}GB / {plan['days']} days",
payload=f"{req.plan_id}:", # Promo code empty for now
payload=f"{req.plan_id}:{req.promo_code or ''}",
provider_token="", # Empty for Stars
currency="XTR",
prices=[LabeledPrice(label=plan['name'], amount=price)]
prices=[LabeledPrice(label=plan['name'], amount=final_price)]
)
return {"invoice_link": invoice_link}
except Exception as e:
@@ -86,7 +109,7 @@ async def get_user_stats(user_id: int):
sub_until = user['subscription_until']
days_left = 0
status = "Inactive"
expire_str = "-"
expire_str = "No active subscription"
if sub_until:
if isinstance(sub_until, str):
@@ -96,7 +119,7 @@ async def get_user_stats(user_id: int):
pass
if isinstance(sub_until, datetime):
expire_str = sub_until.strftime("%Y-%m-%d")
expire_str = sub_until.strftime("%d.%m.%Y")
if sub_until > datetime.now():
delta = sub_until - datetime.now()
days_left = delta.days
@@ -104,16 +127,40 @@ async def get_user_stats(user_id: int):
else:
status = "Expired"
# Fetch detailed stats from Marzban
sub_url = ""
used_traffic = 0
if user['marzban_username']:
try:
m_user = await marzban.get_user(user['marzban_username'])
# Check for error in response
if isinstance(m_user, dict) and not m_user.get('detail'):
used_traffic = m_user.get('used_traffic', 0)
sub_url = m_user.get('subscription_url', "")
# Fix relative URL
if sub_url and sub_url.startswith('/'):
base = CONFIG.get('BASE_URL') or CONFIG['MARZBAN_URL']
sub_url = f"{base.rstrip('/')}{sub_url}"
except Exception as e:
logger.error(f"Marzban fetch error: {e}")
return {
"status": status,
"days_left": days_left,
"expire_date": expire_str,
"data_usage": user['data_limit'] or 0,
"plan": "Custom"
"data_limit_gb": user['data_limit'] or 0,
"used_traffic_gb": round(used_traffic / (1024**3), 2),
"plan": "Premium",
"subscription_url": sub_url,
"username": user['username'],
"marzban_username": user['marzban_username']
}
# Serve Static Files (must be last)
app.mount("/", StaticFiles(directory="web_app/static", html=True), name="static")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
from config import CONFIG
uvicorn.run(app, host="0.0.0.0", port=CONFIG["WEB_APP_PORT"])