refactor: simplify data model by removing unused fields and constraints

- Remove runtime_id from AuthToken model
- Remove market field from Portfolio model
- Remove BrokerCredential and LedgerTrade unused models
- Remove unique constraint on (owner_user_id, name)
- Simplify postgres repository schema and queries
- Clean up auth_service implementation
- Update test suite for simplified model
This commit is contained in:
2026-05-13 06:48:26 +00:00
parent 03bc3dcd8f
commit 3255d096f1
6 changed files with 149 additions and 980 deletions
+3 -42
View File
@@ -78,18 +78,12 @@ def issue_service_token(
name=name,
expires_at=issued.auth_token.expires_at,
revoked=issued.auth_token.revoked_at is not None,
runtime_id=issued.auth_token.runtime_id,
token=issued.token,
)
render_service_token_result(ctx.obj.console, ctx.obj.output_format, response)
# 발급 시 1회에 한해 원문 token 출력 (이후 마스킹된 값만 표시됨)
if ctx.obj.output_format == "json":
from app.cli.core.utils import render_output
render_output(ctx.obj.console, "json", response.model_dump(mode="json"))
else:
ctx.obj.console.print("[bold yellow]⚠ 토큰 원문은 지금 1회만 표시됩니다. 안전한 곳에 저장하세요.[/bold yellow]")
ctx.obj.console.print(f"[bold green]token:[/bold green] {issued.token}")
render_service_token_result(ctx.obj.console, ctx.obj.output_format, response)
if ctx.obj.output_format != "json":
ctx.obj.console.print(f"masked_token: {mask_token_text(issued.token)}")
@token_app.command("revoke")
@@ -114,36 +108,3 @@ def revoke_service_token(
revoked_at=revoked_at,
)
render_service_token_result(ctx.obj.console, ctx.obj.output_format, response)
@token_app.command("rotate")
def rotate_service_token(
ctx: typer.Context,
name: str = typer.Argument(..., help="Service token name to rotate."),
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt."),
) -> None:
if not force:
confirmed = typer.confirm(f"서비스 토큰 '{name}'을 갱신하시겠습니까?")
if not confirmed:
raise typer.Exit(code=0)
def _rotate(service: AuthService, app_context):
return service.rotate_service_token(name)
issued = _with_service(ctx, _rotate)
response = ServiceTokenIssueCliResponse(
token_id=issued.auth_token.id,
name=name,
expires_at=issued.auth_token.expires_at,
revoked=issued.auth_token.revoked_at is not None,
runtime_id=issued.auth_token.runtime_id,
token=issued.token,
)
if ctx.obj.output_format == "json":
from app.cli.core.utils import render_output
render_output(ctx.obj.console, "json", response.model_dump(mode="json"))
else:
ctx.obj.console.print("[bold yellow]⚠ 갱신된 토큰 원문은 지금 1회만 표시됩니다. 안전한 곳에 저장하세요.[/bold yellow]")
ctx.obj.console.print(f"[bold green]token:[/bold green] {issued.token}")
render_service_token_result(ctx.obj.console, ctx.obj.output_format, response)
+72 -74
View File
@@ -39,22 +39,29 @@ def to_plain_data(value: Any) -> Any:
def print_portfolio_table(console: Console, portfolios: list[PortfolioListItem]) -> None:
for i, item in enumerate(portfolios):
if i > 0:
console.print()
lines = [
f"id: {item.portfolio_id}",
f"broker: {item.broker}",
f"status: {item.status}",
f"symbol: {item.symbol}",
f"cash: {format_currency(item.cash_balance)}",
f"qty: {item.asset_quantity:g}",
f"price: {format_currency(item.price)}",
f"buffer: {format_currency(item.profit_buffer)}",
f"total: {format_currency(item.total_value)}",
]
for line in lines:
console.print(line)
table = Table(title="Portfolios", expand=True)
table.add_column("ID", style="cyan", no_wrap=True)
table.add_column("Broker")
table.add_column("Status", no_wrap=True)
table.add_column("Symbol", no_wrap=True)
table.add_column("Cash", justify="right")
table.add_column("Qty", justify="right")
table.add_column("Price", justify="right")
table.add_column("Buffer", justify="right")
table.add_column("Total", justify="right")
for item in portfolios:
table.add_row(
item.portfolio_id,
item.broker,
item.status,
item.symbol,
format_currency(item.cash_balance),
f"{item.asset_quantity:g}",
format_currency(item.price),
format_currency(item.profit_buffer),
format_currency(item.total_value),
)
console.print(table)
def print_portfolio_detail(console: Console, detail: PortfolioDetailResponse) -> None:
@@ -122,69 +129,60 @@ def print_portfolio_detail(console: Console, detail: PortfolioDetailResponse) ->
def print_user_table(console: Console, users: list[UserResponse]) -> None:
for i, user in enumerate(users, 1):
lines = [
f"username: {user.username}",
f"id: {user.id}",
f"email: {user.email or 'N/A'}",
f"permission: {user.permission}",
f"is_active: {'true' if user.is_active else 'false'}",
f"created_at: {user.created_at.isoformat(timespec='seconds') if user.created_at else 'N/A'}",
f"updated_at: {user.updated_at.isoformat(timespec='seconds') if user.updated_at else 'N/A'}",
]
for line in lines:
console.print(line)
if i < len(users):
console.print()
table = Table(title="Users", expand=True)
table.add_column("ID", style="cyan", no_wrap=True)
table.add_column("Username", no_wrap=True)
table.add_column("Email")
table.add_column("Created At", no_wrap=True)
table.add_column("Updated At", no_wrap=True)
for user in users:
table.add_row(
user.id,
user.username,
user.email or "N/A",
user.created_at.isoformat(timespec="seconds") if user.created_at else "N/A",
user.updated_at.isoformat(timespec="seconds") if user.updated_at else "N/A",
)
console.print(table)
def print_user_detail(console: Console, user: UserResponse) -> None:
lines = [
f"id: {user.id}",
f"username: {user.username}",
f"email: {user.email or 'N/A'}",
f"permission: {user.permission}",
f"is_active: {'true' if user.is_active else 'false'}",
f"last_login_at: {user.last_login_at.isoformat(timespec='seconds') if user.last_login_at else 'N/A'}",
f"created_at: {user.created_at.isoformat(timespec='seconds') if user.created_at else 'N/A'}",
f"updated_at: {user.updated_at.isoformat(timespec='seconds') if user.updated_at else 'N/A'}",
]
service_token_count = getattr(user, "service_token_count", None)
service_token_names = getattr(user, "service_token_names", None)
if service_token_count is not None and service_token_names is not None:
rendered_names = ", ".join(service_token_names) if service_token_names else "N/A"
lines.append(f"service_token_count: {service_token_count}")
lines.append(f"service_token_names: {rendered_names}")
for line in lines:
console.print(line)
console.print(
Panel.fit(
"\n".join(
[
f"id: {user.id}",
f"username: {user.username}",
f"email: {user.email or 'N/A'}",
f"created_at: {user.created_at.isoformat(timespec='seconds') if user.created_at else 'N/A'}",
f"updated_at: {user.updated_at.isoformat(timespec='seconds') if user.updated_at else 'N/A'}",
]
),
title="User",
)
)
def print_service_token_list(console: Console, tokens: list[AuthTokenResponse]) -> None:
for i, token in enumerate(tokens, 1):
status = "[red]revoked[/red]" if token.revoked_at else "[green]active[/green]"
expires = token.expires_at.isoformat(timespec="seconds") if token.expires_at else "never"
last_used = token.last_used_at.isoformat(timespec="seconds") if token.last_used_at else "-"
revoked_at = token.revoked_at.isoformat(timespec="seconds") if token.revoked_at else "-"
created_at = token.created_at.isoformat(timespec="seconds") if token.created_at else "N/A"
lines = [
f"[bold cyan]{token.name}[/bold cyan] ({status})",
f" token_prefix: {token.token_prefix} [dim](full token shown only at issue time)[/dim]",
f" id : {token.id}",
f" user_id : {token.user_id}",
f" perm : {token.permission}",
f" expires : {expires}",
f" last_used: {last_used}",
f" created : {created_at}",
]
if token.revoked_at:
lines.append(f" revoked : {revoked_at}")
console.print("\n".join(lines))
if i < len(tokens):
console.print("[dim]─[/dim]" * 40)
# Keep old name as alias for backward compatibility
print_service_token_table = print_service_token_list
def print_service_token_table(console: Console, tokens: list[AuthTokenResponse]) -> None:
table = Table(title="Service Tokens", expand=True)
table.add_column("Name", style="cyan", no_wrap=True)
table.add_column("Permission", no_wrap=True)
table.add_column("User ID", no_wrap=True)
table.add_column("Prefix", no_wrap=True)
table.add_column("Expires At", no_wrap=True)
table.add_column("Revoked At", no_wrap=True)
table.add_column("Created At", no_wrap=True)
for token in tokens:
table.add_row(
token.name,
token.permission,
token.user_id,
token.token_prefix,
token.expires_at.isoformat(timespec="seconds") if token.expires_at else "never",
token.revoked_at.isoformat(timespec="seconds") if token.revoked_at else "-",
token.created_at.isoformat(timespec="seconds") if token.created_at else "N/A",
)
console.print(table)
def print_comment_table(console: Console, comments: list[CommentResponse]) -> None: