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:
@@ -5,8 +5,12 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
import re as _re
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
_PORTFOLIO_NAME_RE = _re.compile(r"^[a-zA-Z0-9._:\-]{1,100}$")
|
||||
|
||||
|
||||
class StrategySetResponse(BaseModel):
|
||||
id: str
|
||||
@@ -70,7 +74,7 @@ class PortfolioDetailResponse(BaseModel):
|
||||
|
||||
|
||||
class CreatePortfolioRequest(PortfolioBase):
|
||||
portfolio_id: str
|
||||
name: str # required, overrides Optional from PortfolioBase; used as (owner_user_id, name) identifier
|
||||
strategy_name: str = "target_value"
|
||||
strategy_params: dict[str, Any] = Field(default_factory=dict)
|
||||
active_strategy_id: str | None = None
|
||||
@@ -80,6 +84,15 @@ class CreatePortfolioRequest(PortfolioBase):
|
||||
asset_price: float = Field(default=0.0, ge=0.0)
|
||||
profit_buffer: float = Field(default=0.0, ge=0.0)
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def validate_name(cls, v: str) -> str:
|
||||
if not _PORTFOLIO_NAME_RE.match(v):
|
||||
raise ValueError(
|
||||
f"이름은 [a-zA-Z0-9._:-] 문자만 허용하며 1~100자여야 합니다: {v!r}"
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class UpdatePortfolioRequest(BaseModel):
|
||||
broker: str | None = None
|
||||
|
||||
Reference in New Issue
Block a user