genichin 7bcf5cbb2b docs: update CLI documentation to use username instead of user-id
CLI Changes:
- Update user.get command to accept username parameter instead of user-id
- Update user.update command signature in documentation
- Update user.delete command signature in documentation

Implementation Consistency:
- Align README.md CLI examples with app/cli/commands/user.py implementation
- Clarify username-based user identification across all user management commands
- Update test expectations to match username-based interface

Documentation:
- README.md: Replace <user-id> with <username> in all user command examples
- Ensure consistency across user create, get, update, delete commands
2026-05-12 12:26:06 +00:00

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:

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

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:

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:

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:

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.

pfhub --help
pfhub user list
pfhub user create --username alice --password password123 --email alice@example.com --force
pfhub user get <username>
pfhub user update <username> --username alice-updated --force
pfhub user delete <username> --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:

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를 반복 검증하기 위한 절차를 정의한다.

준비 — 컨테이너 시작

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 미존재 상태)

# 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 선행 기동)

# 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 순서로 실행한다.

반복 실행 (재현성 확인)

# 컨테이너 + 볼륨 초기화
docker compose -f docker-compose.local-test.yml down -v

# 위 Scenario A 또는 B 재실행

출력이 이전 실행과 동일한지 비교한다. JSON 출력 수집:

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:

pip install -e .[dev]
pre-commit install

Run manually:

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:

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:

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.
S
Description
No description provided
Readme
2.7 MiB
Languages
Python 99.9%
Dockerfile 0.1%