from fastapi import FastAPI, Depends, HTTPException, status, Query from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlmodel import SQLModel, Field, create_engine, Session, select from passlib.context import CryptContext from typing import Optional, List, Dict from datetime import datetime, timedelta from models import RefAddRequest, RefResponse, RegisterRequest, Token, TokenRequest from uuid import uuid4 from fastapi.responses import JSONResponse from sqlalchemy import func # Конфигурация AUTH_DATABASE_ADDRESS = "sqlite:///partner.db" #SQLModel class TgAgent(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) tg_id: int = Field(index=True, unique=True) chat_id: Optional[int] = None phone: Optional[str] = None name: Optional[str] = None login: Optional[str] = None create_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow) class Ref(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) tg_agent_id: int = Field(foreign_key="tgagent.id") ref: str description: Optional[str] = None create_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow) class Sale(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) cost: float crediting: float # сколько начислено за продажу ref: int = Field(foreign_key="ref.id") sale_id: str create_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow) class Transaction(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) transaction_id: str = Field(default_factory=lambda: str(uuid4()), index=True, unique=True) sum: float tg_agent_id: int = Field(foreign_key="tgagent.id") status: str # 'process' || 'done' || 'error' || 'waiting' create_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow) # Создание движка базы данных AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True) SQLModel.metadata.create_all(AUTH_DB_ENGINE) oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token") # FastAPI app app = FastAPI() # CRUD def get_tg_agent_by_tg_id(db: Session, tg_id: int) -> Optional[TgAgent]: statement = select(TgAgent).where(TgAgent.tg_id == tg_id) return db.exec(statement).first() # Dependency def get_db(): with Session(AUTH_DB_ENGINE) as session: yield session # Авторизация async def get_current_tg_agent(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) # Ожидаем токен вида 'session_for_{tg_id}' if not token.startswith("session_for_"): raise credentials_exception try: tg_id = int(token.replace("session_for_", "")) except Exception: raise credentials_exception tg_agent = get_tg_agent_by_tg_id(db, tg_id) if tg_agent is None: raise credentials_exception return tg_agent # Регистрация @app.post("/register", tags=["partner-tg"]) def register(req: RegisterRequest, db: Session = Depends(get_db)): tg_id = req.tg_id chat_id = req.chat_id phone = req.phone name = getattr(req, 'name', None) login = getattr(req, 'login', None) print(f'tg_id: {tg_id}, chat_id: {chat_id}, phone: {phone}, name: {name}, login: {login}') tg_agent = get_tg_agent_by_tg_id(db, tg_id) if tg_agent: raise HTTPException(status_code=400, detail="tg_id already registered") new_tg_agent = TgAgent(tg_id=tg_id, chat_id=chat_id, phone=phone, name=name, login=login) db.add(new_tg_agent) db.commit() db.refresh(new_tg_agent) return {"msg": "TgAgent registered successfully"} def authenticate_tg_agent(engine, tg_id: int): with Session(engine) as db: tg_agent = get_tg_agent_by_tg_id(db, tg_id) if not tg_agent: return None return tg_agent # Защищённый эндпоинт @app.get("/protected", tags=["partner-tg"]) def protected_route(current_tg_agent: TgAgent = Depends(get_current_tg_agent)): return {"msg": f"Hello, {current_tg_agent.tg_id}! This is a protected route."} # Авторизация @app.post("/token", response_model=Token, tags=["partner-tg"]) async def login_for_access_token(req: TokenRequest): tg_id = req.tg_id tg_agent = authenticate_tg_agent(AUTH_DB_ENGINE, tg_id) if not tg_agent: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect tg_id", headers={"WWW-Authenticate": "Bearer"}, ) access_token = f"session_for_{tg_agent.tg_id}" return Token(access_token=access_token, token_type="bearer") @app.get("/ref", response_model=list[RefResponse], tags=["partner-tg"]) def get_refs(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)): refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all() return [RefResponse(ref=r.ref, description=r.description or "") for r in refs] @app.post("/ref/add", tags=["partner-tg"]) def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)): new_ref = Ref( tg_agent_id=current_tg_agent.id, ref=str(uuid4()), description=req.description ) db.add(new_ref) db.commit() db.refresh(new_ref) return {"ref": new_ref.ref} @app.get("/ref/stat", tags=["partner-tg"]) def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)): # 1. Получаем все реферальные ссылки пользователя refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all() result = [] for ref in refs: # 2. Для каждой ссылки считаем продажи и сумму sales = db.exec(select(Sale).where(Sale.ref == ref.id)).all() sales_count = len(sales) income = sum(sale.crediting for sale in sales) result.append({ "description": ref.description or "", "sales": sales_count, "income": income }) return {"refData": result} @app.get("/stat", tags=["partner-tg"]) def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_db)): # 1. Получаем все реферальные ссылки пользователя refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all() ref_ids = [r.id for r in refs] # 2. Считаем totalSales (продажи по всем рефам пользователя) total_sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all() totalSales = len(total_sales) totalIncome = sum(sale.crediting for sale in total_sales) withdrawals = db.exec( select(Transaction).where( Transaction.tg_agent_id == current_tg_agent.id ) ).all() availableWithdrawal = totalIncome - sum(t.sum for t in withdrawals) return { "totalSales": totalSales, "totalIncome": totalIncome, "availableWithdrawal": availableWithdrawal } @app.get("/dashboard/cards", tags=["bff"]) def get_dashboard_cards(db: Session = Depends(get_db)): # 1. Общий доход - сумма всех Sale.cost total_revenue = db.exec(select(Sale)).all() totalRevenue = sum(sale.cost for sale in total_revenue) # 2. Общие выплаты - сумма всех Sale.crediting totalPayouts = sum(sale.crediting for sale in total_revenue) # 3. Активные рефералы - количество уникальных TgAgent.tg_id unique_agents = db.exec(select(TgAgent.tg_id)).all() activeReferrals = len(set(unique_agents)) # 4. Ожидающие выплаты - разница между суммой всех Sale.crediting и суммой всех Transaction.sum all_transactions = db.exec(select(Transaction)).all() totalTransactions = sum(t.sum for t in all_transactions) pendingPayouts = totalPayouts - totalTransactions # 5. Количество продаж totalSales = len(total_revenue) return { "totalRevenue": totalRevenue, "totalPayouts": totalPayouts, "activeReferrals": activeReferrals, "pendingPayouts": pendingPayouts, "totalSales": totalSales } @app.get("/dashboard/chart/total", tags=["bff"]) def get_dashboard_chart_total(db: Session = Depends(get_db)): # Группируем продажи по дате (день) result = db.exec( select( func.strftime('%Y-%m-%d', Sale.create_dttm).label('date'), func.sum(Sale.cost).label('revenue'), func.count(Sale.id).label('sales') ).group_by(func.strftime('%Y-%m-%d', Sale.create_dttm)) .order_by(func.strftime('%Y-%m-%d', Sale.create_dttm)) ).all() # Преобразуем результат в нужный формат data = [ {"date": row.date, "revenue": row.revenue or 0, "sales": row.sales or 0} for row in result ] return JSONResponse(content=data) @app.get("/dashboard/chart/agent", tags=["bff"]) def get_dashboard_chart_agent(db: Session = Depends(get_db)): # Получаем всех агентов agents = db.exec(select(TgAgent)).all() result = [] for agent in agents: # Получаем все рефы этого агента refs = db.exec(select(Ref).where(Ref.tg_agent_id == agent.id)).all() ref_ids = [r.id for r in refs] if not ref_ids: result.append({ "name": agent.name or f"Агент {agent.id}", "count": 0, "sum": 0.0 }) continue # Получаем все продажи по этим рефам sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all() sales_count = len(sales) sales_sum = sum(sale.cost for sale in sales) result.append({ "name": agent.name or f"Агент {agent.id}", "count": sales_count, "sum": sales_sum }) return JSONResponse(content=result) @app.get("/stat/agents", tags=["bff"]) def get_agents_stat( db: Session = Depends(get_db), date_start: str = Query(None), date_end: str = Query(None), ): agents_query = select(TgAgent) if date_start: agents_query = agents_query.where(TgAgent.create_dttm >= date_start) if date_end: agents_query = agents_query.where(TgAgent.create_dttm <= date_end) agents = db.exec(agents_query).all() result = [] for agent in agents: refs = db.exec(select(Ref).where(Ref.tg_agent_id == agent.id)).all() ref_ids = [r.id for r in refs] ref_count = len(ref_ids) if not ref_ids: result.append({ "name": agent.name or f"Агент {agent.id}", "refCount": 0, "salesCount": 0, "salesSum": 0.0, "crediting": 0.0 }) continue sales = db.exec(select(Sale).where(Sale.ref.in_(ref_ids))).all() sales_count = len(sales) sales_sum = sum(sale.cost for sale in sales) crediting_sum = sum(sale.crediting for sale in sales) result.append({ "name": agent.name or f"Агент {agent.id}", "refCount": ref_count, "salesCount": sales_count, "salesSum": sales_sum, "crediting": crediting_sum }) return JSONResponse(content=result) @app.get("/stat/referrals", tags=["bff"]) def get_referrals_stat( db: Session = Depends(get_db), date_start: str = Query(None), date_end: str = Query(None), ): refs_query = select(Ref) if date_start: refs_query = refs_query.where(Ref.create_dttm >= date_start) if date_end: refs_query = refs_query.where(Ref.create_dttm <= date_end) refs = db.exec(refs_query).all() result = [] for ref in refs: agent = db.exec(select(TgAgent).where(TgAgent.id == ref.tg_agent_id)).first() sales = db.exec(select(Sale).where(Sale.ref == ref.id)).all() sales_count = len(sales) sales_sum = sum(sale.cost for sale in sales) result.append({ "ref": ref.ref, "agent": agent.name if agent and agent.name else f"Агент {ref.tg_agent_id}", "description": ref.description or "", "salesSum": sales_sum, "salesCount": sales_count }) return JSONResponse(content=result) @app.get("/stat/sales", tags=["bff"]) def get_sales_stat( db: Session = Depends(get_db), date_start: str = Query(None), date_end: str = Query(None), ): sales_query = select(Sale) if date_start: sales_query = sales_query.where(Sale.create_dttm >= date_start) if date_end: sales_query = sales_query.where(Sale.create_dttm <= date_end) sales = db.exec(sales_query).all() ref_ids = list(set(sale.ref for sale in sales)) refs = db.exec(select(Ref).where(Ref.id.in_(ref_ids))).all() if ref_ids else [] ref_map = {ref.id: ref for ref in refs} agent_ids = list(set(ref.tg_agent_id for ref in refs)) if refs else [] agents = db.exec(select(TgAgent).where(TgAgent.id.in_(agent_ids))).all() if agent_ids else [] agent_map = {agent.id: agent for agent in agents} result = [] for sale in sales: ref_obj = ref_map.get(sale.ref) agent_obj = agent_map.get(ref_obj.tg_agent_id) if ref_obj else None result.append({ "saleId": sale.sale_id, "cost": sale.cost, "crediting": sale.crediting, "ref": ref_obj.ref if ref_obj else None, "name": agent_obj.name if agent_obj else None }) return JSONResponse(content=result) @app.get("/billing/cards", tags=["bff"]) def get_billing_cards(db: Session = Depends(get_db)): # 1. cost - сумма всех Sale.cost sales = db.exec(select(Sale)).all() cost = sum(sale.cost for sale in sales) # 2. crediting - сумма всех Sale.crediting crediting = sum(sale.crediting for sale in sales) # 3. pendingPayouts - разница между crediting и суммой всех Transaction.sum transactions = db.exec(select(Transaction)).all() total_transactions = sum(t.sum for t in transactions) pendingPayouts = crediting - total_transactions return { "cost": cost, "crediting": crediting, "pendingPayouts": pendingPayouts } @app.get("/billing/payouts/transactions", tags=["bff"]) def get_billing_payouts_transactions( db: Session = Depends(get_db), date_start: str = Query(None), date_end: str = Query(None), ): query = select(Transaction) if date_start: query = query.where(Transaction.create_dttm >= date_start) if date_end: query = query.where(Transaction.create_dttm <= date_end) transactions = db.exec(query).all() result = [] for t in transactions: agent = db.exec(select(TgAgent).where(TgAgent.id == t.tg_agent_id)).first() result.append({ "id": t.transaction_id, "sum": t.sum, "agent": agent.name if agent else None, "status": t.status, "create_dttm": t.create_dttm, "update_dttm": t.update_dttm, }) return result @app.get("/billing/chart/stat", tags=["bff"]) def get_billing_chart_stat(db: Session = Depends(get_db)): # Группируем транзакции по дате (день) и статусу result = db.exec( select( func.strftime('%Y-%m-%d', Transaction.create_dttm).label('date'), Transaction.status.label('status'), func.count(Transaction.id).label('count') ).group_by( func.strftime('%Y-%m-%d', Transaction.create_dttm), Transaction.status ).order_by( func.strftime('%Y-%m-%d', Transaction.create_dttm), Transaction.status ) ).all() data = [ {"date": row.date, "status": row.status, "count": row.count} for row in result ] return JSONResponse(content=data) @app.get("/billing/chart/pie", tags=["bff"]) def get_billing_chart_pie(db: Session = Depends(get_db)): result = db.exec( select( Transaction.status.label('status'), func.count(Transaction.id).label('count') ).group_by(Transaction.status) ).all() data = [ {"status": row.status, "count": row.count} for row in result ] return JSONResponse(content=data)