Рефакторинг структуры проекта: перемещение функций и логики работы с базой данных в новый файл helpers_bff.py. Обновлены импорты в fill_db.py и main.py для использования новых функций. Удалены устаревшие функции и классы из main.py, улучшена организация кода.

This commit is contained in:
Redsandyg 2025-06-08 21:23:26 +03:00
parent 155d1002fc
commit 6e804953c0
4 changed files with 267 additions and 249 deletions

View File

@ -1,11 +1,11 @@
import random
from uuid import uuid4
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 datetime import datetime, timedelta
from hashlib import sha256
from passlib.context import CryptContext
from helpers_bff import AUTH_DB_ENGINE, get_password_hash
# Константа: список user_ids
@ -63,11 +63,6 @@ LOGINS = [
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):
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
return [today - timedelta(days=i) for i in range(days, -1, -1)]

90
helpers_bff.py Normal file
View 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
View File

@ -1,204 +1,76 @@
import uuid
from fastapi import FastAPI, Depends, HTTPException, status, Query, Body, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlmodel import SQLModel, Field, create_engine, Session, select
from passlib.context import CryptContext
from fastapi import (
FastAPI,
Depends,
HTTPException,
status,
Query,
Body,
)
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import SQLModel, Session, select
from typing import Optional, List, Dict
from datetime import datetime, timedelta
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 tg_models import RefAddRequest, RefResponse, RegisterRequest, TokenRequest, RefAddResponse, RefStatResponse, StatResponse
from uuid import uuid4
from fastapi.responses import JSONResponse
from datetime import timedelta, datetime
from bff_models import (
Token,
RegisterResponse,
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 hashlib import sha256
import jwt
from jwt.exceptions import InvalidTokenError
from pydantic import BaseModel, EmailStr
from enum import Enum
# Конфигурация
AUTH_DATABASE_ADDRESS = "sqlite:///partner.db"
#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)
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 # Поле для имени агента
import hashlib
from helpers_bff import (
AUTH_DB_ENGINE,
get_db,
get_tg_agent_by_tg_id,
get_current_account,
get_current_tg_agent,
create_access_token,
verify_password,
get_account_by_login,
ACCESS_TOKEN_EXPIRE_MINUTES,
pwd_context,
)
# Создание движка базы данных
AUTH_DB_ENGINE = create_engine(AUTH_DATABASE_ADDRESS, echo=True)
SQLModel.metadata.create_all(AUTH_DB_ENGINE)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
# FastAPI app
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)
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()
if not company:
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(
tg_id=tg_id,
chat_id=chat_id,
@ -234,30 +106,6 @@ def register(req: RegisterRequest, db: Session = Depends(get_db)):
db.refresh(new_tg_agent)
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"])
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(
tg_agent_id=current_tg_agent.id,
ref=str(uuid4()),
ref=str(uuid.uuid4()),
description=req.description
)
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}
# --- Новый функционал для 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)
@ -754,9 +593,9 @@ def change_account_password(
@app.get("/account/agent-transaction", response_model=List[AgentTransactionResponse], tags=["bff", "account"])
def get_account_agent_transactions(
statuses: Optional[List[TransactionStatus]] = Query(None), # Изменено на List[TransactionStatus]
date_start: str = Query(None), # Добавлен параметр date_start
date_end: str = Query(None), # Добавлен параметр date_end
statuses: Optional[List[TransactionStatus]] = Query(None),
date_start: str = Query(None),
date_end: str = Query(None),
current_account: Account = Depends(get_current_account),
db: Session = Depends(get_db)
):
@ -788,23 +627,26 @@ def get_account_agent_transactions(
# Формируем список ответов в формате AgentTransactionResponse
agent_transactions_response = []
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(
AgentTransactionResponse(
amount=agent_trans.amount,
status=agent_trans.status,
status=status_enum,
transaction_group=agent_trans.transaction_group,
create_dttm=agent_trans.create_dttm,
update_dttm=agent_trans.update_dttm,
agent_name=agent.name # Используем имя агента
agent_name=agent.name
)
)
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)
def get_auto_approve_settings(
@ -854,8 +696,8 @@ def update_auto_approve_settings(
# Находим соответствующие партнерские транзакции и обновляем их статус
partner_transactions_to_update = db.exec(
select(PartnerTransaction)
.where(PartnerTransaction.agent_transaction_id == agent_trans.id) # Используем связь по ID
.where(PartnerTransaction.status == TransactionStatus.PROCESS) # Предполагаем, что связанные партнерские транзакции в статусе PROCESS
.where(PartnerTransaction.agent_transaction_id == agent_trans.id)
.where(PartnerTransaction.status == TransactionStatus.PROCESS)
).all()
for partner_trans in partner_transactions_to_update:
partner_trans.status = TransactionStatus.NEW
@ -867,9 +709,7 @@ def update_auto_approve_settings(
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)
def approve_agent_transactions(
@ -893,11 +733,11 @@ def approve_agent_transactions(
.join(TgAgent)
.where(TgAgent.company_id == company_id)
.where(AgentTransaction.transaction_group.in_(req.transaction_ids))
.where(AgentTransaction.status == TransactionStatus.WAITING) # Утверждаем только транзакции в статусе 'waiting'
.where(AgentTransaction.status == TransactionStatus.WAITING)
).all()
for agent_trans in transactions_to_approve:
agent_trans.status = TransactionStatus.NEW # Переводим в статус 'new'
agent_trans.status = TransactionStatus.NEW
agent_trans.update_dttm = datetime.utcnow()
db.add(agent_trans)
approved_count += 1

93
sql_models.py Normal file
View 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)