Introduces discovery for real DB migration validation and operations readiness: - dcy-001_20260430_real-db-migration-validation-and-operations-readiness.md - REQ-001: Local DB validation scenarios (Testing) - REQ-002: Alembic runtime schema ownership (Migration) - REQ-003: Local DB operations runbook and baseline updates (Documentation) Updates discovery and SRS indexes to include new documents.
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):
- Environment variables
- Variables from
.envfile - 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 <user-id>
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
pfhub portfolio status 005930:tv --recent 10
pfhub portfolio create 005930:tv --symbol 005930 --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-dbmigrate-dbuser listuser getuser createuser updateuser deleteservice-token issueservice-token revokeportfolio listportfolio statusportfolio createportfolio updateportfolio buyportfolio sellportfolio investportfolio transferportfolio resetportfolio deletehealth-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.
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 onedocs/technical/document must be updated in the same commit/PR. - If files under
app/api/orapp/schemas/change,docs/technical/api-contracts.mdmust be updated in the same commit/PR. - If files under
migrations/versions/change,docs/technical/persistence-and-migrations.mdmust 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 endpointstrade-write: mutation endpoints such as buy, sell, invest, transfer, resetadmin: delete, user management, and future operational actions
User management
User management is exposed through both HTTP API and CLI.
- API base route:
/users - Permission:
adminonly - Supported operations: list, get, create, update, delete
Frontend and app authentication is exposed through /auth.
POST /auth/login: username/password login, returns bearer session tokenGET /auth/tokens: list current user's issued tokensPOST /auth/tokens: issue a client token for app/script usageDELETE /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
- Apply and verify Alembic migrations against a real PostgreSQL/Timescale instance.
- Add integration tests for DB-backed portfolio and user flows.
- Design token-to-user mapping if ownership or user-scoped authorization is needed later.