## Plan: `pfhub service-token list` 명령 추가 `pfhub service-token list` 명령을 추가한다. 레포지토리 → 서비스 → 출력 → CLI 순서로 구현하며, 기존 `user list` 패턴을 템플릿으로 사용한다. --- ### Phase 1 — Repository 레이어 **1.** portfolio_repository.py — `PortfolioRepository` 프로토콜에 추상 메서드 추가: ``` list_service_tokens(user_id: str | None, *, include_revoked: bool) -> list[AuthToken] ``` **2.** postgres.py — `PostgresRepository`에 구현체 추가: - 기본 쿼리: `SELECT … FROM auth_tokens WHERE token_type='service'` - `user_id` 지정 시 `AND user_id = %s` 조건 추가 - `include_revoked=False`(기본)이면 `AND revoked_at IS NULL` 조건 추가 - `ORDER BY created_at DESC` - 기존 `list_auth_tokens()` 의 `_row_to_auth_token()` 재사용 --- ### Phase 2 — Service 레이어 **3.** auth_service.py — `AuthService`에 메서드 추가: ``` list_service_tokens(username: str | None, *, include_revoked: bool = False) -> list[AuthToken] ``` - `username` 지정 시 `get_user_by_username()` → `user_id` 변환 후 리포지토리 위임 - `username` 없으면 `user_id=None` 으로 전체 조회 --- ### Phase 3 — Schema / 출력 레이어 **4.** auth.py — 기존 `AuthTokenResponse` 재사용 (이미 token_hash 미포함, 안전) **5.** utils.py — `print_service_token_table(console, tokens)` 함수 추가: - Rich Table 컬럼: `name`, `permission`, `user_id`, `expires_at`, `revoked_at`, `created_at`, `token_prefix` - `render_output()` dispatch에 `list[AuthTokenResponse]` 분기 추가 --- ### Phase 4 — CLI 레이어 **6.** service_tokens.py — `@token_app.command("list")` 추가: - `--user ` (Optional[str], default=None) — 특정 사용자 토큰만 조회 - `--show-revoked` (bool flag, default=False) — revoked 토큰 포함 - `_with_service()` + lambda 패턴 (user.py 참조) - `render_output(..., empty_message="No service tokens found.")` --- ### Verification 1. `pfhub service-token list` → 활성 서비스 토큰 테이블 출력 2. `pfhub service-token list --show-revoked` → revoked 토큰 포함 출력 3. `pfhub service-token list --user alice` → alice 소유 토큰만 필터링 4. `pfhub service-token list --output json` → JSON 포맷 출력 5. 토큰 없을 때 → `"No service tokens found."` 메시지 출력 6. test_cli.py 또는 test_auth.py에 신규 테스트 추가 후 전체 테스트 통과 확인 --- ### Decisions - revoked 토큰: 기본 제외, `--show-revoked` 옵션으로 포함 - `--user` 없으면 전체 서비스 토큰 조회 - 스키마: `AuthTokenResponse` 재사용 (신규 모델 불필요) - `token_hash`는 절대 노출하지 않음