Compare commits

...

3 Commits

3 changed files with 158 additions and 29 deletions

View File

@ -1,7 +1,7 @@
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, Transaction, Account from main import AUTH_DB_ENGINE, TgAgent, Ref, Sale, Transaction, Account, Company
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
@ -80,23 +80,39 @@ def fill_db():
session.execute(text("DELETE FROM ref")) session.execute(text("DELETE FROM ref"))
session.execute(text("DELETE FROM tgagent")) session.execute(text("DELETE FROM tgagent"))
session.execute(text("DELETE FROM account")) session.execute(text("DELETE FROM account"))
session.execute(text('DELETE FROM "transaction"'))
session.execute(text("DELETE FROM company"))
session.commit() session.commit()
# 0. Accounts # 0. Company
company = Company(
name="RE: Premium",
commission=10.0,
key="re-premium-key",
)
session.add(company)
session.commit()
session.refresh(company)
# 1. Accounts
accounts = [] accounts = []
for i in range(4): for i in range(4):
name_parts = NAMES[i % len(NAMES)].split()
first_name = name_parts[0]
surname = name_parts[1] if len(name_parts) > 1 else 'Тестов'
acc = Account( acc = Account(
login=f"user{i+1}", login=f"user{i+1}",
password_hash=get_password_hash("password123"), # теперь храним хеш password_hash=get_password_hash("password123"),
name=NAMES[i % len(NAMES)], firstName=first_name,
surname=surname,
phone=PHONES[i % len(PHONES)],
email=f"user{i+1}@example.com", email=f"user{i+1}@example.com",
balance=round(random.uniform(1000, 10000), 2) company_id=company.id
) )
session.add(acc) session.add(acc)
accounts.append(acc) accounts.append(acc)
session.commit() session.commit()
for acc in accounts: for acc in accounts:
session.refresh(acc) session.refresh(acc)
# 1. TgAgents # 2. TgAgents
tg_agents = [] tg_agents = []
for i, tg_agent_id in enumerate(USER_IDS): for i, tg_agent_id in enumerate(USER_IDS):
dt = random.choice(date_list) dt = random.choice(date_list)
@ -107,6 +123,7 @@ def fill_db():
phone=PHONES[i % len(PHONES)], phone=PHONES[i % len(PHONES)],
name=NAMES[i % len(NAMES)], name=NAMES[i % len(NAMES)],
login=LOGINS[i % len(LOGINS)], login=LOGINS[i % len(LOGINS)],
company_id=company.id,
create_dttm=dt, create_dttm=dt,
update_dttm=dt, update_dttm=dt,
hash=hash_value hash=hash_value
@ -116,8 +133,7 @@ def fill_db():
session.commit() session.commit()
for tg_agent in tg_agents: for tg_agent in tg_agents:
session.refresh(tg_agent) session.refresh(tg_agent)
# 3. Refs (минимум 22 на агента)
# 2. Refs (минимум 22 на агента)
refs = [] refs = []
desc_count = len(ALL_DESCRIPTIONS) desc_count = len(ALL_DESCRIPTIONS)
for tg_agent in tg_agents: for tg_agent in tg_agents:
@ -138,8 +154,7 @@ def fill_db():
session.commit() session.commit()
for ref in refs: for ref in refs:
session.refresh(ref) session.refresh(ref)
# 4. Sales (минимум 20 на каждый ref)
# 3. Sales (минимум 20 на каждый ref)
for ref in refs: for ref in refs:
sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25 sale_count = random.randint(20, int(20 * 1.25)) # от 20 до 25
for _ in range(sale_count): for _ in range(sale_count):
@ -151,13 +166,13 @@ def fill_db():
crediting=crediting, crediting=crediting,
ref=ref.id, ref=ref.id,
sale_id=str(uuid4()), sale_id=str(uuid4()),
company_id=company.id,
create_dttm=dt, create_dttm=dt,
update_dttm=dt update_dttm=dt
) )
session.add(sale) session.add(sale)
session.commit() session.commit()
# 5. Transactions (только withdrawal на агента)
# 4. Transactions (только withdrawal на агента)
TRANSACTION_STATUSES = ['process', 'done', 'error', 'waiting'] TRANSACTION_STATUSES = ['process', 'done', 'error', 'waiting']
for tg_agent in tg_agents: for tg_agent in tg_agents:
withdrawal_count = random.randint(5, int(5 * 1.25)) # от 5 до 6 withdrawal_count = random.randint(5, int(5 * 1.25)) # от 5 до 6
@ -175,6 +190,7 @@ def fill_db():
sum=round(random.uniform(200, 3000), 2), sum=round(random.uniform(200, 3000), 2),
tg_agent_id=tg_agent.id, tg_agent_id=tg_agent.id,
status=status, status=status,
company_id=company.id,
create_dttm=dt, create_dttm=dt,
update_dttm=dt update_dttm=dt
) )

144
main.py
View File

@ -10,11 +10,21 @@ from fastapi.responses import JSONResponse
from sqlalchemy import func from sqlalchemy import func
from hashlib import sha256 from hashlib import sha256
import jwt import jwt
from jwt.exceptions import InvalidTokenError
from pydantic import BaseModel, EmailStr
# Конфигурация # Конфигурация
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db" AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
#SQLModel #SQLModel
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)
class TgAgent(SQLModel, table=True): class TgAgent(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
tg_id: int = Field(index=True, unique=True) tg_id: int = Field(index=True, unique=True)
@ -23,6 +33,7 @@ class TgAgent(SQLModel, table=True):
name: Optional[str] = None name: Optional[str] = None
login: Optional[str] = None login: Optional[str] = None
hash: Optional[str] = None hash: Optional[str] = None
company_id: int = Field(foreign_key="company.id")
create_dttm: datetime = Field(default_factory=datetime.utcnow) create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow)
@ -40,6 +51,7 @@ class Sale(SQLModel, table=True):
crediting: float # сколько начислено за продажу crediting: float # сколько начислено за продажу
ref: int = Field(foreign_key="ref.id") ref: int = Field(foreign_key="ref.id")
sale_id: str sale_id: str
company_id: int = Field(foreign_key="company.id")
create_dttm: datetime = Field(default_factory=datetime.utcnow) create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow)
@ -49,6 +61,7 @@ class Transaction(SQLModel, table=True):
sum: float sum: float
tg_agent_id: int = Field(foreign_key="tgagent.id") tg_agent_id: int = Field(foreign_key="tgagent.id")
status: str # 'process' || 'done' || 'error' || 'waiting' status: str # 'process' || 'done' || 'error' || 'waiting'
company_id: int = Field(foreign_key="company.id")
create_dttm: datetime = Field(default_factory=datetime.utcnow) create_dttm: datetime = Field(default_factory=datetime.utcnow)
update_dttm: datetime = Field(default_factory=datetime.utcnow) update_dttm: datetime = Field(default_factory=datetime.utcnow)
@ -56,9 +69,23 @@ class Account(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
login: str = Field(index=True, unique=True) login: str = Field(index=True, unique=True)
password_hash: str # теперь хранится hash пароля password_hash: str # теперь хранится hash пароля
name: Optional[str] = None firstName: Optional[str] = None
surname: Optional[str] = None
phone: Optional[str] = None
email: Optional[str] = None email: Optional[str] = None
balance: float = 0.0 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
# Создание движка базы данных # Создание движка базы данных
AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True) AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True)
@ -110,12 +137,25 @@ def register(req: RegisterRequest, db: Session = Depends(get_db)):
phone = req.phone phone = req.phone
name = getattr(req, 'name', None) name = getattr(req, 'name', None)
login = getattr(req, 'login', None) login = getattr(req, 'login', None)
print(f'tg_id: {tg_id}, chat_id: {chat_id}, phone: {phone}, name: {name}, login: {login}') company_key = req.company_key
print(f'tg_id: {tg_id}, chat_id: {chat_id}, phone: {phone}, name: {name}, login: {login}, company_key: {company_key}')
tg_agent = get_tg_agent_by_tg_id(db, tg_id) tg_agent = get_tg_agent_by_tg_id(db, tg_id)
if tg_agent: if tg_agent:
raise HTTPException(status_code=400, detail="tg_id already registered") raise HTTPException(status_code=400, detail="tg_id already registered")
# Поиск компании по ключу
company = db.exec(select(Company).where(Company.key == company_key)).first()
if not company:
raise HTTPException(status_code=400, detail="Компания с таким ключом не найдена")
hash_value = sha256(f"{tg_id}sold".encode()).hexdigest() hash_value = sha256(f"{tg_id}sold".encode()).hexdigest()
new_tg_agent = TgAgent(tg_id=tg_id, chat_id=chat_id, phone=phone, name=name, login=login, hash=hash_value) new_tg_agent = TgAgent(
tg_id=tg_id,
chat_id=chat_id,
phone=phone,
name=name,
login=login,
hash=hash_value,
company_id=company.id
)
db.add(new_tg_agent) db.add(new_tg_agent)
db.commit() db.commit()
db.refresh(new_tg_agent) db.refresh(new_tg_agent)
@ -472,18 +512,7 @@ def get_billing_chart_pie(db: Session = Depends(get_db)):
] ]
return JSONResponse(content=data) return JSONResponse(content=data)
@app.get("/account", tags=["bff"])
def get_account(db: Session = Depends(get_db)):
account = db.exec(select(Account)).first()
if not account:
raise HTTPException(status_code=404, detail="Account not found")
return {
"id": account.id,
"login": account.login,
"name": account.name,
"email": account.email,
"balance": account.balance
}
@app.post("/tg_auth", tags=["partner-tg"]) @app.post("/tg_auth", tags=["partner-tg"])
def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_db)): def tg_auth(hash: str = Body(..., embed=True), db: Session = Depends(get_db)):
@ -501,3 +530,86 @@ def verify_password(plain_password, hashed_password):
def get_account_by_login(db: Session, login: str) -> Optional[Account]: def get_account_by_login(db: Session, login: str) -> Optional[Account]:
statement = select(Account).where(Account.login == login) statement = select(Account).where(Account.login == login)
return db.exec(statement).first() return db.exec(statement).first()
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
@app.get("/account", tags=["bff"])
def get_account(current_account: Account = Depends(get_current_account)):
return {
"firstName": current_account.firstName,
"surname": current_account.surname
}
@app.get("/account/profile", tags=["bff"])
def get_account_profile(current_account: Account = Depends(get_current_account), db: Session = Depends(get_db)):
company = db.exec(select(Company).where(Company.id == current_account.company_id)).first()
if not company:
raise HTTPException(status_code=404, detail="Компания не найдена")
return {
"firstName": current_account.firstName,
"surname": current_account.surname,
"phone": current_account.phone,
"email": current_account.email,
"create_dttm": current_account.create_dttm,
"company": {
"name": company.name,
"key": company.key,
"commission": company.commission
}
}
@app.post("/account/profile", tags=["bff"])
def update_account_profile(
req: AccountProfileUpdateRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
# Проверка, что все поля заполнены (Pydantic уже валидирует email и обязательность)
if not req.firstName.strip() or not req.surname.strip() or not req.email or not req.phone.strip():
raise HTTPException(status_code=400, detail="Все поля должны быть заполнены")
# Обновляем поля
current_account.firstName = req.firstName.strip()
current_account.surname = req.surname.strip()
current_account.email = req.email
current_account.phone = req.phone.strip()
db.add(current_account)
db.commit()
db.refresh(current_account)
return {"msg": "Профиль обновлён успешно"}
@app.post("/account/password", tags=["bff"])
def change_account_password(
req: AccountPasswordChangeRequest,
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
# Проверяем текущий пароль
if not verify_password(req.currentPassword, current_account.password_hash):
raise HTTPException(status_code=400, detail="Текущий пароль неверный")
# Проверяем, что новый пароль не пустой и отличается от текущего
if not req.newPassword.strip():
raise HTTPException(status_code=400, detail="Новый пароль не может быть пустым")
if verify_password(req.newPassword, current_account.password_hash):
raise HTTPException(status_code=400, detail="Новый пароль не должен совпадать с текущим")
# Хешируем и сохраняем новый пароль
current_account.password_hash = pwd_context.hash(req.newPassword)
db.add(current_account)
db.commit()
db.refresh(current_account)
return {"msg": "Пароль успешно изменён"}

View File

@ -29,3 +29,4 @@ class RegisterRequest(BaseModel):
phone: Optional[str] = None phone: Optional[str] = None
name: Optional[str] = None name: Optional[str] = None
login: Optional[str] = None login: Optional[str] = None
company_key: str