Добавлены новые файлы: .gitignore для исключения временных файлов, fill_db.py для заполнения базы данных тестовыми данными, main.py с основным функционалом FastAPI, models.py с API моделями и requirements.txt для зависимостей проекта.
This commit is contained in:
448
main.py
Normal file
448
main.py
Normal file
@@ -0,0 +1,448 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user