feat: implement portfolio UUID migration and v1 API finalization (BAT-006, BAT-001 re-verified)
Core Changes: - Migrate portfolio_id from string to UUID in schema and repository layer - Update portfolio CLI commands to use name+user combination for identification - Update HTTP API to accept portfolio_id as UUID path parameter - Implement portfolio ownership validation in repository queries Requirements Completed (REQ-016 to REQ-019): - REQ-016: portfolios table UUID schema migration with data migration script - REQ-017: CLI portfolio commands with name+user identification - REQ-018: HTTP API portfolio path parameters and v1 asset_id UUID returns - REQ-019: Portfolio UUID baseline documentation updates BAT-001 Re-verification: - REQ-004~010 marked as Implemented after verification evidence validation - All v1 API endpoints verified with comprehensive test coverage Documentation Updates: - Update data-model.md with portfolio UUID structure - Update interface-contract.md with UUID path parameters - Update runtime-flows.md with ownership-based resolution flows - Update project-structure.md with database layer changes - Add migration script for portfolio UUID transition Testing: - Add test_portfolio_uuid.py for migration validation - Update test_cli.py and test_portfolio_ownership.py for ownership checks
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# BAT-006 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-006
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
`portfolios.portfolio_id`를 사용자 지정 TEXT PK에서 서버 생성 UUID(TEXT)로 교체한다. 기존 `portfolio_id` 문자열은 `name` 컬럼으로 이전하고, `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. CLI는 `<name>` + `--user` 조합으로 portfolio를 식별하고 내부에서 `get_portfolio_by_name_and_user()`를 통해 UUID를 조회한다. HTTP API path parameter와 `GET /v1/assets` `asset_id` 반환은 UUID 기반으로 전환된다.
|
||||
|
||||
## Baseline vs Batch 변경점
|
||||
|
||||
### docs/project-structure.md (현재 baseline)
|
||||
- `app/repositories/postgres.py`: SQL 세부사항 담당으로만 기술 — `get_portfolio_by_name_and_user()` 미기재
|
||||
- `app/domain/models.py`: `portfolio_id` 필드 의미 변경 미기재
|
||||
|
||||
### docs/runtime-flows.md (현재 baseline)
|
||||
- `CLI Operational Command Execution` flow: name+user 식별 방식 미기재
|
||||
- `v1 API — GET /v1/assets` flow: `asset_id` 필드 언급 없음
|
||||
|
||||
### docs/data-model.md (현재 baseline)
|
||||
- `portfolio_id :: text :: stable portfolio primary key` — UUID 의미 변경 미기재
|
||||
- `name` 컬럼 별도 기재 없음
|
||||
- `(owner_user_id, name)` UNIQUE 제약 미기재
|
||||
|
||||
### docs/interface-contract.md (현재 baseline)
|
||||
- `GET /v1/assets` 응답 명세 없음 (bat-001에서 추가 예정이었으나 아직 반영 안 됨)
|
||||
- trex-basic 동시 배포 계획 없음
|
||||
|
||||
## Changed Areas
|
||||
|
||||
- Docs:
|
||||
- `docs/project-structure.md`
|
||||
- `docs/runtime-flows.md`
|
||||
- `docs/data-model.md`
|
||||
- `docs/interface-contract.md`
|
||||
- `docs/discovery/dcy-002_20260502_trex-basic-consumer-api-contract.md` (A-3 철회 주석)
|
||||
- Modules:
|
||||
- `migrations/versions/20260511_0015_portfolio_uuid_migration.py` (신규 Alembic revision)
|
||||
- `app/domain/models.py` (주석 없음, Portfolio dataclass 구조 변경 없음 — 기존 `portfolio_id` + `name` 필드 유지)
|
||||
- `app/repositories/postgres.py`:
|
||||
- `create_portfolio()`: `portfolio_id` 파라미터 제거, 서버 UUID 생성
|
||||
- `get_portfolio_by_name_and_user()` 신규 메서드
|
||||
- `ensure_schema()`: `(owner_user_id, name)` UNIQUE 제약 추가
|
||||
- `update_portfolio()`: UniqueViolation 시 ValueError("이미 사용 중인 이름입니다") 반환
|
||||
- `app/domain/services.py`: `PortfolioDomainRepository` Protocol 업데이트
|
||||
- `app/schemas/portfolio.py`:
|
||||
- `CreatePortfolioRequest`: `portfolio_id` 필드 제거, `name: str` 필수로 변경, name 유효성 validator 추가
|
||||
- `app/services/portfolio_service.py`: `create_portfolio()` 호출부 업데이트
|
||||
- `app/cli/commands/portfolio.py`:
|
||||
- `create`: `portfolio_id` Argument → `name` Argument (필수), `--name` Option 제거
|
||||
- `status`, `buy`, `sell`, `invest`, `transfer`, `reset`, `delete`: `portfolio_id` → `name` Argument + `--user` Option
|
||||
- `update`: `portfolio_id` → `name` Argument + `--user` Option + `--new-name` Option (이름 변경용)
|
||||
- `_resolve_portfolio_id()` helper 추가
|
||||
- `_validate_portfolio_name()` helper 추가
|
||||
- Interfaces:
|
||||
- CLI: `portfolio <subcommand> <portfolio_id>` → `portfolio <subcommand> <name> --user <username>`
|
||||
- HTTP API: `POST /portfolios` request body: `portfolio_id` 필드 제거, `name` 필수화
|
||||
- HTTP API: `GET /v1/assets` `assets[].asset_id` → UUID 형식 반환
|
||||
|
||||
## Key Decisions
|
||||
|
||||
1. **portfolio_id 컬럼 타입 TEXT 유지**: PostgreSQL UUID 타입 변환은 이번 범위 밖. `uuid.uuid4()` 문자열을 TEXT로 저장.
|
||||
2. **get_portfolio_by_name_and_user() 공유**: CLI와 HTTP API 식별 모두 이 단일 repository 메서드 사용. Protocol에도 추가.
|
||||
3. **이름 대소문자 구분**: `TV`와 `tv`는 다른 이름. DB UNIQUE 제약도 case-sensitive.
|
||||
4. **단일 Alembic revision**: name 컬럼 추가 (IF NOT EXISTS), UUID 교체, FK 4개 테이블 갱신, UNIQUE 제약을 하나의 revision에서 수행. IF NOT EXISTS 가드로 재실행 안전성 보장.
|
||||
5. **update 명령 name 변경**: CLI `update` 명령은 `--new-name` Option으로 이름 변경. `--force` 없으면 confirm 프롬프트. DB UNIQUE 위반 시 `이미 사용 중인 이름입니다: {name}` 오류.
|
||||
6. **name 유효성**: `[a-zA-Z0-9._:-]`, 1~100자. Pydantic validator + CLI 레이어에서 이중 검사.
|
||||
7. **comment/tag sub-commands**: REQ-017 명시 범위 밖. 이번 batch에서 변경하지 않음 (UUID 직접 전달 필요).
|
||||
8. **dcy-002 A-3 공식 철회**: REQ-018 구현 시 해당 discovery 문서에 주석 추가.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **기존 portfolio_id 값이 UUID 형식인 경우**: migration이 이미 적용된 것으로 판단하는 guard (UNIQUE constraint 존재 여부로 판단). 재실행 시 skip.
|
||||
- **FK 고아 레코드**: migration revision 내 트랜잭션 후 orphan 검증 쿼리. 고아 발견 시 RuntimeError 발생.
|
||||
- **name 허용 문자 범위 외 입력**: CLI validation + Pydantic validator에서 조기 거부.
|
||||
- **동일 owner_user_id + 동일 name**: DB UNIQUE 위반 → `create_portfolio()`에서 ValueError, `update_portfolio()`에서 UniqueViolation catch → ValueError.
|
||||
- **존재하지 않는 name+user 조합**: `get_portfolio_by_name_and_user()` None 반환 → `포트폴리오를 찾을 수 없습니다: {name} (user: {username})`.
|
||||
- **revision 재실행**: `already_migrated` guard (UNIQUE constraint 유무 확인). 이미 적용됐으면 UUID 교체 블록 전체 skip.
|
||||
- **migration 중 portfolio_tags PK 업데이트**: PostgreSQL은 PK 값 UPDATE 허용. FK 제약 없으므로 안전.
|
||||
|
||||
## Architecture Impact
|
||||
|
||||
both (project-structure + runtime-flows)
|
||||
|
||||
## Reference Doc Update Plan
|
||||
|
||||
- project-structure.md:
|
||||
- `app/repositories/postgres.py` 항목: `get_portfolio_by_name_and_user(owner_user_id, name) → Portfolio | None` 추가
|
||||
- `app/domain/models.py` 항목: `Portfolio.portfolio_id`가 서버 생성 UUID임을 명시
|
||||
- runtime-flows.md:
|
||||
- `CLI Operational Command Execution` flow: `<name>` + `--user` → `get_portfolio_by_name_and_user()` → UUID 조회 흐름 추가
|
||||
- `v1 API — GET /v1/assets` flow: `asset_id = portfolio_id (UUID)` 반환 명시
|
||||
Reference in New Issue
Block a user