Release: REL-003_20260512 — CLI user scoping and local DB ops runbook Requirements & Batches: - BAT-003 (REQ-013): portfolio list --user required enforcement with owner filtering - BAT-004 (REQ-014): portfolio create --user required enforcement with owner assignment - BAT-007 (REQ-001/002/003): local DB validation runbook and baseline docs update CLI Enhancements: - Update user.py: add username-based user lookup (get_by_username) - Add UserDetailResponse schema with service token listing - Update list_service_tokens API in user_service.py - Enhance user detail display with service token information - Add service_token_count and service_token_names to user responses Testing: - Add comprehensive test coverage in test_cli.py for user commands - Validate username-based lookup functionality Release: REL-004_20260512 — Portfolio UUID migration and API UUID switch Requirements & Batches: - BAT-006 (REQ-016/017/018): portfolio UUID migration complete - BAT-008 (REQ-019): baseline documentation update for UUID migration Breaking Changes (REL-003): - portfolio list requires --user <username> parameter - portfolio create requires --user <username> parameter Documentation: - Create rel-003 and rel-004 release documents with rollout and rollback plans - Update docs/releases/index.md with new releases - Update README.md with CLI user scoping clarification - Sync all release verification checklists and deployment notes
346 lines
11 KiB
Markdown
346 lines
11 KiB
Markdown
# Portfolio Hub
|
|
|
|
Portfolio Hub is a FastAPI service that extracts portfolio management from `jsbot-kis` and exposes it through an authenticated HTTP API.
|
|
|
|
## Current status
|
|
|
|
This repository is in bootstrap phase.
|
|
|
|
- FastAPI application entrypoint is available.
|
|
- Token-based authentication and role checks are implemented.
|
|
- Portfolio and user management API contracts are implemented.
|
|
- Core portfolio domain services are ported from `jsbot-kis`.
|
|
- Postgres-backed portfolio and user persistence is implemented and gated behind `pfhub_DB_ENABLED=true`.
|
|
|
|
## Configuration
|
|
|
|
Copy `.env.example` to `.env` and edit values as needed:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
The application loads configuration in this order (higher priority first):
|
|
1. Environment variables
|
|
2. Variables from `.env` file
|
|
3. Default values in code
|
|
|
|
When deploying to production, set environment variables directly instead of using `.env`.
|
|
|
|
### Key configuration options
|
|
|
|
- **DB repository switch**: `pfhub_DB_ENABLED` (enables DB-backed persistence)
|
|
- **Initial user bootstrap**: `pfhub_BOOTSTRAP_USERNAME`, `pfhub_BOOTSTRAP_EMAIL`
|
|
- **Initial user password**: `pfhub_BOOTSTRAP_PASSWORD`
|
|
- **Session/token TTL**: `pfhub_SESSION_TOKEN_TTL_HOURS`, `pfhub_CLIENT_TOKEN_DEFAULT_TTL_DAYS`, `pfhub_SERVICE_TOKEN_DEFAULT_TTL_DAYS`
|
|
- **TimescaleDB / PostgreSQL**: `TIMESCALEDB_*` (single source of DB connection settings)
|
|
- **Redis**: `REDIS_*` (for caching and sessions)
|
|
- **Loki**: `LOKI_*` (for centralized logging)
|
|
- **Authentication**: `pfhub_*_TOKENS` (bearer tokens with role-based access)
|
|
|
|
For complete configuration options, see `.env.example`.
|
|
|
|
## Run locally
|
|
|
|
```bash
|
|
python -m venv .venv
|
|
source .venv/bin/activate
|
|
python -m pip install -e '.[dev]'
|
|
|
|
# Configure .env
|
|
cp .env.example .env
|
|
|
|
# Start server
|
|
uvicorn app.main:app --reload
|
|
|
|
# In another terminal, run tests
|
|
pytest
|
|
```
|
|
|
|
If you need local infrastructure for TimescaleDB, Redis, and Loki, start them with Docker Compose before running the app:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.local-test.yml up -d
|
|
docker compose -f docker-compose.local-test.yml ps
|
|
```
|
|
|
|
Default endpoints exposed by `docker-compose.local-test.yml`:
|
|
|
|
- TimescaleDB: `127.0.0.1:5432`
|
|
- Redis: `127.0.0.1:6379` (RedisInsight UI: `127.0.0.1:8001`)
|
|
- Loki: `127.0.0.1:3100`
|
|
|
|
Typical `.env` values for this local setup:
|
|
|
|
```env
|
|
pfhub_DB_ENABLED=true
|
|
TIMESCALEDB_HOST=127.0.0.1
|
|
TIMESCALEDB_PORT=5432
|
|
TIMESCALEDB_USER=portfolio_hub_user
|
|
TIMESCALEDB_PASSWORD=change-me
|
|
TIMESCALEDB_NAME=portfolio_hub
|
|
|
|
REDIS_HOST=127.0.0.1
|
|
REDIS_PORT=6379
|
|
|
|
LOKI_HOST=127.0.0.1
|
|
LOKI_PORT=3100
|
|
```
|
|
|
|
To stop and remove the local containers:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.local-test.yml down
|
|
```
|
|
|
|
## CLI
|
|
|
|
The repository now includes a `pfhub` CLI that mirrors the main portfolio flows from `jsbot-kis`.
|
|
The same commands are also available through the shorter alias `phub`.
|
|
|
|
```bash
|
|
pfhub --help
|
|
pfhub user list
|
|
pfhub user create --username alice --password password123 --email alice@example.com --force
|
|
pfhub user get <username>
|
|
pfhub user update <user-id> --username alice-updated --force
|
|
pfhub user delete <user-id> --force
|
|
pfhub service-token issue nightly --user alice --ttl-days 30
|
|
pfhub service-token issue permanent --user alice --no-expiry
|
|
pfhub service-token revoke nightly --force
|
|
pfhub portfolio list --user <username>
|
|
pfhub portfolio status 005930:tv --recent 10
|
|
pfhub portfolio create 005930:tv --symbol 005930 --user <username> --cash-balance 10000000 --force
|
|
pfhub portfolio buy 005930:tv --quantity 10 --price 71000 --fee 5000 --force
|
|
pfhub portfolio sell 005930:tv --quantity 5 --price 72000 --tax 3000 --force
|
|
pfhub portfolio invest 005930:tv --type deposit --amount 50000 --force
|
|
pfhub portfolio transfer 005930:tv --direction to-buffer --amount 100000 --force
|
|
pfhub portfolio reset 005930:tv --force
|
|
pfhub portfolio delete 005930:tv --keep-ledger --force
|
|
```
|
|
|
|
Supported commands:
|
|
|
|
- `init-db`
|
|
- `migrate-db`
|
|
- `user list`
|
|
- `user get`
|
|
- `user create`
|
|
- `user update`
|
|
- `user delete`
|
|
- `service-token issue`
|
|
- `service-token revoke`
|
|
- `portfolio list`
|
|
- `portfolio status`
|
|
- `portfolio create`
|
|
- `portfolio update`
|
|
- `portfolio buy`
|
|
- `portfolio sell`
|
|
- `portfolio invest`
|
|
- `portfolio transfer`
|
|
- `portfolio reset`
|
|
- `portfolio delete`
|
|
- `health-check`
|
|
|
|
Write commands ask for confirmation by default. Use `--force` to skip the prompt for scripts and automation.
|
|
|
|
Service token issue policy:
|
|
|
|
- 기본값은 365일 TTL이다.
|
|
- `--ttl-days`와 `--no-expiry`는 동시에 사용할 수 없다.
|
|
- `--no-expiry`는 관리자 권한에서만 허용된다.
|
|
- 토큰 원문은 발급 응답 1회 이후 마스킹된 값으로만 출력/기록된다.
|
|
|
|
The CLI uses the same repository and settings layer as the HTTP API. To target the external TimescaleDB, set `pfhub_DB_ENABLED=true` and provide `TIMESCALEDB_*` values in the environment or `.env`.
|
|
|
|
Initialize a new database explicitly before the first use:
|
|
|
|
```bash
|
|
pfhub_DB_ENABLED=true pfhub init-db
|
|
pfhub_DB_ENABLED=true pfhub health-check
|
|
pfhub_DB_ENABLED=true pfhub portfolio list
|
|
```
|
|
|
|
`init-db` will:
|
|
|
|
- connect to the maintenance database, defaulting to `postgres`
|
|
- create the target database if it does not exist
|
|
- run Alembic `upgrade head`
|
|
|
|
`migrate-db` will:
|
|
|
|
- connect to the target database
|
|
- run Alembic `upgrade head`
|
|
- apply any future schema revisions in order
|
|
|
|
This repository now uses Alembic for versioned schema migrations. Future schema changes should be added under `migrations/versions`.
|
|
|
|
## Local DB Validation Runbook
|
|
|
|
이 섹션은 로컬 docker compose 환경에서 DB 초기화, 마이그레이션, health-check, smoke flow를 반복 검증하기 위한 절차를 정의한다.
|
|
|
|
### 준비 — 컨테이너 시작
|
|
|
|
```bash
|
|
docker compose -f docker-compose.local-test.yml up -d
|
|
docker compose -f docker-compose.local-test.yml ps
|
|
# portfolio-hub-timescaledb 상태가 (healthy) 인지 확인
|
|
```
|
|
|
|
기본 접속 정보 (`.env` 설정 기준):
|
|
- TimescaleDB: `127.0.0.1:5432`
|
|
- DB: `portfolio_hub`, User: `portfolio_hub_user`
|
|
|
|
### Scenario A — Fresh DB (DB 미존재 상태)
|
|
|
|
```bash
|
|
# 1. DB 생성 + Alembic upgrade head
|
|
pfhub_DB_ENABLED=true pfhub init-db
|
|
# 기대: database: portfolio_hub / created: True / schema_initialized: True / migration_applied: head
|
|
|
|
# 2. DB 연결 health 확인
|
|
pfhub_DB_ENABLED=true pfhub health-check
|
|
# 기대: db_enabled: True / repository: PostgresPortfolioRepository
|
|
|
|
# 3. Smoke flow — 사용자 생성 + portfolio 생성 + 목록 확인
|
|
pfhub_DB_ENABLED=true pfhub user create --username alice --password password123 --force
|
|
pfhub_DB_ENABLED=true pfhub portfolio create my-portfolio --symbol 005930 --user alice --cash-balance 1000000 --force
|
|
pfhub_DB_ENABLED=true pfhub portfolio list --user alice
|
|
# 기대: my-portfolio가 목록에 포함
|
|
```
|
|
|
|
**pass 기준**: 각 명령 exit code 0 / 기대 필드 출력 확인
|
|
**fail 기준**: connection refused / Alembic revision 오류 / exit code ≠ 0
|
|
|
|
### Scenario B — runtime-precreated schema (App 선행 기동)
|
|
|
|
```bash
|
|
# 1. App 먼저 기동 (ensure_schema() 실행)
|
|
pfhub_DB_ENABLED=true uvicorn app.main:app &
|
|
|
|
# 2. migrate-db로 Alembic 관리 상태로 전환
|
|
pfhub_DB_ENABLED=true pfhub migrate-db
|
|
# 기대: migration_applied: head
|
|
|
|
# 3. health-check 재확인
|
|
pfhub_DB_ENABLED=true pfhub health-check
|
|
```
|
|
|
|
**동작 원리**: `bootstrap_or_upgrade_to_head()`가 `alembic_version` 테이블 없음 → 기존 핵심 테이블 존재 → legacy stamp → head upgrade 순서로 실행한다.
|
|
|
|
### 반복 실행 (재현성 확인)
|
|
|
|
```bash
|
|
# 컨테이너 + 볼륨 초기화
|
|
docker compose -f docker-compose.local-test.yml down -v
|
|
|
|
# 위 Scenario A 또는 B 재실행
|
|
```
|
|
|
|
출력이 이전 실행과 동일한지 비교한다. JSON 출력 수집:
|
|
|
|
```bash
|
|
pfhub_DB_ENABLED=true pfhub init-db --output json
|
|
pfhub_DB_ENABLED=true pfhub health-check --output json
|
|
```
|
|
|
|
로그 수집 위치: `docker compose -f docker-compose.local-test.yml logs portfolio-hub-timescaledb`
|
|
|
|
### Rollback / 재시도 판단 기준
|
|
|
|
| 증상 | 판단 | 조치 |
|
|
|------|------|------|
|
|
| `init-db`: `connection refused` | DB 컨테이너 미준비 | `docker compose ps` → `(healthy)` 대기 후 재시도 |
|
|
| `migrate-db`: `Alembic revision 오류` | revision 파일 손상 또는 순서 오류 | `migrations/versions/` revision 확인 |
|
|
| `migrate-db` 후 v1 API 오류 | ensure_schema() 선행 후 Alembic 미실행 | `migrate-db` 재실행 |
|
|
| 데이터 오염으로 스키마 불일치 | 심각한 데이터 오류 | 볼륨 초기화(`down -v`) + Scenario A 재시작 |
|
|
|
|
### Release Gate Criteria
|
|
|
|
다음 조건을 모두 만족해야 로컬 검증 완료로 간주한다:
|
|
|
|
1. Scenario A: `init-db` exit code 0 + `health-check` exit code 0 + smoke flow 정상 완료
|
|
2. `migrate-db` 반복 실행 시 오류 없음 (멱등성 확인)
|
|
3. `docker compose logs`에 ERROR 레벨 로그 없음
|
|
|
|
## Docs Sync Guardrail
|
|
|
|
To keep implementation and specification documents in sync, this repository provides an automated check.
|
|
|
|
Rules enforced:
|
|
|
|
- If files under `app/` change, at least one `docs/technical/` document must be updated in the same commit/PR.
|
|
- If files under `app/api/` or `app/schemas/` change, `docs/technical/api-contracts.md` must be updated in the same commit/PR.
|
|
- If files under `migrations/versions/` change, `docs/technical/persistence-and-migrations.md` must be updated in the same commit/PR.
|
|
- Changed migration revision IDs must appear in `docs/technical/persistence-and-migrations.md`.
|
|
|
|
Enable local pre-commit check:
|
|
|
|
```bash
|
|
pip install -e .[dev]
|
|
pre-commit install
|
|
```
|
|
|
|
Run manually:
|
|
|
|
```bash
|
|
python3 scripts/check_docs_sync.py --staged
|
|
```
|
|
|
|
CI check:
|
|
|
|
- GitHub Actions workflow: `.github/workflows/docs-sync-check.yml`
|
|
- Runs automatically on pull requests.
|
|
|
|
## Authentication model
|
|
|
|
pfhub currently supports bearer tokens with three permission levels.
|
|
|
|
- `read`: list and status endpoints
|
|
- `trade-write`: mutation endpoints such as buy, sell, invest, transfer, reset
|
|
- `admin`: delete, user management, and future operational actions
|
|
|
|
## User management
|
|
|
|
User management is exposed through both HTTP API and CLI.
|
|
|
|
- API base route: `/users`
|
|
- Permission: `admin` only
|
|
- Supported operations: list, get, create, update, delete
|
|
|
|
Frontend and app authentication is exposed through `/auth`.
|
|
|
|
- `POST /auth/login`: username/password login, returns bearer session token
|
|
- `GET /auth/tokens`: list current user's issued tokens
|
|
- `POST /auth/tokens`: issue a client token for app/script usage
|
|
- `DELETE /auth/tokens/{token_id}`: revoke a previously issued token
|
|
|
|
Service tokens (long-lived integration tokens) are managed via CLI:
|
|
|
|
```bash
|
|
pfhub service-token issue <name> --user <username> [--permission read|trade-write] [--ttl-days N | --no-expiry]
|
|
pfhub service-token revoke <name> [--force]
|
|
```
|
|
|
|
Example API calls:
|
|
|
|
```bash
|
|
curl -X GET "http://localhost:8000/users" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
|
|
|
curl -X POST "http://localhost:8000/users" \
|
|
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username": "alice", "email": "alice@example.com"}'
|
|
```
|
|
|
|
The default `.env.example` includes example tokens for development.
|
|
|
|
When `pfhub_DB_ENABLED=true` and both `pfhub_BOOTSTRAP_USERNAME` and `pfhub_BOOTSTRAP_PASSWORD` are set, the app creates that admin user automatically on startup only if the `users` table is empty. This is intended for first-run bootstrap, not recurring synchronization.
|
|
|
|
## Planned next steps
|
|
|
|
1. Apply and verify Alembic migrations against a real PostgreSQL/Timescale instance.
|
|
2. Add integration tests for DB-backed portfolio and user flows.
|
|
3. Design token-to-user mapping if ownership or user-scoped authorization is needed later.
|