feat(req-002): implement portfolio tagging and tag filters

This commit is contained in:
2026-04-14 01:06:10 +09:00
parent 2dab17cbb1
commit a776ea3b1d
20 changed files with 1033 additions and 32 deletions
+41 -2
View File
@@ -54,6 +54,15 @@
- `POST /portfolios/{portfolio_id}/transfer`
- `POST /portfolios/{portfolio_id}/reset`
- `DELETE /portfolios/{portfolio_id}`
- `POST /portfolios/{portfolio_id}/tags`
- `GET /portfolios/{portfolio_id}/tags`
- `DELETE /portfolios/{portfolio_id}/tags`
REQ-002 정렬 메모:
- 포트폴리오 목록 조회는 반복 query parameter `includeTags`, `excludeTags`, `matchMode`를 지원한다.
- 태그 mutation은 기존 포트폴리오 리소스 하위 경로 `/portfolios/{portfolio_id}/tags`로 구현한다.
- CLI는 `portfolio tag add|list|remove` 및 `portfolio list --include-tag/--exclude-tag/--match-mode` 계약을 제공한다.
### 포트폴리오 댓글 (`REQ-001`)
- `POST /portfolios/{portfolio_id}/comments`
@@ -106,12 +115,17 @@
- Query:
- `broker: string | null`
- `status_filter: string | null`
- `includeTags: string[] | null`
- `excludeTags: string[] | null`
- `matchMode: "AND" | "OR" | null` (`includeTags`에만 적용)
- 응답 `200`: `PortfolioListItem[]`
- 오류:
- `400`: `matchMode`가 `AND` 또는 `OR`가 아님
예시:
```bash
curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled" \
curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled&includeTags=grade:A&excludeTags=blacklist:trade&matchMode=OR" \
-H "Authorization: Bearer <READ_TOKEN>"
```
@@ -123,6 +137,7 @@ curl -X GET "http://localhost:8000/portfolios?broker=upbit&status_filter=enabled
"status": "enabled",
"name": "Bitcoin",
"symbol": "KRW-BTC",
"tags": ["grade:A"],
"cash_balance": 1000000.0,
"asset_quantity": 0.1,
"price": 100000000.0,
@@ -157,6 +172,7 @@ curl -X GET "http://localhost:8000/portfolios/krw-btc?recent_trades=5" \
"status": "enabled",
"name": "Bitcoin",
"symbol": "KRW-BTC",
"tags": ["grade:A"],
"owner_user_id": "user-1",
"cash_balance": 1000000.0,
"asset_quantity": 0.1,
@@ -246,6 +262,16 @@ curl -X PATCH "http://localhost:8000/portfolios/krw-btc" \
- `POST /portfolios/{portfolio_id}/transfer`: `TransferRequest` -> `PortfolioDetailResponse`
- `POST /portfolios/{portfolio_id}/reset`: `ResetPortfolioRequest` -> `PortfolioDetailResponse`
- `DELETE /portfolios/{portfolio_id}`: admin only, query `keep_ledger: bool = false`, 응답 `204`
- `POST /portfolios/{portfolio_id}/tags`: `PortfolioTagMutationRequest` -> `PortfolioTagResponse`
- `GET /portfolios/{portfolio_id}/tags`: `PortfolioTagResponse[]`
- `DELETE /portfolios/{portfolio_id}/tags`: body `PortfolioTagMutationRequest`, 응답 `204`
태그 엔드포인트 추가 규칙:
- `POST`/`DELETE`는 `trade-write`, `GET`은 `read`
- 비관리자 요청은 기존 ownership 검사를 동일하게 적용
- 입력 `tag`는 앞뒤 공백을 제거한 값을 저장/비교 기준으로 사용
- 정규화 후 빈 문자열은 `422`, 중복 태그 추가는 `409`, 포트폴리오 또는 태그 미존재는 `404`
대표 요청:
@@ -277,6 +303,19 @@ curl -X POST "http://localhost:8000/portfolios/krw-btc/reset" \
curl -X DELETE "http://localhost:8000/portfolios/krw-btc?keep_ledger=false" \
-H "Authorization: Bearer <ADMIN_TOKEN>"
curl -X POST "http://localhost:8000/portfolios/krw-btc/tags" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"tag": " grade:A "}'
curl -X GET "http://localhost:8000/portfolios/krw-btc/tags" \
-H "Authorization: Bearer <READ_TOKEN>"
curl -X DELETE "http://localhost:8000/portfolios/krw-btc/tags" \
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"tag": "grade:A"}'
```
#### 포트폴리오 댓글 (`REQ-001`)
@@ -703,7 +742,7 @@ curl -X POST "http://localhost:8000/users" \
## 문서 동기화 규칙
- `app/api/` 또는 `app/schemas/` 변경 시 본 문서를 같은 변경 단위에서 갱신한다.
- `REQ-002` 태그 검색 API가 구현되면 요청/응답 파라미터와 필터 의미를 본 문서에 추가한다.
- `REQ-002` 태그 검색 구현은 승인된 정규화/필터 의미와 실제 route/CLI 계약을 모두 본 문서에 유지한다.
## Open Implementation Points
- 에러 응답 body를 공통 포맷(`error_code`, `message`, `details`)으로 강제할지 결정한다.