feat: asset catalog API, 태그 DB 기초, 마이그레이션 3건 및 도메인/테스트 갱신 (2026-05-19)

This commit is contained in:
2026-05-19 22:15:55 +00:00
parent e2226e28b6
commit 158929b2d9
33 changed files with 2575 additions and 102 deletions
+52 -9
View File
@@ -101,22 +101,31 @@
### Authentication
- All /v1 endpoints require `Authorization: Bearer <service-token>` header.
- Token must be a DB-issued service token (`token_source="db"`, `token_type="service"`); env tokens are rejected with HTTP 403.
- All /v1 endpoints require `Authorization: Bearer <token>` header.
- Token must be DB-issued (`token_source="db"`); env tokens are rejected with HTTP 403.
- Admin catalog endpoints (`POST /v1/assets`, `PATCH /v1/assets/{id}`, `PATCH /v1/assets/{id}/deactivate`) require admin permission.
- POST /v1/assets/{asset_id}/ledger/trades additionally requires `trade-write` permission.
### Token Type Branching
- `token_type="service"`: daemon-scoped endpoints. `daemon_id` must be non-NULL (NULL → HTTP 403 fail-closed).
- `token_type="user"`: user-scoped. Endpoints return owner's own data.
- `token_type="admin"`: admin-scoped. May query other users' data via `?user_id=<id>` param.
### Daemon Authorization Model
- Service tokens are bound to a daemon at issuance via `daemon_id` field. Legacy/unbound tokens have `daemon_id=NULL`.
- GET /v1/assets and POST /v1/assets/{asset_id}/ledger/trades are daemon-scoped. NULL token `daemon_id` → HTTP 403 (fail-closed).
- GET /v1/assets (service token) and POST /v1/assets/{asset_id}/ledger/trades are daemon-scoped. NULL token `daemon_id` → HTTP 403 (fail-closed).
- GET /v1/user_info is available to any service token; returns `daemon_id: null` as diagnostic for unbound tokens.
- Each asset (portfolio) may be assigned to exactly one daemon via `portfolios.managed_by_daemon_id`. Mismatch or unassigned asset → HTTP 403 on ledger writes.
### Endpoint: `GET /v1/assets`
- Auth: service-token bearer (read); token must have `daemon_id` bound (NULL → HTTP 403)
- Query Params: `?status=<value>` (optional, filters by portfolio status)
- Filter: returns only assets where `portfolios.managed_by_daemon_id = token.daemon_id`
- Auth: DB token required; behavior branches by `token_type`
- `service`: returns portfolios where `managed_by_daemon_id = token.daemon_id`; NULL daemon_id → 403
- `user`: returns caller's own portfolios (`owner_user_id = token.user_id`)
- `admin`: returns caller's own portfolios; `?user_id=<id>` overrides to specified user
- Query Params: `?status=<value>` (optional portfolio status filter), `?market=<code>` (optional market code filter), `?user_id=<id>` (admin only)
- Response: HTTP 200
```
@@ -124,9 +133,8 @@
"total": <int>,
"assets": [
{
"asset_id": "<UUID>", // portfolio.portfolio_id (UUID after BAT-006)
"asset_id": "<UUID>", // portfolio.portfolio_id
"symbol": "<str>",
"market": "<str|null>",
"status": "<str>",
"cash": <float>,
"quantity": <float>,
@@ -147,7 +155,41 @@
}
```
- Error: HTTP 401 if no token; HTTP 403 if not a service token or daemon_id is NULL
- Error: HTTP 401 if no token; HTTP 403 if env token or (service token with daemon_id NULL)
### Endpoint: `POST /v1/assets` (Admin Catalog)
- Auth: admin token required
- Body: `{"symbol": "<str>", "markets": [<str>], "display_name": "<str|null>", "status": "active"}`
- Response: HTTP 201 `AssetCatalogResponse`
```
{
"asset_id": "<UUID>",
"symbol": "<str>",
"markets": [<str>],
"display_name": "<str|null>",
"status": "<str>",
"created_at": "<ISO8601|null>",
"updated_at": "<ISO8601|null>"
}
```
- Error: HTTP 403 if not admin
### Endpoint: `PATCH /v1/assets/{asset_id}` (Admin Catalog Update)
- Auth: admin token required
- Body: `{"markets": [<str>]|null, "display_name": "<str|null>", "status": "<str|null>"}`
- Response: HTTP 200 `AssetCatalogResponse`
- Error: HTTP 403 if not admin
### Endpoint: `PATCH /v1/assets/{asset_id}/deactivate` (Admin Catalog Deactivate)
- Auth: admin token required
- Body: none
- Response: HTTP 200 `AssetCatalogResponse` with `status="inactive"`
- Error: HTTP 403 if not admin
### Endpoint: `GET /v1/user_info`
@@ -252,6 +294,7 @@
## Change Log
- 2026-05-19 KST: bat-002 — REQ-010/012/013/015: added admin catalog endpoints (POST/PATCH /v1/assets, PATCH /v1/assets/{id}/deactivate); rewrote GET /v1/assets authentication to support service/user/admin token branching; added market filter param; removed market field from AssetItem; added AssetCatalogResponse schema.
- 2026-05-13 KST: bat-010 — REQ-006/007/008/011/023: added daemon authorization model section; updated GET /v1/assets (daemon-scoped filter, NULL 403); updated GET /v1/user_info (daemon_id field, NULL runtime_id → 200+null diagnostic); updated POST /v1/assets/{asset_id}/ledger/trades (daemon assignment authorization, fail-closed policy); added daemon CLI subcommand to CLI surface; updated v1 purpose and source discovery.
- 2026-05-11 KST: bat-008 — REQ-011 AC-1: expanded v1 Consumer API section with authentication contract, detailed request/response schemas for GET /v1/assets, GET /v1/user_info, POST /v1/assets/{asset_id}/ledger/trades; updated GET /health response schema (REQ-004); added idempotency contract.
- 2026-05-11 KST: bat-006 — Added v1 consumer API interface section (trex-basic); noted asset_id UUID change and simultaneous deployment constraint; updated CLI portfolio inputs to reflect name+user identification.