Рефакторинг структуры проекта: перемещение функций и логики работы с базой данных в новый файл helpers_bff.py. Обновлены импорты в fill_db.py и main.py для использования новых функций. Удалены устаревшие функции и классы из main.py, улучшена организация кода.
This commit is contained in:
parent
155d1002fc
commit
6e804953c0
@ -1,11 +1,11 @@
|
|||||||
import random
|
import random
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
from main import AUTH_DB_ENGINE, TgAgent, Ref, Sale, Account, Company, AgentTransaction, PartnerTransaction, CompanyBalance, AgentBalance
|
from sql_models import TgAgent, Ref, Sale, Account, Company, AgentTransaction, PartnerTransaction, CompanyBalance, AgentBalance
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from passlib.context import CryptContext
|
from helpers_bff import AUTH_DB_ENGINE, get_password_hash
|
||||||
|
|
||||||
|
|
||||||
# Константа: список user_ids
|
# Константа: список user_ids
|
||||||
@ -63,11 +63,6 @@ LOGINS = [
|
|||||||
ALL_DESCRIPTIONS = DESCRIPTIONS
|
ALL_DESCRIPTIONS = DESCRIPTIONS
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
||||||
|
|
||||||
def get_password_hash(password):
|
|
||||||
return pwd_context.hash(password)
|
|
||||||
|
|
||||||
def get_date_list(days=7):
|
def get_date_list(days=7):
|
||||||
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
return [today - timedelta(days=i) for i in range(days, -1, -1)]
|
return [today - timedelta(days=i) for i in range(days, -1, -1)]
|
||||||
|
|||||||
90
helpers_bff.py
Normal file
90
helpers_bff.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
from sqlmodel import Session, select, create_engine
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from bff_models import Token, TransactionStatus
|
||||||
|
from sql_models import Company, TgAgent, Account, AgentBalance, AgentTransaction, PartnerTransaction, Sale, Ref
|
||||||
|
from hashlib import sha256
|
||||||
|
import jwt
|
||||||
|
from jwt.exceptions import InvalidTokenError
|
||||||
|
from fastapi import HTTPException, status, Depends, Request
|
||||||
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
|
|
||||||
|
# Конфигурация
|
||||||
|
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
|
||||||
|
AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True)
|
||||||
|
|
||||||
|
SECRET_KEY = "supersecretkey"
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
with Session(AUTH_DB_ENGINE) as session:
|
||||||
|
yield session
|
||||||
|
|
||||||
|
def get_current_account(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"},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
login: str = payload.get("sub")
|
||||||
|
if login is None:
|
||||||
|
raise credentials_exception
|
||||||
|
except InvalidTokenError:
|
||||||
|
raise credentials_exception
|
||||||
|
account = get_account_by_login(db, login)
|
||||||
|
if account is None:
|
||||||
|
raise credentials_exception
|
||||||
|
return account
|
||||||
|
|
||||||
|
async def get_current_tg_agent(request: Request, db: Session = Depends(get_db)):
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
auth_header = request.headers.get("Authorization")
|
||||||
|
if not auth_header or not auth_header.startswith("Bearer "):
|
||||||
|
raise credentials_exception
|
||||||
|
hash_value = auth_header.replace("Bearer ", "").strip()
|
||||||
|
tg_agent = db.exec(select(TgAgent).where(TgAgent.hash == hash_value)).first()
|
||||||
|
if tg_agent is None:
|
||||||
|
raise credentials_exception
|
||||||
|
return tg_agent
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def create_access_token(data: dict, expires_delta: timedelta = None):
|
||||||
|
to_encode = data.copy()
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.utcnow() + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
def verify_password(plain_password, hashed_password):
|
||||||
|
return pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
def get_password_hash(password):
|
||||||
|
return pwd_context.hash(password)
|
||||||
|
|
||||||
|
def get_account_by_login(db: Session, login: str) -> Optional[Account]:
|
||||||
|
statement = select(Account).where(Account.login == login)
|
||||||
|
return db.exec(statement).first()
|
||||||
324
main.py
324
main.py
@ -1,204 +1,76 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from fastapi import FastAPI, Depends, HTTPException, status, Query, Body, Request
|
from fastapi import (
|
||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
FastAPI,
|
||||||
from sqlmodel import SQLModel, Field, create_engine, Session, select
|
Depends,
|
||||||
from passlib.context import CryptContext
|
HTTPException,
|
||||||
|
status,
|
||||||
|
Query,
|
||||||
|
Body,
|
||||||
|
)
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
from sqlmodel import SQLModel, Session, select
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta, datetime
|
||||||
from bff_models import Token, AccountProfileUpdateRequest, AccountPasswordChangeRequest, AgentTransactionResponse, AutoApproveSettingsRequest, ApproveTransactionsRequest, TransactionStatus, RegisterResponse, DashboardCardsResponse, DashboardChartTotalResponse, DashboardChartAgentResponse, StatAgentsResponse, StatReferralsResponse, StatSalesResponse, BillingCardsResponse, BillingChartStatResponse, BillingChartPieResponse, AccountResponse, CompanyProfileResponse, AccountProfileResponse, AccountProfileUpdateResponse, AccountPasswordChangeResponse, AutoApproveSettingsGetResponse, AutoApproveSettingsUpdateResponse, ApproveTransactionsResult, TgAuthResponse, BillingPayoutsTransactionsResponse
|
from bff_models import (
|
||||||
from tg_models import RefAddRequest, RefResponse, RegisterRequest, TokenRequest, RefAddResponse, RefStatResponse, StatResponse
|
Token,
|
||||||
from uuid import uuid4
|
RegisterResponse,
|
||||||
from fastapi.responses import JSONResponse
|
DashboardCardsResponse,
|
||||||
|
DashboardChartTotalResponse,
|
||||||
|
DashboardChartAgentResponse,
|
||||||
|
StatAgentsResponse,
|
||||||
|
StatReferralsResponse,
|
||||||
|
StatSalesResponse,
|
||||||
|
BillingCardsResponse,
|
||||||
|
BillingChartStatResponse,
|
||||||
|
BillingChartPieResponse,
|
||||||
|
AccountResponse,
|
||||||
|
AccountProfileResponse,
|
||||||
|
AccountProfileUpdateResponse,
|
||||||
|
AccountPasswordChangeResponse,
|
||||||
|
AutoApproveSettingsGetResponse,
|
||||||
|
AutoApproveSettingsUpdateResponse,
|
||||||
|
ApproveTransactionsResult,
|
||||||
|
TgAuthResponse,
|
||||||
|
BillingPayoutsTransactionsResponse,
|
||||||
|
AccountProfileUpdateRequest,
|
||||||
|
AccountPasswordChangeRequest,
|
||||||
|
AutoApproveSettingsRequest,
|
||||||
|
ApproveTransactionsRequest,
|
||||||
|
AgentTransactionResponse,
|
||||||
|
TransactionStatus
|
||||||
|
)
|
||||||
|
from tg_models import RefAddRequest, RefResponse, RegisterRequest, RefAddResponse, RefStatResponse, StatResponse
|
||||||
|
from sql_models import (
|
||||||
|
Company,
|
||||||
|
TgAgent,
|
||||||
|
Ref,
|
||||||
|
Sale,
|
||||||
|
AgentTransaction,
|
||||||
|
PartnerTransaction,
|
||||||
|
AgentBalance,
|
||||||
|
Account
|
||||||
|
)
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from hashlib import sha256
|
import hashlib
|
||||||
import jwt
|
from helpers_bff import (
|
||||||
from jwt.exceptions import InvalidTokenError
|
AUTH_DB_ENGINE,
|
||||||
from pydantic import BaseModel, EmailStr
|
get_db,
|
||||||
from enum import Enum
|
get_tg_agent_by_tg_id,
|
||||||
|
get_current_account,
|
||||||
# Конфигурация
|
get_current_tg_agent,
|
||||||
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
|
create_access_token,
|
||||||
|
verify_password,
|
||||||
#SQLModel
|
get_account_by_login,
|
||||||
class Company(SQLModel, table=True):
|
ACCESS_TOKEN_EXPIRE_MINUTES,
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
pwd_context,
|
||||||
name: str
|
)
|
||||||
commission: float # процент комиссии
|
|
||||||
key: str = Field(index=True, unique=True)
|
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
auto_approve_transactions: bool = Field(default=False) # Новое поле для автоподтверждения
|
|
||||||
|
|
||||||
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
|
|
||||||
hash: Optional[str] = None
|
|
||||||
company_id: int = Field(foreign_key="company.id")
|
|
||||||
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
|
|
||||||
company_id: int = Field(foreign_key="company.id")
|
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class AgentTransaction(SQLModel, table=True):
|
|
||||||
__tablename__ = "agent_transactions" # Указываем имя таблицы явно
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
tg_agent_id: int = Field(foreign_key="tgagent.id") # ID агента, связь с TgAgent
|
|
||||||
amount: float # Используем float для DECIMAL(15,2)
|
|
||||||
status: str # 'waiting', 'process', 'done', 'reject', 'error'
|
|
||||||
transaction_group: uuid.UUID = Field(default_factory=uuid.uuid4) # UUID для группировки
|
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class PartnerTransaction(SQLModel, table=True):
|
|
||||||
__tablename__ = "partner_transactions" # Указываем имя таблицы явно
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
company_id: int = Field(foreign_key="company.id") # ID партнера, связь с Company
|
|
||||||
type: str # 'deposit', 'agent_payout', 'service_fee'
|
|
||||||
amount: float # Используем float для DECIMAL(15,2)
|
|
||||||
status: str # 'process', 'done', 'error'
|
|
||||||
transaction_group: uuid.UUID # UUID для группировки, может быть связан с agent_transactions
|
|
||||||
agent_transaction_id: Optional[int] = Field(default=None, foreign_key="agent_transactions.id") # Связь с агентской транзакцией
|
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class CompanyBalance(SQLModel, table=True):
|
|
||||||
__tablename__ = "company_balances" # Указываем имя таблицы явно
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
company_id: int = Field(foreign_key="company.id", unique=True) # ID компании, уникальный баланс на компанию
|
|
||||||
available_balance: float = Field(default=0.0) # Используем float для DECIMAL(15,2)
|
|
||||||
pending_balance: float = Field(default=0.0) # Используем float для DECIMAL(15,2)
|
|
||||||
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class AgentBalance(SQLModel, table=True):
|
|
||||||
__tablename__ = "agent_balances" # Указываем имя таблицы явно
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
tg_agent_id: int = Field(foreign_key="tgagent.id", unique=True) # ID агента, уникальный баланс на агента
|
|
||||||
available_balance: float = Field(default=0.0) # Используем float для DECIMAL(15,2)
|
|
||||||
frozen_balance: float = Field(default=0.0) # Используем float для DECIMAL(15,2)
|
|
||||||
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class Account(SQLModel, table=True):
|
|
||||||
id: Optional[int] = Field(default=None, primary_key=True)
|
|
||||||
login: str = Field(index=True, unique=True)
|
|
||||||
password_hash: str
|
|
||||||
firstName: Optional[str] = None
|
|
||||||
surname: Optional[str] = None
|
|
||||||
phone: Optional[str] = None
|
|
||||||
email: Optional[str] = None
|
|
||||||
company_id: int = Field(foreign_key="company.id")
|
|
||||||
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
|
||||||
|
|
||||||
class AccountProfileUpdateRequest(BaseModel):
|
|
||||||
firstName: str
|
|
||||||
surname: str
|
|
||||||
email: EmailStr
|
|
||||||
phone: str
|
|
||||||
|
|
||||||
class AccountPasswordChangeRequest(BaseModel):
|
|
||||||
currentPassword: str
|
|
||||||
newPassword: str
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionStatus(str, Enum): # Определяем Enum для статусов
|
|
||||||
WAITING = 'waiting'
|
|
||||||
PROCESS = 'process'
|
|
||||||
DONE = 'done'
|
|
||||||
REJECT = 'reject'
|
|
||||||
ERROR = 'error'
|
|
||||||
NEW = 'new' # Новый статус
|
|
||||||
|
|
||||||
|
|
||||||
# Новая модель ответа для агентских транзакций с именем агента
|
|
||||||
class AgentTransactionResponse(BaseModel):
|
|
||||||
amount: float
|
|
||||||
status: TransactionStatus # Используем Enum
|
|
||||||
transaction_group: uuid.UUID
|
|
||||||
create_dttm: datetime
|
|
||||||
update_dttm: datetime
|
|
||||||
agent_name: Optional[str] = None # Поле для имени агента
|
|
||||||
|
|
||||||
|
|
||||||
# Создание движка базы данных
|
# Создание движка базы данных
|
||||||
AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True)
|
|
||||||
SQLModel.metadata.create_all(AUTH_DB_ENGINE)
|
SQLModel.metadata.create_all(AUTH_DB_ENGINE)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
|
|
||||||
|
|
||||||
# FastAPI app
|
# FastAPI app
|
||||||
app = FastAPI()
|
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
|
|
||||||
|
|
||||||
def get_current_account(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"},
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
||||||
login: str = payload.get("sub")
|
|
||||||
if login is None:
|
|
||||||
raise credentials_exception
|
|
||||||
except InvalidTokenError:
|
|
||||||
raise credentials_exception
|
|
||||||
account = get_account_by_login(db, login)
|
|
||||||
if account is None:
|
|
||||||
raise credentials_exception
|
|
||||||
return account
|
|
||||||
|
|
||||||
# Авторизация
|
|
||||||
async def get_current_tg_agent(request: Request, db: Session = Depends(get_db)):
|
|
||||||
credentials_exception = HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Could not validate credentials",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
auth_header = request.headers.get("Authorization")
|
|
||||||
if not auth_header or not auth_header.startswith("Bearer "):
|
|
||||||
raise credentials_exception
|
|
||||||
hash_value = auth_header.replace("Bearer ", "").strip()
|
|
||||||
tg_agent = db.exec(select(TgAgent).where(TgAgent.hash == hash_value)).first()
|
|
||||||
if tg_agent is None:
|
|
||||||
raise credentials_exception
|
|
||||||
return tg_agent
|
|
||||||
|
|
||||||
# Регистрация
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse)
|
@app.post("/register", tags=["partner-tg"], response_model=RegisterResponse)
|
||||||
def register(req: RegisterRequest, db: Session = Depends(get_db)):
|
def register(req: RegisterRequest, db: Session = Depends(get_db)):
|
||||||
@ -219,7 +91,7 @@ def register(req: RegisterRequest, db: Session = Depends(get_db)):
|
|||||||
company = db.exec(select(Company).where(Company.key == company_key)).first()
|
company = db.exec(select(Company).where(Company.key == company_key)).first()
|
||||||
if not company:
|
if not company:
|
||||||
raise HTTPException(status_code=400, detail="Компания с таким ключом не найдена")
|
raise HTTPException(status_code=400, detail="Компания с таким ключом не найдена")
|
||||||
hash_value = sha256(f"{tg_id}sold".encode()).hexdigest()
|
hash_value = hashlib.sha256(f"{tg_id}sold".encode()).hexdigest()
|
||||||
new_tg_agent = TgAgent(
|
new_tg_agent = TgAgent(
|
||||||
tg_id=tg_id,
|
tg_id=tg_id,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
@ -234,30 +106,6 @@ def register(req: RegisterRequest, db: Session = Depends(get_db)):
|
|||||||
db.refresh(new_tg_agent)
|
db.refresh(new_tg_agent)
|
||||||
return {"msg": "TgAgent registered successfully"}
|
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Авторизация
|
|
||||||
|
|
||||||
SECRET_KEY = "supersecretkey" # Лучше вынести в .env
|
|
||||||
ALGORITHM = "HS256"
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60
|
|
||||||
|
|
||||||
def create_access_token(data: dict, expires_delta: timedelta = None):
|
|
||||||
to_encode = data.copy()
|
|
||||||
if expires_delta:
|
|
||||||
expire = datetime.utcnow() + expires_delta
|
|
||||||
else:
|
|
||||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
|
||||||
to_encode.update({"exp": expire})
|
|
||||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
||||||
return encoded_jwt
|
|
||||||
|
|
||||||
@app.post("/token", response_model=Token, tags=["bff", "token"])
|
@app.post("/token", response_model=Token, tags=["bff", "token"])
|
||||||
def login_account_for_access_token(
|
def login_account_for_access_token(
|
||||||
@ -299,7 +147,7 @@ def add_ref(req: RefAddRequest, current_tg_agent: TgAgent = Depends(get_current_
|
|||||||
"""
|
"""
|
||||||
new_ref = Ref(
|
new_ref = Ref(
|
||||||
tg_agent_id=current_tg_agent.id,
|
tg_agent_id=current_tg_agent.id,
|
||||||
ref=str(uuid4()),
|
ref=str(uuid.uuid4()),
|
||||||
description=req.description
|
description=req.description
|
||||||
)
|
)
|
||||||
db.add(new_ref)
|
db.add(new_ref)
|
||||||
@ -662,15 +510,6 @@ def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_db)):
|
|||||||
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
|
return {"msg": "Auth success", "tg_id": tg_agent.tg_id}
|
||||||
|
|
||||||
# --- Новый функционал для Account ---
|
# --- Новый функционал для Account ---
|
||||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|
||||||
|
|
||||||
def verify_password(plain_password, hashed_password):
|
|
||||||
return pwd_context.verify(plain_password, hashed_password)
|
|
||||||
|
|
||||||
def get_account_by_login(db: Session, login: str) -> Optional[Account]:
|
|
||||||
statement = select(Account).where(Account.login == login)
|
|
||||||
return db.exec(statement).first()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/account", tags=["bff", "account"], response_model=AccountResponse)
|
@app.get("/account", tags=["bff", "account"], response_model=AccountResponse)
|
||||||
@ -754,9 +593,9 @@ def change_account_password(
|
|||||||
|
|
||||||
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=["bff", "account"])
|
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=["bff", "account"])
|
||||||
def get_account_agent_transactions(
|
def get_account_agent_transactions(
|
||||||
statuses: Optional[List[TransactionStatus]] = Query(None), # Изменено на List[TransactionStatus]
|
statuses: Optional[List[TransactionStatus]] = Query(None),
|
||||||
date_start: str = Query(None), # Добавлен параметр date_start
|
date_start: str = Query(None),
|
||||||
date_end: str = Query(None), # Добавлен параметр date_end
|
date_end: str = Query(None),
|
||||||
current_account: Account = Depends(get_current_account),
|
current_account: Account = Depends(get_current_account),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
@ -788,23 +627,26 @@ def get_account_agent_transactions(
|
|||||||
# Формируем список ответов в формате AgentTransactionResponse
|
# Формируем список ответов в формате AgentTransactionResponse
|
||||||
agent_transactions_response = []
|
agent_transactions_response = []
|
||||||
for agent_trans, agent in results:
|
for agent_trans, agent in results:
|
||||||
|
try:
|
||||||
|
status_enum = TransactionStatus(agent_trans.status)
|
||||||
|
except ValueError:
|
||||||
|
# Если статус из БД не соответствует Enum, используем статус ERROR
|
||||||
|
status_enum = TransactionStatus.ERROR
|
||||||
agent_transactions_response.append(
|
agent_transactions_response.append(
|
||||||
AgentTransactionResponse(
|
AgentTransactionResponse(
|
||||||
amount=agent_trans.amount,
|
amount=agent_trans.amount,
|
||||||
status=agent_trans.status,
|
status=status_enum,
|
||||||
transaction_group=agent_trans.transaction_group,
|
transaction_group=agent_trans.transaction_group,
|
||||||
create_dttm=agent_trans.create_dttm,
|
create_dttm=agent_trans.create_dttm,
|
||||||
update_dttm=agent_trans.update_dttm,
|
update_dttm=agent_trans.update_dttm,
|
||||||
agent_name=agent.name # Используем имя агента
|
agent_name=agent.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return agent_transactions_response
|
return agent_transactions_response
|
||||||
|
|
||||||
# Модель запроса для POST /account/auto-approve
|
|
||||||
class AutoApproveSettingsRequest(BaseModel):
|
|
||||||
auto_approve: bool
|
|
||||||
apply_to_current: Optional[bool] = False
|
|
||||||
|
|
||||||
@app.get("/account/auto-approve", tags=["bff", "account"], response_model=AutoApproveSettingsGetResponse)
|
@app.get("/account/auto-approve", tags=["bff", "account"], response_model=AutoApproveSettingsGetResponse)
|
||||||
def get_auto_approve_settings(
|
def get_auto_approve_settings(
|
||||||
@ -854,8 +696,8 @@ def update_auto_approve_settings(
|
|||||||
# Находим соответствующие партнерские транзакции и обновляем их статус
|
# Находим соответствующие партнерские транзакции и обновляем их статус
|
||||||
partner_transactions_to_update = db.exec(
|
partner_transactions_to_update = db.exec(
|
||||||
select(PartnerTransaction)
|
select(PartnerTransaction)
|
||||||
.where(PartnerTransaction.agent_transaction_id == agent_trans.id) # Используем связь по ID
|
.where(PartnerTransaction.agent_transaction_id == agent_trans.id)
|
||||||
.where(PartnerTransaction.status == TransactionStatus.PROCESS) # Предполагаем, что связанные партнерские транзакции в статусе PROCESS
|
.where(PartnerTransaction.status == TransactionStatus.PROCESS)
|
||||||
).all()
|
).all()
|
||||||
for partner_trans in partner_transactions_to_update:
|
for partner_trans in partner_transactions_to_update:
|
||||||
partner_trans.status = TransactionStatus.NEW
|
partner_trans.status = TransactionStatus.NEW
|
||||||
@ -867,9 +709,7 @@ def update_auto_approve_settings(
|
|||||||
|
|
||||||
return {"msg": "Настройка автоматического подтверждения обновлена", "auto_approve_transactions": company.auto_approve_transactions}
|
return {"msg": "Настройка автоматического подтверждения обновлена", "auto_approve_transactions": company.auto_approve_transactions}
|
||||||
|
|
||||||
# Модель запроса для POST /account/approve-transactions
|
|
||||||
class ApproveTransactionsRequest(BaseModel):
|
|
||||||
transaction_ids: List[uuid.UUID]
|
|
||||||
|
|
||||||
@app.post("/account/approve-transactions", tags=["bff", "account"], response_model=ApproveTransactionsResult)
|
@app.post("/account/approve-transactions", tags=["bff", "account"], response_model=ApproveTransactionsResult)
|
||||||
def approve_agent_transactions(
|
def approve_agent_transactions(
|
||||||
@ -893,11 +733,11 @@ def approve_agent_transactions(
|
|||||||
.join(TgAgent)
|
.join(TgAgent)
|
||||||
.where(TgAgent.company_id == company_id)
|
.where(TgAgent.company_id == company_id)
|
||||||
.where(AgentTransaction.transaction_group.in_(req.transaction_ids))
|
.where(AgentTransaction.transaction_group.in_(req.transaction_ids))
|
||||||
.where(AgentTransaction.status == TransactionStatus.WAITING) # Утверждаем только транзакции в статусе 'waiting'
|
.where(AgentTransaction.status == TransactionStatus.WAITING)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
for agent_trans in transactions_to_approve:
|
for agent_trans in transactions_to_approve:
|
||||||
agent_trans.status = TransactionStatus.NEW # Переводим в статус 'new'
|
agent_trans.status = TransactionStatus.NEW
|
||||||
agent_trans.update_dttm = datetime.utcnow()
|
agent_trans.update_dttm = datetime.utcnow()
|
||||||
db.add(agent_trans)
|
db.add(agent_trans)
|
||||||
approved_count += 1
|
approved_count += 1
|
||||||
|
|||||||
93
sql_models.py
Normal file
93
sql_models.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
import uuid
|
||||||
|
from sqlmodel import SQLModel, Field
|
||||||
|
|
||||||
|
class Company(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
name: str
|
||||||
|
commission: float # процент комиссии
|
||||||
|
key: str = Field(index=True, unique=True)
|
||||||
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
auto_approve_transactions: bool = Field(default=False)
|
||||||
|
|
||||||
|
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
|
||||||
|
hash: Optional[str] = None
|
||||||
|
company_id: int = Field(foreign_key="company.id")
|
||||||
|
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
|
||||||
|
company_id: int = Field(foreign_key="company.id")
|
||||||
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class AgentTransaction(SQLModel, table=True):
|
||||||
|
__tablename__ = "agent_transactions"
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
tg_agent_id: int = Field(foreign_key="tgagent.id")
|
||||||
|
amount: float
|
||||||
|
status: str
|
||||||
|
transaction_group: uuid.UUID = Field(default_factory=uuid.uuid4)
|
||||||
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class PartnerTransaction(SQLModel, table=True):
|
||||||
|
__tablename__ = "partner_transactions"
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
company_id: int = Field(foreign_key="company.id")
|
||||||
|
type: str
|
||||||
|
amount: float
|
||||||
|
status: str
|
||||||
|
transaction_group: uuid.UUID
|
||||||
|
agent_transaction_id: Optional[int] = Field(default=None, foreign_key="agent_transactions.id")
|
||||||
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class CompanyBalance(SQLModel, table=True):
|
||||||
|
__tablename__ = "company_balances"
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
company_id: int = Field(foreign_key="company.id", unique=True)
|
||||||
|
available_balance: float = Field(default=0.0)
|
||||||
|
pending_balance: float = Field(default=0.0)
|
||||||
|
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class AgentBalance(SQLModel, table=True):
|
||||||
|
__tablename__ = "agent_balances"
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
tg_agent_id: int = Field(foreign_key="tgagent.id", unique=True)
|
||||||
|
available_balance: float = Field(default=0.0)
|
||||||
|
frozen_balance: float = Field(default=0.0)
|
||||||
|
updated_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
class Account(SQLModel, table=True):
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
login: str = Field(index=True, unique=True)
|
||||||
|
password_hash: str
|
||||||
|
firstName: Optional[str] = None
|
||||||
|
surname: Optional[str] = None
|
||||||
|
phone: Optional[str] = None
|
||||||
|
email: Optional[str] = None
|
||||||
|
company_id: int = Field(foreign_key="company.id")
|
||||||
|
create_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
update_dttm: datetime = Field(default_factory=datetime.utcnow)
|
||||||
Loading…
x
Reference in New Issue
Block a user