diff --git a/fill_db.py b/fill_db.py index 3aa9f34..13c32d5 100644 --- a/fill_db.py +++ b/fill_db.py @@ -95,10 +95,15 @@ def fill_db(): # 1. Accounts accounts = [] 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( login=f"user{i+1}", 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", company_id=company.id ) diff --git a/main.py b/main.py index 0054859..f25758e 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,8 @@ from fastapi.responses import JSONResponse from sqlalchemy import func from hashlib import sha256 import jwt +from jwt.exceptions import InvalidTokenError +from pydantic import BaseModel, EmailStr # Конфигурация AUTH_DATABASE_ADDRESS = "sqlite:///partner.db" @@ -67,12 +69,24 @@ class Account(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) login: str = Field(index=True, unique=True) password_hash: str # теперь хранится hash пароля - name: Optional[str] = None + 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 + # Создание движка базы данных AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True) 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) -@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"]) 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]: statement = select(Account).where(Account.login == login) 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": "Пароль успешно изменён"}