from fastapi import FastAPI, HTTPException, Request from fastapi.staticfiles import StaticFiles from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import uvicorn from datetime import datetime import logging import json from database import db from config import PLANS # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger("server") app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) @app.on_event("startup") async def startup(): await db.connect() logger.info("Database connected") @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({ "id": pid, **p }) return plans_list from aiogram.types import LabeledPrice from pydantic import BaseModel class BuyPlanRequest(BaseModel): user_id: int plan_id: str @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"}) 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'] 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 provider_token="", # Empty for Stars currency="XTR", prices=[LabeledPrice(label=plan['name'], amount=price)] ) return {"invoice_link": invoice_link} except Exception as e: logger.error(f"Error generating invoice: {e}") return JSONResponse(status_code=500, content={"error": str(e)}) @app.get("/api/user/{user_id}") async def get_user_stats(user_id: int): user = await db.get_user(user_id) if not user: return JSONResponse(status_code=404, content={"error": "User not found"}) sub_until = user['subscription_until'] days_left = 0 status = "Inactive" expire_str = "-" if sub_until: if isinstance(sub_until, str): try: sub_until = datetime.fromisoformat(sub_until) except: pass if isinstance(sub_until, datetime): expire_str = sub_until.strftime("%Y-%m-%d") if sub_until > datetime.now(): delta = sub_until - datetime.now() days_left = delta.days status = "Active" else: status = "Expired" return { "status": status, "days_left": days_left, "expire_date": expire_str, "data_usage": user['data_limit'] or 0, "plan": "Custom" } # 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)