Compare commits

..

No commits in common. "d113ae4adbe410b612a183f632c07a61bc32bf2b" and "7045d6790a1b9a56befae54171ffae4edba15066" have entirely different histories.

9 changed files with 12 additions and 310 deletions

View File

@ -89,7 +89,6 @@ class StatReferralsItem(BaseModel):
ref: str
agent: Optional[str] = None
description: str
promocode: str
salesSum: float
salesCount: int

View File

@ -6,7 +6,6 @@ from sqlalchemy import text
from datetime import datetime, timedelta
from hashlib import sha256
from helpers_bff import AUTH_DB_ENGINE, get_password_hash
import string
# Константа: список user_ids
@ -72,7 +71,6 @@ def fill_db():
date_list = get_date_list(7) # 8 дней: от недели назад до сегодня
with Session(AUTH_DB_ENGINE) as session:
# Очистка таблиц
# session.execute(text("DELETE FROM promocode"))
session.execute(text("DELETE FROM sale"))
session.execute(text("DELETE FROM ref"))
session.execute(text("DELETE FROM tgagent"))
@ -151,28 +149,19 @@ def fill_db():
session.commit()
for tg_agent in tg_agents:
session.refresh(tg_agent)
# Отладка: количество агентов
agent_count = session.execute(text("SELECT COUNT(*) FROM tgagent")).scalar()
print(f'Агентов в базе: {agent_count}')
# 3. Refs (минимум 22 на агента)
refs = []
desc_count = len(ALL_DESCRIPTIONS)
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
for tg_agent in tg_agents:
ref_count = random.randint(22, int(22 * 1.25)) # от 22 до 27
for j in range(ref_count):
ref_val = str(uuid4())
desc_val = ALL_DESCRIPTIONS[(j % desc_count)]
dt = random.choice(date_list)
promocode = ''.join(random.choices(alphabet, k=8))
# Проверяем уникальность промокода среди уже созданных рефов
while any(r.promocode == promocode for r in refs):
promocode = ''.join(random.choices(alphabet, k=8))
ref = Ref(
tg_agent_id=tg_agent.id,
ref=ref_val,
description=desc_val,
promocode=promocode,
create_dttm=dt,
update_dttm=dt
)

View File

@ -1,22 +0,0 @@
from sqlmodel import SQLModel
from helpers_bff import AUTH_DB_ENGINE
from sqlalchemy.schema import CreateTable
import os
# --- Отладочный вывод ---
print("Таблицы в metadata:", SQLModel.metadata.sorted_tables)
print("Все зарегистрированные модели:", SQLModel.__subclasses__())
# --- Генерация и сохранение SQL-скрипта создания таблиц ---
def get_sql_create_script():
script = []
for table in SQLModel.metadata.sorted_tables:
create_table_sql = str(CreateTable(table).compile(dialect=AUTH_DB_ENGINE.dialect))
script.append(f"{create_table_sql};")
return "\n\n".join(script)
sql_script = get_sql_create_script()
sql_file_path = os.path.join(os.path.dirname(__file__), "sql_create.sql")
with open(sql_file_path, "w", encoding="utf-8") as f:
f.write(sql_script)
print("SQL-скрипт успешно сгенерирован!")

View File

@ -1,14 +1,12 @@
from fastapi import FastAPI, Depends, HTTPException, status, Header, Body, Request
from fastapi import FastAPI, Depends, HTTPException, status, Header, Body
from sqlmodel import Session, select, Field
from typing import Optional, List, Dict
from datetime import datetime, timedelta
import hashlib
import uuid
from random import choices
import string
from sql_models import Company, IntegrationToken, Ref, Sale, AgentTransaction, PartnerTransaction, AgentBalance, TgAgent, CompanyBalance
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus, WithdrawRequest, WithdrawResponse
from integration_models import Token, SaleCreateRequest, SaleCreateResponse, TransactionStatus
from bff_models import RegisterResponse, TgAuthResponse
from tg_models import RefAddRequest, RefResponse, RefAddResponse, RefStatResponse, RegisterRequest, StatResponse
from helpers_bff import AUTH_DB_ENGINE, get_integration_db, create_integration_jwt_token, get_current_company_from_jwt, get_tg_agent_by_tg_id, get_current_tg_agent
@ -55,36 +53,30 @@ async def get_token_for_api_key(
jwt_token = create_integration_jwt_token(integration_token_db.company_id)
return {"access_token": jwt_token, "token_type": "bearer"}
@app.get("/ref", response_model=List[RefResponse], tags=["agent-tg"])
@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_integration_db)):
"""
Возвращает список реферальных ссылок текущего Telegram-агента.
"""
refs = db.exec(select(Ref).where(Ref.tg_agent_id == current_tg_agent.id)).all()
return [RefResponse(ref=r.ref, description=r.description or "", promocode=r.promocode) for r in refs]
return [RefResponse(ref=r.ref, description=r.description or "") for r in refs]
@app.post("/ref/add", tags=["agent-tg"], response_model=RefAddResponse)
@app.post("/ref/add", tags=["partner-tg"], response_model=RefAddResponse)
def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Добавляет новую реферальную ссылку для текущего Telegram-агента.
"""
# Генерация промокода (логика как была для промокодов)
alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
promocode = ''.join(choices(alphabet, k=8))
while db.exec(select(Ref).where(Ref.promocode == promocode)).first():
promocode = ''.join(choices(alphabet, k=8))
new_ref = Ref(
tg_agent_id=current_tg_agent.id,
ref=str(uuid.uuid4()),
description=req.description,
promocode=promocode
description=req.description
)
db.add(new_ref)
db.commit()
db.refresh(new_ref)
return {"ref": new_ref.ref, "promocode": new_ref.promocode, "description": new_ref.description}
return {"ref": new_ref.ref}
@app.get("/ref/stat", tags=["agent-tg"], response_model=RefStatResponse)
@app.get("/ref/stat", tags=["partner-tg"], response_model=RefStatResponse)
def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает статистику по реферальным ссылкам текущего Telegram-агента.
@ -104,7 +96,7 @@ def get_ref_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db:
})
return {"refData": result}
@app.get("/stat", tags=["agent-tg"], response_model=StatResponse)
@app.get("/stat", tags=["partner-tg"], response_model=StatResponse)
def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Session = Depends(get_integration_db)):
"""
Возвращает общую статистику для текущего Telegram-агента.
@ -126,7 +118,7 @@ def get_stat(current_tg_agent: TgAgent = Depends(get_current_tg_agent), db: Sess
"availableWithdrawal": availableWithdrawal
}
@app.post("/tg_auth", tags=["agent-tg"], response_model=TgAuthResponse)
@app.post("/tg_auth", tags=["partner-tg"], response_model=TgAuthResponse)
def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integration_db)):
"""
Авторизует Telegram-агента по хешу.
@ -136,56 +128,6 @@ def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_integra
raise HTTPException(status_code=401, detail="Hash not found")
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
@app.post("/withdraw", tags=["agent-tg"], response_model=WithdrawResponse)
async def withdraw_funds(
req: WithdrawRequest,
db: Session = Depends(get_integration_db)
):
"""
Запрос на вывод средств для Telegram-агента.
"""
tg_agent = db.exec(select(TgAgent).where(TgAgent.tg_id == req.tg_id)).first()
if not tg_agent:
raise HTTPException(status_code=404, detail="Telegram-агент не найден")
company = db.exec(select(Company).where(Company.id == tg_agent.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена для агента")
if req.amount <= 0:
raise HTTPException(status_code=400, detail="Сумма для вывода должна быть положительной")
agent_balance = db.exec(select(AgentBalance).where(AgentBalance.tg_agent_id == tg_agent.id)).first()
if not agent_balance or agent_balance.available_balance < req.amount:
raise HTTPException(status_code=400, detail="Недостаточно средств на балансе для вывода")
# Определяем статус транзакции
transaction_status = TransactionStatus.WAITING
if company.auto_approve_transactions:
transaction_status = TransactionStatus.NEW
# Создаем запись AgentTransaction
new_agent_transaction = AgentTransaction(
tg_agent_id=tg_agent.id,
amount=req.amount,
status=transaction_status.value,
transaction_group=uuid.uuid4()
)
db.add(new_agent_transaction)
# Обновляем баланс агента
agent_balance.available_balance -= req.amount
if transaction_status == TransactionStatus.WAITING: # Если автоматически одобряется, переводим на замороженный баланс компании (т.е. компания должна выплатить)
agent_balance.frozen_balance += req.amount # Удерживаем средства, пока они не будут выведены
db.add(agent_balance)
db.commit()
db.refresh(new_agent_transaction)
db.refresh(agent_balance)
return {"msg": "Запрос на вывод средств успешно создан", "transaction_id": new_agent_transaction.transaction_group}
@app.post("/sale", tags=["integration"], response_model=SaleCreateResponse)
async def create_sale(
req: SaleCreateRequest,
@ -279,7 +221,7 @@ async def create_sale(
"crediting": new_sale.crediting
}
@app.post("/register", tags=["agent-tg"], response_model=RegisterResponse)
@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse)
def register(req: RegisterRequest, db: Session = Depends(get_integration_db)):
"""
Регистрирует нового Telegram-агента в системе.

View File

@ -25,14 +25,6 @@ class SaleCreateResponse(BaseModel):
sale_id: str
crediting: float
class WithdrawRequest(BaseModel):
tg_id: int
amount: float
class WithdrawResponse(BaseModel):
msg: str
transaction_id: uuid.UUID
class TransactionStatus(str, Enum):
NEW = "new"
PROCESS = "process"

View File

@ -1,4 +1,3 @@
import uuid
from fastapi import (
FastAPI,
Depends,
@ -63,7 +62,6 @@ from helpers_bff import (
ACCESS_TOKEN_EXPIRE_MINUTES,
pwd_context,
)
import os
# Создание движка базы данных
@ -73,8 +71,6 @@ SQLModel.metadata.create_all(AUTH_DB_ENGINE)
app = FastAPI()
@app.post("/token", response_model=Token, tags=["bff", "token"])
def login_account_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
@ -252,7 +248,6 @@ def get_referrals_stat(
"ref": ref.ref,
"agent": agent.name if agent and agent.name else f"Агент {ref.tg_agent_id}",
"description": ref.description or "",
"promocode": ref.promocode,
"salesSum": sales_sum,
"salesCount": sales_count
})

View File

@ -1,161 +0,0 @@
CREATE TABLE company (
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
commission FLOAT NOT NULL,
agent_commission FLOAT NOT NULL,
"key" VARCHAR NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
auto_approve_transactions BOOLEAN NOT NULL,
PRIMARY KEY (id)
)
;
CREATE TABLE account (
id INTEGER NOT NULL,
login VARCHAR NOT NULL,
password_hash VARCHAR NOT NULL,
"firstName" VARCHAR,
surname VARCHAR,
phone VARCHAR,
email VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE company_balances (
id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
available_balance FLOAT NOT NULL,
pending_balance FLOAT NOT NULL,
updated_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE (company_id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE integrationtoken (
id INTEGER NOT NULL,
description VARCHAR NOT NULL,
token_hash VARCHAR,
masked_token VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
use_dttm DATETIME,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE tgagent (
id INTEGER NOT NULL,
tg_id INTEGER NOT NULL,
chat_id INTEGER,
phone VARCHAR,
name VARCHAR,
login VARCHAR,
hash VARCHAR,
company_id INTEGER NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;
CREATE TABLE agent_balances (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
available_balance FLOAT NOT NULL,
frozen_balance FLOAT NOT NULL,
updated_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE (tg_agent_id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id)
)
;
CREATE TABLE agent_transactions (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
amount FLOAT NOT NULL,
status VARCHAR NOT NULL,
transaction_group CHAR(32) NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id),
UNIQUE (transaction_group)
)
;
CREATE TABLE ref (
id INTEGER NOT NULL,
tg_agent_id INTEGER NOT NULL,
ref VARCHAR NOT NULL,
description VARCHAR,
promocode VARCHAR(8) NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(tg_agent_id) REFERENCES tgagent (id)
)
;
CREATE TABLE partner_transactions (
id INTEGER NOT NULL,
company_id INTEGER NOT NULL,
type VARCHAR NOT NULL,
amount FLOAT NOT NULL,
status VARCHAR NOT NULL,
transaction_group CHAR(32) NOT NULL,
agent_transaction_id INTEGER,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(company_id) REFERENCES company (id),
FOREIGN KEY(agent_transaction_id) REFERENCES agent_transactions (id)
)
;
CREATE TABLE sale (
id INTEGER NOT NULL,
cost FLOAT NOT NULL,
crediting FLOAT NOT NULL,
ref INTEGER NOT NULL,
sale_id VARCHAR NOT NULL,
company_id INTEGER NOT NULL,
sale_dttm DATETIME NOT NULL,
create_dttm DATETIME NOT NULL,
update_dttm DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY(ref) REFERENCES ref (id),
FOREIGN KEY(company_id) REFERENCES company (id)
)
;

View File

@ -15,11 +15,6 @@ class Company(SQLModel, table=True):
auto_approve_transactions: bool = Field(default=False) # Отвечает за автоматическое одобрение агентских транзакций на вывод.
integration_tokens: List["IntegrationToken"] = Relationship(back_populates="company")
tg_agents: List["TgAgent"] = Relationship(back_populates="company")
sales: List["Sale"] = Relationship(back_populates="company")
partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="company")
company_balance: Optional["CompanyBalance"] = Relationship(back_populates="company")
accounts: List["Account"] = Relationship(back_populates="company")
class TgAgent(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
@ -33,23 +28,14 @@ class TgAgent(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="tg_agents")
refs: List["Ref"] = Relationship(back_populates="tg_agent")
agent_transactions: List["AgentTransaction"] = Relationship(back_populates="tg_agent")
agent_balance: Optional["AgentBalance"] = Relationship(back_populates="tg_agent")
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
promocode: str = Field(index=True, unique=True, max_length=8)
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="refs")
sales: List["Sale"] = Relationship(back_populates="ref_obj")
class Sale(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
cost: float
@ -61,9 +47,6 @@ class Sale(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
ref_obj: "Ref" = Relationship(back_populates="sales")
company: "Company" = Relationship(back_populates="sales")
class AgentTransaction(SQLModel, table=True):
__tablename__ = "agent_transactions"
id: Optional[int] = Field(default=None, primary_key=True)
@ -74,9 +57,6 @@ class AgentTransaction(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="agent_transactions")
partner_transactions: List["PartnerTransaction"] = Relationship(back_populates="agent_transaction")
class PartnerTransaction(SQLModel, table=True):
__tablename__ = "partner_transactions"
id: Optional[int] = Field(default=None, primary_key=True)
@ -89,9 +69,6 @@ class PartnerTransaction(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="partner_transactions")
agent_transaction: Optional["AgentTransaction"] = Relationship(back_populates="partner_transactions")
class CompanyBalance(SQLModel, table=True):
__tablename__ = "company_balances"
id: Optional[int] = Field(default=None, primary_key=True)
@ -100,8 +77,6 @@ class CompanyBalance(SQLModel, table=True):
pending_balance: float = Field(default=0.0)
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="company_balance")
class AgentBalance(SQLModel, table=True):
__tablename__ = "agent_balances"
id: Optional[int] = Field(default=None, primary_key=True)
@ -110,8 +85,6 @@ class AgentBalance(SQLModel, table=True):
frozen_balance: float = Field(default=0.0)
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
tg_agent: "TgAgent" = Relationship(back_populates="agent_balance")
class Account(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
login: str = Field(index=True, unique=True)
@ -124,8 +97,6 @@ class Account(SQLModel, table=True):
create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow)
company: "Company" = Relationship(back_populates="accounts")
# Новая модель для интеграционных токенов
class IntegrationToken(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
@ -137,4 +108,4 @@ class IntegrationToken(SQLModel, table=True):
update_dttm: datetime = Field(default_factory=datetime.utcnow, nullable=False)
use_dttm: Optional[datetime] = None
company: "Company" = Relationship(back_populates="integration_tokens")
company: Company = Relationship(back_populates="integration_tokens")

View File

@ -7,7 +7,6 @@ from uuid import UUID
class RefResponse(BaseModel):
ref: str
description: str
promocode: str
class RefAddRequest(BaseModel):
description: str
@ -26,8 +25,6 @@ class RegisterRequest(BaseModel):
# New Response Models for TG APIs
class RefAddResponse(BaseModel):
ref: str
promocode: str
description: str
class RefStatItem(BaseModel):
description: str