Обновлены модели аккаунтов для хранения имени, фамилии и телефона. Добавлены функции для получения и обновления профиля аккаунта, а также изменения пароля. Улучшена валидация данных с использованием Pydantic.

This commit is contained in:
Redsandyg 2025-06-03 20:38:16 +03:00
parent fa0161710e
commit 161e0b3ec4
2 changed files with 105 additions and 14 deletions

View File

@ -95,10 +95,15 @@ def fill_db():
# 1. Accounts # 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",
company_id=company.id company_id=company.id
) )

112
main.py
View File

@ -10,6 +10,8 @@ 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"
@ -67,12 +69,24 @@ 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
company_id: int = Field(foreign_key="company.id") 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)
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)
SQLModel.metadata.create_all(AUTH_DB_ENGINE) SQLModel.metadata.create_all(AUTH_DB_ENGINE)
@ -498,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)):
@ -527,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": "Пароль успешно изменён"}