fix(dcy-011/bat-022): GET /v1/admin/daemons 목록 엔드포인트 추가 — UI 계약 누락(405) 해소 (라이브 통합테스트 결함)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-28 18:20:13 +09:00
co-authored by Claude Opus 4.8
parent 0fbd33b08b
commit 2dcbafb7a8
4 changed files with 172 additions and 1 deletions
+40 -1
View File
@@ -9,7 +9,13 @@ from fastapi import APIRouter, Depends, HTTPException, Response, status
from app.core.auth import AuthContext, require_admin
from app.core.config import Settings, get_settings
from app.repositories.factory import get_portfolio_repository_from_settings
from app.schemas.v1 import DaemonAdminResponse, DaemonCreateRequest, DaemonUpdateRequest
from app.schemas.v1 import (
DaemonAdminResponse,
DaemonCreateRequest,
DaemonDetail,
DaemonDetailListResponse,
DaemonUpdateRequest,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/v1/admin", tags=["v1-admin"])
@@ -41,6 +47,39 @@ def _daemon_error_to_http(exc: ValueError) -> HTTPException:
return HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
# ---------------------------------------------------------------------------
# GET /v1/admin/daemons — 전체 데몬 목록 (admin 전용)
# ---------------------------------------------------------------------------
@router.get("/daemons", response_model=DaemonDetailListResponse)
def list_all_daemons(
_auth: AuthContext = Depends(require_admin),
settings: Settings = Depends(get_settings),
) -> DaemonDetailListResponse:
"""Return all daemons across all owners (admin only).
Matches trex-web DaemonDetailListResponse / DaemonDetail contract
consumed by adminListDaemons() (REQ-035).
"""
repo = _get_repo(settings)
try:
daemons = repo.list_daemons_all()
items = [
DaemonDetail(
id=d.id,
daemon_slug=d.daemon_slug,
display_name=d.display_name,
is_active=d.is_active,
owner_user_id=d.owner_user_id,
)
for d in daemons
]
return DaemonDetailListResponse(daemons=items, total=len(items))
finally:
if callable(getattr(type(repo), "close", None)):
repo.close()
# ---------------------------------------------------------------------------
# POST /v1/admin/daemons — 데몬 생성 (REQ-053 AC-1/2)
# ---------------------------------------------------------------------------
+13
View File
@@ -2289,6 +2289,19 @@ class PostgresPortfolioRepository:
rows = cur.fetchall()
return [self._row_to_daemon(row) for row in rows]
def list_daemons_all(self) -> list[Daemon]:
"""Return all daemons across all owners (admin use only)."""
self.ensure_schema()
with self._connect().cursor() as cur:
cur.execute(
"""
SELECT id, owner_user_id, daemon_slug, display_name, is_active, created_at, updated_at
FROM daemons ORDER BY created_at ASC
""",
)
rows = cur.fetchall()
return [self._row_to_daemon(row) for row in rows]
def update_daemon(
self,
daemon_id: str,
+19
View File
@@ -375,3 +375,22 @@ class DaemonAdminResponse(BaseModel):
display_name: str | None = None
owner_user_id: str
is_active: bool
# ---------------------------------------------------------------------------
# GET /v1/admin/daemons (REQ-053 목록 조회)
# ---------------------------------------------------------------------------
class DaemonDetail(BaseModel):
"""Single daemon item for admin list — matches trex-web DaemonDetail contract."""
id: str
daemon_slug: str
display_name: str | None = None
is_active: bool
owner_user_id: str
class DaemonDetailListResponse(BaseModel):
"""Response for GET /v1/admin/daemons — matches trex-web DaemonDetailListResponse."""
daemons: list[DaemonDetail]
total: int