feat(auth): add DB-backed login, token management, and idempotent auth migration
This commit is contained in:
+46
-18
@@ -2,24 +2,37 @@
|
||||
|
||||
from fastapi import APIRouter, Depends, Response, status
|
||||
|
||||
from app.core.auth import require_read, require_trade_write
|
||||
from app.core.auth import AuthContext, require_read, require_trade_write
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.repositories.factory import get_portfolio_repository_from_settings
|
||||
from app.schemas.portfolio import CreateStrategySetRequest, StrategySetResponse, UpdateStrategySetRequest
|
||||
from app.services.strategy_set_service import StrategySetService, get_strategy_set_repository
|
||||
|
||||
|
||||
def get_strategy_set_service(repository=Depends(get_strategy_set_repository)) -> StrategySetService:
|
||||
return StrategySetService(repository)
|
||||
from app.services.strategy_set_service import StrategySetService
|
||||
|
||||
|
||||
router = APIRouter(prefix="/strategy-sets", tags=["strategy-sets"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[StrategySetResponse], dependencies=[Depends(require_read)])
|
||||
def _get_service(settings: Settings) -> tuple[StrategySetService, object]:
|
||||
repository = get_portfolio_repository_from_settings(settings)
|
||||
return StrategySetService(repository), repository
|
||||
|
||||
|
||||
def _close_repository(repository: object) -> None:
|
||||
if callable(getattr(type(repository), "close", None)):
|
||||
repository.close()
|
||||
|
||||
|
||||
@router.get("", response_model=list[StrategySetResponse])
|
||||
def list_strategy_sets(
|
||||
service: StrategySetService = Depends(get_strategy_set_service),
|
||||
_auth: AuthContext = Depends(require_read),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> list[StrategySetResponse]:
|
||||
"""List all strategy sets."""
|
||||
strategy_sets = service.list()
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
strategy_sets = service.list()
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
return [
|
||||
StrategySetResponse(
|
||||
id=ss.id,
|
||||
@@ -32,13 +45,18 @@ def list_strategy_sets(
|
||||
]
|
||||
|
||||
|
||||
@router.post("", response_model=StrategySetResponse, status_code=status.HTTP_201_CREATED, dependencies=[Depends(require_trade_write)])
|
||||
@router.post("", response_model=StrategySetResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create_strategy_set(
|
||||
payload: CreateStrategySetRequest,
|
||||
service: StrategySetService = Depends(get_strategy_set_service),
|
||||
_auth: AuthContext = Depends(require_trade_write),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> StrategySetResponse:
|
||||
"""Create a new strategy set (or return existing if (name, config) pair already exists)."""
|
||||
strategy_set = service.create(payload)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
strategy_set = service.create(payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
return StrategySetResponse(
|
||||
id=strategy_set.id,
|
||||
name=strategy_set.name,
|
||||
@@ -48,18 +66,23 @@ def create_strategy_set(
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{strategy_set_id}", response_model=StrategySetResponse, dependencies=[Depends(require_trade_write)])
|
||||
@router.patch("/{strategy_set_id}", response_model=StrategySetResponse)
|
||||
def update_strategy_set(
|
||||
strategy_set_id: str,
|
||||
payload: UpdateStrategySetRequest,
|
||||
service: StrategySetService = Depends(get_strategy_set_service),
|
||||
_auth: AuthContext = Depends(require_trade_write),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> StrategySetResponse:
|
||||
"""Update a strategy set's name and/or config.
|
||||
|
||||
Note: Since strategy sets are identified by (name, config) uniqueness,
|
||||
updating may result in a new strategy set ID if name or config changes.
|
||||
"""
|
||||
strategy_set = service.update(strategy_set_id, payload)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
strategy_set = service.update(strategy_set_id, payload)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
return StrategySetResponse(
|
||||
id=strategy_set.id,
|
||||
name=strategy_set.name,
|
||||
@@ -69,10 +92,11 @@ def update_strategy_set(
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{strategy_set_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(require_trade_write)])
|
||||
@router.delete("/{strategy_set_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_strategy_set(
|
||||
strategy_set_id: str,
|
||||
service: StrategySetService = Depends(get_strategy_set_service),
|
||||
_auth: AuthContext = Depends(require_trade_write),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> Response:
|
||||
"""Delete a strategy set.
|
||||
|
||||
@@ -80,7 +104,11 @@ def delete_strategy_set(
|
||||
Returns 409 Conflict if the strategy set is in use by any portfolio.
|
||||
"""
|
||||
try:
|
||||
service.delete(strategy_set_id)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
service.delete(strategy_set_id)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
except ValueError as e:
|
||||
# If strategy set is in use, return 409 Conflict
|
||||
raise ValueError(str(e)) from e
|
||||
|
||||
Reference in New Issue
Block a user