feat(req-002): implement portfolio tagging and tag filters
This commit is contained in:
+78
-2
@@ -1,6 +1,6 @@
|
||||
"""Portfolio API routes."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Query, Request, Response, status
|
||||
|
||||
from app.core.auth import AuthContext, Permission, authorize_request
|
||||
from app.core.config import Settings, get_settings
|
||||
@@ -14,6 +14,8 @@ from app.schemas.portfolio import (
|
||||
InvestRequest,
|
||||
PortfolioDetailResponse,
|
||||
PortfolioListItem,
|
||||
PortfolioTagMutationRequest,
|
||||
PortfolioTagResponse,
|
||||
ResetPortfolioRequest,
|
||||
SellRequest,
|
||||
TransferRequest,
|
||||
@@ -26,6 +28,8 @@ router = APIRouter(prefix="/portfolios", tags=["portfolios"])
|
||||
|
||||
def _raise_portfolio_error(exc: ValueError) -> None:
|
||||
detail = str(exc)
|
||||
if "이미 연결된 태그입니다" in detail:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=detail) from exc
|
||||
if "찾을 수 없습니다" in detail:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=detail) from exc
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail) from exc
|
||||
@@ -68,6 +72,9 @@ def list_portfolios(
|
||||
request: Request,
|
||||
broker: str | None = None,
|
||||
status_filter: str | None = None,
|
||||
include_tags: list[str] | None = Query(default=None, alias="includeTags"),
|
||||
exclude_tags: list[str] | None = Query(default=None, alias="excludeTags"),
|
||||
match_mode: str | None = Query(default=None, alias="matchMode"),
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> list[PortfolioListItem]:
|
||||
auth_context = authorize_request(Permission.READ, request, settings)
|
||||
@@ -76,7 +83,16 @@ def list_portfolios(
|
||||
owner_user_id: str | None = None
|
||||
if _ownership_required(settings) and auth_context.permission != Permission.ADMIN:
|
||||
owner_user_id = _require_owner_context(auth_context)
|
||||
return service.list_portfolios(broker=broker, status=status_filter, owner_user_id=owner_user_id)
|
||||
return service.list_portfolios(
|
||||
broker=broker,
|
||||
status=status_filter,
|
||||
owner_user_id=owner_user_id,
|
||||
include_tags=include_tags,
|
||||
exclude_tags=exclude_tags,
|
||||
match_mode=match_mode,
|
||||
)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
|
||||
@@ -329,3 +345,63 @@ def delete_comment(
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{portfolio_id}/tags", response_model=PortfolioTagResponse, status_code=status.HTTP_201_CREATED)
|
||||
def add_tag(
|
||||
portfolio_id: str,
|
||||
payload: PortfolioTagMutationRequest,
|
||||
request: Request,
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> PortfolioTagResponse:
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.add_tag(portfolio_id, payload)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
|
||||
|
||||
@router.get("/{portfolio_id}/tags", response_model=list[PortfolioTagResponse])
|
||||
def list_tags(
|
||||
portfolio_id: str,
|
||||
request: Request,
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> list[PortfolioTagResponse]:
|
||||
auth_context = authorize_request(Permission.READ, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
return service.list_tags(portfolio_id)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
|
||||
|
||||
@router.delete("/{portfolio_id}/tags", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def remove_tag(
|
||||
portfolio_id: str,
|
||||
request: Request,
|
||||
payload: PortfolioTagMutationRequest = Body(...),
|
||||
*,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> Response:
|
||||
auth_context = authorize_request(Permission.TRADE_WRITE, request, settings)
|
||||
service, repository = _get_service(settings)
|
||||
try:
|
||||
if _ownership_required(settings):
|
||||
_ensure_portfolio_owned(repository, portfolio_id, auth_context)
|
||||
service.remove_tag(portfolio_id, payload)
|
||||
except ValueError as exc:
|
||||
_raise_portfolio_error(exc)
|
||||
finally:
|
||||
_close_repository(repository)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
Reference in New Issue
Block a user