feat(REQ-001): add portfolio comment history API, CLI, repository, migration, and tests

Agent-Logs-Url: https://github.com/genichin/portfolio-hub/sessions/64fc2cec-78f5-41d1-99a4-45f868781edd

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-10 13:54:18 +00:00
committed by GitHub
co-authored by genichin
parent 4684597353
commit e8f782bec6
14 changed files with 1173 additions and 10 deletions
+33 -1
View File
@@ -7,7 +7,7 @@ import json
from fastapi import Depends
from app.core.config import Settings, get_settings
from app.domain.models import LedgerEntry, Portfolio, StrategySet
from app.domain.models import LedgerEntry, Portfolio, PortfolioComment, StrategySet
from app.domain.services import (
apply_buy,
apply_delete,
@@ -21,6 +21,9 @@ from app.repositories.factory import get_portfolio_repository_from_settings
from app.repositories.portfolio_repository import PortfolioRepository
from app.schemas.portfolio import (
BuyRequest,
CommentCreateRequest,
CommentResponse,
CommentUpdateRequest,
CreatePortfolioRequest,
InvestRequest,
PortfolioDetailResponse,
@@ -107,6 +110,16 @@ def _to_ledger_entry_response(entry: LedgerEntry):
)
def _to_comment_response(comment: PortfolioComment) -> CommentResponse:
return CommentResponse(
id=comment.id,
portfolio_id=comment.portfolio_id,
content=comment.content,
created_at=comment.created_at,
updated_at=comment.updated_at,
)
def _to_detail_response(status_result) -> PortfolioDetailResponse:
return PortfolioDetailResponse(
portfolio=_to_portfolio_summary(status_result.portfolio),
@@ -257,6 +270,25 @@ class PortfolioService:
def delete(self, portfolio_id: str, *, keep_ledger: bool = False) -> None:
apply_delete(self._repository, portfolio_id, clear_ledger=not keep_ledger)
def create_comment(self, portfolio_id: str, payload: CommentCreateRequest) -> CommentResponse:
self._repository.get(portfolio_id)
comment = self._repository.create_comment(portfolio_id, content=payload.content)
return _to_comment_response(comment)
def list_comments(self, portfolio_id: str) -> list[CommentResponse]:
self._repository.get(portfolio_id)
comments = self._repository.list_comments(portfolio_id)
return [_to_comment_response(c) for c in comments]
def update_comment(self, portfolio_id: str, comment_id: str, payload: CommentUpdateRequest) -> CommentResponse:
self._repository.get(portfolio_id)
comment = self._repository.update_comment(portfolio_id, comment_id, content=payload.content)
return _to_comment_response(comment)
def delete_comment(self, portfolio_id: str, comment_id: str) -> None:
self._repository.get(portfolio_id)
self._repository.delete_comment(portfolio_id, comment_id)
def get_portfolio_service(
repository: PortfolioRepository = Depends(get_portfolio_repository),