Files
lux_fiscal/backend/app/api/deps.py
T
lauadminandClaude Sonnet 5 f4072be451 Add CRM order queue: live sync, view modal, receipt tabs, delete
Wires the CRM (exoCRM GetOrders) into the dashboard as a locally
persisted order queue instead of the previous static mockup:

- CrmClient Protocol + ExoCrmClient/StubCrmClient for the CRM's
  signed JSON-RPC API
- Order model + migration, synced from CRM on each queue view;
  soft-deleted orders stay hidden across re-syncs
- GET/DELETE /api/v1/orders with "no receipt"/"receipt issued" tabs
  (the latter is empty until Checkbox fiscalization lands)
- Dashboard: real order list, item-detail modal, tab switcher,
  one-click delete

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 21:39:03 +03:00

98 lines
3.2 KiB
Python

"""Зависимости FastAPI: сессия БД, текущий пользователь, проверка ролей."""
from __future__ import annotations
import uuid
from collections.abc import Callable, Coroutine
from typing import Annotated, Any
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.security import TokenError, decode_access_token
from app.db.models.user import User, UserRole
from app.db.session import get_session
from app.services.crm.client import CrmClient
from app.services.crm.exo_client import ExoCrmClient
bearer_scheme = HTTPBearer(auto_error=False)
SessionDep = Annotated[AsyncSession, Depends(get_session)]
_UNAUTHORIZED = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Требуется аутентификация",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user(
session: SessionDep,
credentials: Annotated[HTTPAuthorizationCredentials | None, Depends(bearer_scheme)],
) -> User:
if credentials is None:
raise _UNAUTHORIZED
try:
payload = decode_access_token(credentials.credentials)
except TokenError as exc:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=str(exc),
headers={"WWW-Authenticate": "Bearer"},
) from exc
try:
user_id = uuid.UUID(payload["sub"])
except (KeyError, ValueError) as exc:
raise _UNAUTHORIZED from exc
# Пользователь читается из БД на каждый запрос, а не берётся из токена:
# отключённая учётка должна терять доступ немедленно, не дожидаясь
# истечения access-токена.
user = await session.get(User, user_id)
if user is None or not user.is_active:
raise _UNAUTHORIZED
return user
CurrentUser = Annotated[User, Depends(get_current_user)]
def require_roles(*roles: UserRole) -> Callable[..., Coroutine[Any, Any, User]]:
"""Ограничивает эндпоинт набором ролей.
Пример:
@router.post("/", dependencies=[Depends(require_roles(UserRole.ADMIN))])
"""
allowed = set(roles)
async def _guard(user: CurrentUser) -> User:
if user.role not in allowed:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Недостаточно прав для этого действия",
)
return user
return _guard
# Готовые зависимости под роли из плана.
require_admin = require_roles(UserRole.ADMIN)
require_cashier = require_roles(UserRole.ADMIN, UserRole.CASHIER)
require_any = require_roles(UserRole.ADMIN, UserRole.CASHIER, UserRole.VIEWER)
AdminUser = Annotated[User, Depends(require_admin)]
CashierUser = Annotated[User, Depends(require_cashier)]
def get_crm_client() -> CrmClient:
return ExoCrmClient(settings)
CrmClientDep = Annotated[CrmClient, Depends(get_crm_client)]