feat(bat-019): REQ-044 GET /v1/assets require_read + REQ-045 GET /v1/daemons caller-scope
REQ-044: list_catalog_assets 의존성을 require_admin → require_read로 완화. asset 쓰기(POST/PATCH/DELETE)는 require_admin 유지. REQ-045: GET /v1/daemons 신규 엔드포인트 — caller 소유 데몬만 반환(require_read). user_id=None(env 토큰)은 빈 목록 200. DaemonResponse/DaemonListResponse 스키마 추가. 기존 테스트(test_v1.py, test_asset_catalog.py, test_daemon_assignment.py, test_tags_master.py) REQ-044 권한 완화에 맞게 업데이트. test_v1_daemons.py 신규 추가(13 케이스). pytest 472 passed, 0 failed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -6,7 +6,7 @@ import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
|
||||
from app.core.auth import AuthContext, require_admin
|
||||
from app.core.auth import AuthContext, require_admin, require_read
|
||||
from app.repositories.factory import get_portfolio_repository_from_settings
|
||||
from app.schemas.v1 import (
|
||||
AssetCatalogCreateRequest,
|
||||
@@ -76,7 +76,7 @@ _CATALOG_STATUS_FILTERS = {"active", "inactive", "all"}
|
||||
def list_catalog_assets(
|
||||
status_filter: str = Query(default="active", alias="status"),
|
||||
market: str | None = Query(default=None),
|
||||
auth: AuthContext = Depends(require_admin),
|
||||
auth: AuthContext = Depends(require_read),
|
||||
) -> AssetCatalogListResponse:
|
||||
if status_filter not in _CATALOG_STATUS_FILTERS:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
"""v1 daemon list endpoint — caller-scoped daemon list (REQ-045)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.core.auth import AuthContext, require_read
|
||||
from app.repositories.factory import get_portfolio_repository_from_settings
|
||||
from app.schemas.v1 import DaemonListResponse, DaemonResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/v1", tags=["v1"])
|
||||
|
||||
|
||||
def _get_repo(settings=None):
|
||||
from app.core.config import get_settings
|
||||
return get_portfolio_repository_from_settings(settings or get_settings())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /v1/daemons — caller-scoped daemon list (REQ-045)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@router.get("/daemons", response_model=DaemonListResponse)
|
||||
def list_daemons(
|
||||
auth: AuthContext = Depends(require_read),
|
||||
) -> DaemonListResponse:
|
||||
"""Return all daemons owned by the caller (auth.user_id).
|
||||
|
||||
- Caller scope only: returns daemons belonging to auth.user_id.
|
||||
- admin token with user_id also returns only that user's daemons.
|
||||
- env/service tokens with user_id=None return an empty list (HTTP 200).
|
||||
- Returns all daemons (active and inactive) with is_active field for distinction.
|
||||
"""
|
||||
if auth.user_id is None:
|
||||
return DaemonListResponse(daemons=[], total=0)
|
||||
|
||||
repo = _get_repo()
|
||||
try:
|
||||
daemons = repo.list_daemons_for_user(auth.user_id)
|
||||
finally:
|
||||
if callable(getattr(type(repo), "close", None)):
|
||||
repo.close()
|
||||
|
||||
items = [
|
||||
DaemonResponse(
|
||||
id=d.id,
|
||||
daemon_slug=d.daemon_slug,
|
||||
display_name=d.display_name,
|
||||
is_active=d.is_active,
|
||||
)
|
||||
for d in daemons
|
||||
]
|
||||
return DaemonListResponse(daemons=items, total=len(items))
|
||||
Reference in New Issue
Block a user