feat(REQ-001): add portfolio comment history API, CLI, repository, migration, and tests
Agent-Logs-Url: https://github.com/genichin/portfolio-hub/sessions/64fc2cec-78f5-41d1-99a4-45f868781edd Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
4684597353
commit
e8f782bec6
@@ -259,6 +259,104 @@ Response 204:
|
||||
|
||||
- No content.
|
||||
|
||||
### 4.11 POST /portfolios/{portfolio_id}/comments
|
||||
|
||||
Create a new comment for a portfolio.
|
||||
|
||||
Required permission: `trade-write`
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/portfolios/krw-btc/comments" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "Bought more BTC today.\nLooking good."}'
|
||||
```
|
||||
|
||||
Response 201:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"portfolio_id": "krw-btc",
|
||||
"content": "Bought more BTC today.\nLooking good.",
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.12 GET /portfolios/{portfolio_id}/comments
|
||||
|
||||
List all comments for a portfolio, sorted by most recent first.
|
||||
|
||||
Required permission: `read`
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/portfolios/krw-btc/comments" \
|
||||
-H "Authorization: Bearer <READ_TOKEN>"
|
||||
```
|
||||
|
||||
Response 200:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"portfolio_id": "krw-btc",
|
||||
"content": "Bought more BTC today.\nLooking good.",
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 4.13 PATCH /portfolios/{portfolio_id}/comments/{comment_id}
|
||||
|
||||
Update an existing comment's content.
|
||||
|
||||
Required permission: `trade-write`
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X PATCH "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "Updated note: BTC rally continues."}'
|
||||
```
|
||||
|
||||
Response 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"portfolio_id": "krw-btc",
|
||||
"content": "Updated note: BTC rally continues.",
|
||||
"created_at": "2026-04-10T00:00:00Z",
|
||||
"updated_at": "2026-04-10T01:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.14 DELETE /portfolios/{portfolio_id}/comments/{comment_id}
|
||||
|
||||
Delete a comment.
|
||||
|
||||
Required permission: `trade-write`
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8000/portfolios/krw-btc/comments/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
|
||||
-H "Authorization: Bearer <TRADE_WRITE_TOKEN>"
|
||||
```
|
||||
|
||||
Response 204:
|
||||
|
||||
- No content.
|
||||
|
||||
## 5. Strategy Sets
|
||||
|
||||
### 5.1 GET /strategy-sets
|
||||
|
||||
+33
-2
@@ -5,7 +5,7 @@
|
||||
This document defines the database schema in an AI-agent-friendly format.
|
||||
|
||||
- Source of truth: Alembic revisions in `migrations/versions`
|
||||
- Current baseline revision: `20260403_0008`
|
||||
- Current baseline revision: `20260410_0009`
|
||||
- Baseline files:
|
||||
- `migrations/versions/20260403_0001_initial_portfolio_schema.py`
|
||||
- `migrations/versions/20260403_0002_strategy_set_columns.py`
|
||||
@@ -15,6 +15,7 @@ This document defines the database schema in an AI-agent-friendly format.
|
||||
- `migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py`
|
||||
- `migrations/versions/20260403_0007_bot_principal_permission_only.py`
|
||||
- `migrations/versions/20260403_0008_portfolio_name_column.py`
|
||||
- `migrations/versions/20260410_0009_portfolio_comments_table.py`
|
||||
|
||||
## 2. Scope
|
||||
|
||||
@@ -26,6 +27,7 @@ Schema objects covered by baseline revision:
|
||||
- `bot_principals`
|
||||
- `portfolios`
|
||||
- `portfolio_ledger`
|
||||
- `portfolio_comments`
|
||||
- secondary indexes
|
||||
- Timescale hypertable conversion rule for `portfolio_ledger`
|
||||
|
||||
@@ -206,6 +208,29 @@ Constraints:
|
||||
- Primary key: none in baseline
|
||||
- Foreign key constraints: none in baseline
|
||||
|
||||
### 4.6 portfolio_comments
|
||||
|
||||
Purpose:
|
||||
|
||||
- Stores user-authored comment history per portfolio for memo/annotation tracking.
|
||||
- Supports multiline text content.
|
||||
- Comments are isolated per portfolio and listed in reverse chronological order.
|
||||
|
||||
Columns:
|
||||
|
||||
| Column | Type | Null | Default | Constraints / Notes |
|
||||
|---|---|---|---|---|
|
||||
| id | Text | NO | - | Primary Key (UUID) |
|
||||
| portfolio_id | Text | NO | - | Logical reference to `portfolios.portfolio_id` (no FK) |
|
||||
| content | Text | NO | - | Comment text; supports multiline |
|
||||
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
||||
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
||||
|
||||
Constraints:
|
||||
|
||||
- Primary key: `id`
|
||||
- Foreign key constraints: none
|
||||
|
||||
## 5. Indexes
|
||||
|
||||
### 5.1 ix_users_created_at
|
||||
@@ -257,7 +282,12 @@ Constraints:
|
||||
- Unique: no
|
||||
- Intent: speed list/filter queries by broker/status and recency.
|
||||
|
||||
## 6. TimescaleDB Behavior
|
||||
### 5.8 idx_portfolio_comments_portfolio_created
|
||||
|
||||
- Table: `portfolio_comments`
|
||||
- Columns/expressions: (`portfolio_id`, `created_at DESC`)
|
||||
- Unique: no
|
||||
- Intent: fetch comments per portfolio efficiently in reverse chronological order.
|
||||
|
||||
Conditional behavior in migration upgrade:
|
||||
|
||||
@@ -297,3 +327,4 @@ When schema changes:
|
||||
| 20260403_0006 | migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py | Add `bot_principals` table and `auth_tokens.bot_principal_id` for bot API key management |
|
||||
| 20260403_0007 | migrations/versions/20260403_0007_bot_principal_permission_only.py | Remove `bot_principals.owner_user_id` and enforce bot-local `permission` model |
|
||||
| 20260403_0008 | migrations/versions/20260403_0008_portfolio_name_column.py | Add non-null `portfolios.name` (asset display name), backfilled from `symbol` |
|
||||
| 20260410_0009 | migrations/versions/20260410_0009_portfolio_comments_table.py | Add `portfolio_comments` table for per-portfolio memo/comment history |
|
||||
|
||||
@@ -217,6 +217,36 @@ Base routes:
|
||||
- `keep_ledger` (bool, default `false`)
|
||||
- Response `204`: no content
|
||||
|
||||
#### Create Comment
|
||||
|
||||
- `POST /portfolios/{portfolio_id}/comments`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CommentCreateRequest` (`content: str`)
|
||||
- Response `201`: `CommentResponse`
|
||||
- Response `404`: portfolio not found
|
||||
|
||||
#### List Comments
|
||||
|
||||
- `GET /portfolios/{portfolio_id}/comments`
|
||||
- Auth: `read`
|
||||
- Response `200`: array of `CommentResponse`, sorted by `created_at DESC`
|
||||
- Response `404`: portfolio not found
|
||||
|
||||
#### Update Comment
|
||||
|
||||
- `PATCH /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
- Auth: `trade-write`
|
||||
- Body: `CommentUpdateRequest` (`content: str`)
|
||||
- Response `200`: `CommentResponse`
|
||||
- Response `404`: portfolio or comment not found
|
||||
|
||||
#### Delete Comment
|
||||
|
||||
- `DELETE /portfolios/{portfolio_id}/comments/{comment_id}`
|
||||
- Auth: `trade-write`
|
||||
- Response `204`: no content
|
||||
- Response `404`: portfolio or comment not found
|
||||
|
||||
### 7.3 Strategy Set Endpoints
|
||||
|
||||
#### List Strategy Sets
|
||||
@@ -473,6 +503,14 @@ Bootstrap rule:
|
||||
|
||||
- `infer_symbol: str | null`
|
||||
|
||||
`CommentCreateRequest`
|
||||
|
||||
- `content: str`
|
||||
|
||||
`CommentUpdateRequest`
|
||||
|
||||
- `content: str`
|
||||
|
||||
`CreateStrategySetRequest`
|
||||
|
||||
- `name: str`
|
||||
@@ -529,6 +567,14 @@ Bootstrap rule:
|
||||
|
||||
- `detail: str`
|
||||
|
||||
`CommentResponse`
|
||||
|
||||
- `id: str`
|
||||
- `portfolio_id: str`
|
||||
- `content: str`
|
||||
- `created_at: datetime | null`
|
||||
- `updated_at: datetime | null`
|
||||
|
||||
`BotPrincipalResponse`
|
||||
|
||||
- `id: str`
|
||||
@@ -565,6 +611,13 @@ Portfolio subcommands:
|
||||
- `portfoliohub portfolio reset <portfolio_id> [--infer-symbol ...] [--force]`
|
||||
- `portfoliohub portfolio delete <portfolio_id> [--keep-ledger] [--force]`
|
||||
|
||||
Portfolio comment subcommands:
|
||||
|
||||
- `portfoliohub portfolio comment add <portfolio_id> --content <text>`
|
||||
- `portfoliohub portfolio comment list <portfolio_id>`
|
||||
- `portfoliohub portfolio comment update <portfolio_id> <comment_id> --content <text> [--force]`
|
||||
- `portfoliohub portfolio comment delete <portfolio_id> <comment_id> [--force]`
|
||||
|
||||
Strategy subcommands:
|
||||
|
||||
- `portfoliohub strategy list`
|
||||
|
||||
Reference in New Issue
Block a user