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:
@@ -0,0 +1,77 @@
|
||||
# BAT-019 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-019
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
이 batch는 두 가지 독립적인 변경으로 구성된다.
|
||||
|
||||
1. **REQ-044**: `GET /v1/assets`의 인증 게이트를 `require_admin` → `require_read`로 완화.
|
||||
기존 `AssetCatalogListResponse` 응답 구조, 필터 파라미터, 라우터 등록은 모두 불변.
|
||||
|
||||
2. **REQ-045**: `GET /v1/daemons` 신규 엔드포인트.
|
||||
caller(`auth.user_id`) 소유 데몬만 반환. `require_read` 인증.
|
||||
신규 파일: `app/api/v1_daemons.py`, 신규 스키마: `DaemonResponse`, `DaemonListResponse`.
|
||||
`app/main.py`에 라우터 등록.
|
||||
|
||||
## Changed Areas
|
||||
|
||||
- Docs: `docs/batches/bat-019_20260627_assets-read-permission-and-daemon-list/`
|
||||
- Modules:
|
||||
- `app/api/v1_assets.py` (require_admin → require_read, list_catalog_assets만)
|
||||
- `app/api/v1_daemons.py` (신규)
|
||||
- `app/schemas/v1.py` (DaemonResponse, DaemonListResponse 추가)
|
||||
- `app/main.py` (v1_daemons 라우터 include_router 추가)
|
||||
- Interfaces:
|
||||
- `GET /v1/assets`: 기존 인터페이스 불변, 인증 게이트만 완화
|
||||
- `GET /v1/daemons`: 신규 엔드포인트
|
||||
|
||||
## Key Decisions
|
||||
|
||||
1. **REQ-044 — 권한 완화 범위**: `list_catalog_assets`(GET /v1/assets) 1개만 `require_read`로 교체.
|
||||
POST /v1/assets, PATCH /{id}, PATCH /{id}/deactivate, POST /{id}/tags, DELETE /{id}/tags/{tid}는 `require_admin` 유지.
|
||||
응답 스키마 `AssetCatalogListResponse`는 불변.
|
||||
|
||||
2. **REQ-045 — 경로**: `GET /v1/daemons`.
|
||||
`/v1/user/daemons` 대신 `/v1/daemons` 채택 — 기존 `/v1/tags`, `/v1/assets`, `/v1/portfolios` 패턴과 일치.
|
||||
|
||||
3. **REQ-045 — 응답 스키마**: `DaemonListResponse{daemons: list[DaemonResponse], total: int}`.
|
||||
`DaemonResponse` 필드: `id`(str), `daemon_slug`(str), `display_name`(str|None), `is_active`(bool).
|
||||
기존 `TagListResponse{tags: list[TagItem], total: int}` 패턴과 동일 구조.
|
||||
|
||||
4. **REQ-045 — env/service 토큰(user_id=None) 처리**: `auth.user_id`가 None이면 빈 목록 `[]` + `total: 0`을 HTTP 200으로 반환.
|
||||
이유: 환경 변수 토큰 또는 user_id 없는 서비스 토큰은 특정 사용자 소유 데몬이 없으므로 에러보다 빈 응답이 자연스럽다.
|
||||
기존 `/v1/user_info`의 env 토큰 처리(403 Service token required)와 다른 접근이며, 데몬 목록은 사용자 식별 없이도 빈 목록을 반환하는 것이 호출자 친화적이다.
|
||||
|
||||
5. **REQ-045 — active 필터**: 기본적으로 소유 데몬 전체 반환(active/inactive 모두). `is_active` 필드로 구분 표시.
|
||||
이유: caller가 비활성 데몬도 인지해야 할 수 있으며(재활성화 등), 필터는 UI 레이어에서 처리 가능.
|
||||
|
||||
6. **REQ-045 — AuthService 활용**: `AuthService.find_candidate_daemons_for_user(user_id)` 호출.
|
||||
단, 라우터에서 직접 repository를 호출하는 기존 패턴(v1_assets, v1_tags 등)과 일관성을 위해
|
||||
라우터에서 직접 `repo.list_daemons_for_user(user_id)`를 호출하는 방식 채택
|
||||
(service 레이어 DI overhead 없이 기존 패턴 유지).
|
||||
|
||||
7. **REQ-044 테스트 영향**: 기존 테스트 중 `test_get_v1_assets_user_token_returns_403`,
|
||||
`test_get_v1_assets_service_token_returns_403`는 이번 완화로 동작이 바뀜.
|
||||
— user 토큰(permission="read"): 이제 200이어야 함 → 테스트 업데이트.
|
||||
— service 토큰(permission="trade-write"): trade-write는 read 포함(_has_permission 로직상)이므로 200이어야 함 → 테스트 업데이트.
|
||||
또한 `test_v1_assets_rejects_env_token`(test_v1.py): env read 토큰도 이제 200이어야 함 → 업데이트.
|
||||
`test_v1_assets_returns_asset_list`: read 토큰이 이제 403이 아닌 200 → 업데이트.
|
||||
`test_v1_assets_market_filter`, `test_v1_assets_asset_id_is_uuid`: 동일.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- `user_id=None`(env 토큰): 빈 목록 200 반환 (결정 #4).
|
||||
- 데몬 없는 사용자: 빈 목록 `[]` 200 반환.
|
||||
- admin 토큰도 caller 스코프: admin 토큰의 `user_id`가 None이면 빈 목록, user_id가 있으면 해당 user 소유 데몬만.
|
||||
- `list_daemons_for_user` DB 미활성(db_enabled=False): 빈 목록 반환 또는 InMemory 구현에 따름.
|
||||
|
||||
## Architecture Impact
|
||||
|
||||
- none (라우터 추가는 runtime-flows 수준 변경이 아님; project-structure에 신규 파일 기록 수준)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
|
||||
- project-structure.md: none (파일 추가 수준, 별도 문서화 불필요)
|
||||
- runtime-flows.md: none
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# BAT-019 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-019
|
||||
|
||||
## Plan Summary
|
||||
|
||||
- REQ-044: `app/api/v1_assets.py`의 `list_catalog_assets` 의존성을 `require_admin` → `require_read`로 교체.
|
||||
- REQ-045: `app/schemas/v1.py`에 `DaemonResponse`/`DaemonListResponse` 추가, `app/api/v1_daemons.py` 신규 생성, `app/main.py`에 라우터 등록.
|
||||
- 테스트: `tests/test_v1.py` 기존 403 기대 케이스 업데이트, `tests/test_asset_catalog.py` 기존 403 기대 케이스 업데이트, `tests/test_v1_daemons.py` 신규 생성.
|
||||
|
||||
## Changed Files
|
||||
|
||||
- `app/api/v1_assets.py`: `require_read` import 추가, `list_catalog_assets` 의존성 교체 (require_admin → require_read)
|
||||
- `app/api/v1_daemons.py`: 신규 생성 — GET /v1/daemons 라우터
|
||||
- `app/schemas/v1.py`: `DaemonResponse`, `DaemonListResponse` 스키마 추가
|
||||
- `app/main.py`: `v1_daemons_router` import 및 `include_router` 추가
|
||||
- `tests/test_v1.py`: 5개 테스트 업데이트 (403→200 기대치 변경, 일부 내용/이름 변경)
|
||||
- `tests/test_asset_catalog.py`: 2개 테스트 삭제 후 3개로 교체 (REQ-044 반영 + 무토큰 401 추가)
|
||||
- `tests/test_v1_daemons.py`: 신규 생성 — REQ-045 AC 전체 + REQ-044 회귀 커버
|
||||
|
||||
## Execution Log
|
||||
|
||||
1. `app/api/v1_assets.py`: `require_read` import, `list_catalog_assets` Depends 교체
|
||||
2. `app/schemas/v1.py`: DaemonResponse, DaemonListResponse 스키마 추가 (파일 말미)
|
||||
3. `app/api/v1_daemons.py`: 신규 작성 (GET /v1/daemons, user_id=None → 빈 목록 200)
|
||||
4. `app/main.py`: v1_daemons_router import + include_router
|
||||
5. `tests/test_v1.py`: 영향받는 5개 테스트 업데이트
|
||||
6. `tests/test_asset_catalog.py`: 영향받는 2개 테스트 → 3개로 교체
|
||||
7. `tests/test_v1_daemons.py`: 신규 생성 (13개 테스트)
|
||||
8. pytest 전체 실행 → 결과 verification.md에 기록
|
||||
|
||||
## Validation
|
||||
|
||||
- pytest 전체 실행 결과: verification.md 참조
|
||||
|
||||
## Remaining Risks
|
||||
|
||||
- `list_daemons_for_user`는 DB 라이브 테스트 없이 mock으로만 검증됨 (DB 통합 테스트는 비활성화 환경 밖에서 필요)
|
||||
@@ -0,0 +1,22 @@
|
||||
# BAT-019 Index
|
||||
|
||||
- Status: in-delivery
|
||||
- Profile: standard
|
||||
- Batch: BAT-019
|
||||
- Discovery: dcy-009
|
||||
- Created: 2026-06-27
|
||||
|
||||
## Included REQ
|
||||
|
||||
- REQ-044: GET /v1/assets 권한 완화 (require_admin → require_read)
|
||||
- REQ-045: 사용자 가용 데몬 목록 엔드포인트 신규 (GET /v1/daemons)
|
||||
|
||||
## Scope Summary
|
||||
|
||||
GET /v1/assets 엔드포인트의 인증 의존성을 `require_admin`에서 `require_read`로 완화한다.
|
||||
신규로 GET /v1/daemons 엔드포인트를 추가해 caller 소유 데몬 목록을 반환한다(require_read).
|
||||
asset 쓰기 엔드포인트는 require_admin을 유지한다.
|
||||
|
||||
## Folder
|
||||
|
||||
[bat-019](./bat-019_20260627_assets-read-permission-and-daemon-list/)
|
||||
@@ -0,0 +1,62 @@
|
||||
# BAT-019 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-019
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
|
||||
- REQ-044: GET /v1/assets 권한 완화 (require_admin → require_read)
|
||||
- REQ-045: 사용자 가용 데몬 목록 엔드포인트 신규 (GET /v1/daemons)
|
||||
|
||||
## Delivery Plan
|
||||
|
||||
### REQ-044 순서
|
||||
|
||||
1. `app/api/v1_assets.py`의 `list_catalog_assets` 의존성을 `require_read`로 교체.
|
||||
2. 나머지 쓰기 엔드포인트(POST/PATCH/DELETE)는 `require_admin` 유지.
|
||||
3. 기존 test_asset_catalog.py의 `test_get_v1_assets_user_token_returns_403`,
|
||||
`test_get_v1_assets_service_token_returns_403` 등 403 기대 테스트를
|
||||
200/403 신규 기대치로 업데이트.
|
||||
|
||||
### REQ-045 순서
|
||||
|
||||
1. `app/schemas/v1.py`에 `DaemonResponse`, `DaemonListResponse` 스키마 추가.
|
||||
2. `app/api/v1_daemons.py` 신규 생성: GET /v1/daemons 라우터.
|
||||
3. `app/main.py`에 v1_daemons 라우터 include_router 등록.
|
||||
4. `tests/test_v1_daemons.py` 신규 생성: AC 전체 커버.
|
||||
|
||||
### 구현 순서
|
||||
|
||||
REQ-044(단순 1줄 변경) → REQ-045(스키마 → 라우터 → 등록 → 테스트) → 테스트 전체 실행
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Internal:
|
||||
- `app/core/auth.py`: `require_read` 의존성 (이미 존재)
|
||||
- `app/domain/models.py`: `Daemon` 도메인 모델 (이미 존재, line 66)
|
||||
- `app/services/auth_service.py`: `find_candidate_daemons_for_user` (이미 존재, line 161)
|
||||
- `app/repositories/portfolio_repository.py`: `list_daemons_for_user` (이미 프로토콜 선언됨, line 103)
|
||||
- External:
|
||||
- 없음
|
||||
|
||||
## Design Gate
|
||||
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: REQ-045 신규 엔드포인트이므로 경로 컨벤션, 응답 래퍼 스키마, env/service 토큰(user_id=None) 처리, active 필터 여부를 design에서 확정해야 안전하게 구현할 수 있다. REQ-044는 단순 교체지만 같은 batch이므로 design에 함께 기록한다.
|
||||
|
||||
## Milestones
|
||||
|
||||
1. BAT-019 batch 폴더 및 문서 생성 완료
|
||||
2. Planning 확정
|
||||
3. Design 확정 (경로·스키마·env토큰 처리)
|
||||
4. REQ-044 구현 + 테스트 조정
|
||||
5. REQ-045 구현 + 신규 테스트
|
||||
6. pytest 전체 통과
|
||||
7. Verification 완료
|
||||
|
||||
## Risks
|
||||
|
||||
- REQ-044: 기존 테스트 중 `require_admin` 기대 403 케이스가 다수 있어 신규 200 기대로 업데이트 필요. 누락 시 기존 테스트 깨짐.
|
||||
- REQ-045: `user_id=None`(env/service 토큰) 처리를 design에서 명확히 결정해야 구현이 일관됨.
|
||||
- REQ-045: `AuthService.find_candidate_daemons_for_user`가 `list_daemons_for_user`를 호출하는데, InMemoryRepository 미구현 시 서비스 레이어를 직접 mock해야 함.
|
||||
Reference in New Issue
Block a user