KManager / app /api /accounts.py
StarrySkyWorld's picture
Initial commit
494c89b
"""
Accounts API - List, switch, delete accounts
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
from services.token_service import TokenService
from services.kiro_service import KiroService
router = APIRouter()
token_service = TokenService()
kiro_service = KiroService()
class AccountResponse(BaseModel):
filename: str
accountName: str
email: Optional[str] = None
provider: str
authMethod: str
region: str
isActive: bool
isExpired: bool
expiresAt: Optional[str] = None
expiresIn: Optional[str] = None
class AccountListResponse(BaseModel):
accounts: List[AccountResponse]
total: int
valid: int
expired: int
activeAccount: Optional[str] = None
@router.get("", response_model=AccountListResponse)
async def list_accounts():
"""Get all accounts"""
tokens = token_service.list_tokens()
current = token_service.get_current_token()
current_refresh = current.raw_data.get('refreshToken') if current else None
accounts = []
valid_count = 0
expired_count = 0
active_account = None
for token in tokens:
is_active = (current_refresh and
token.raw_data.get('refreshToken') == current_refresh)
if is_active:
active_account = token.account_name
if token.is_expired:
expired_count += 1
else:
valid_count += 1
# Calculate expires in
expires_in = None
if token.expires_at:
try:
# Handle timezone-aware and naive datetimes
now = datetime.now(token.expires_at.tzinfo) if token.expires_at.tzinfo else datetime.now()
delta = token.expires_at - now
if delta.total_seconds() > 0:
hours = int(delta.total_seconds() // 3600)
if hours < 24:
expires_in = f"{hours}h"
else:
expires_in = f"{hours // 24}d"
else:
expires_in = "expired"
except Exception:
expires_in = "—"
accounts.append(AccountResponse(
filename=token.path.name,
accountName=token.account_name,
email=token.raw_data.get('email') if token.raw_data else None,
provider=token.provider,
authMethod=token.auth_method,
region=token.region,
isActive=is_active,
isExpired=token.is_expired,
expiresAt=token.expires_at.isoformat() if token.expires_at else None,
expiresIn=expires_in
))
return AccountListResponse(
accounts=accounts,
total=len(accounts),
valid=valid_count,
expired=expired_count,
activeAccount=active_account
)
@router.post("/{filename}/switch")
async def switch_account(filename: str):
"""Switch to a specific account"""
token = token_service.get_token(filename)
if not token:
raise HTTPException(status_code=404, detail="Account not found")
success = token_service.activate_token(token)
if not success:
raise HTTPException(status_code=500, detail="Failed to switch account")
return {"success": True, "message": f"Switched to {token.account_name}"}
@router.delete("/{filename}")
async def delete_account(filename: str):
"""Delete an account"""
token = token_service.get_token(filename)
if not token:
raise HTTPException(status_code=404, detail="Account not found")
try:
token_service.delete_token(filename)
return {"success": True, "message": "Account deleted"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{filename}/refresh")
async def refresh_account(filename: str):
"""Refresh account token"""
token = token_service.get_token(filename)
if not token:
raise HTTPException(status_code=404, detail="Account not found")
try:
updated = token_service.refresh_and_save(token)
return {
"success": True,
"expiresAt": updated.expires_at.isoformat() if updated.expires_at else None
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/expired/all")
async def delete_expired():
"""Delete all expired accounts"""
tokens = token_service.list_tokens()
deleted = 0
for token in tokens:
if token.is_expired:
try:
token_service.delete_token(token.path.name)
deleted += 1
except Exception:
pass
return {"success": True, "deleted": deleted}
@router.get("/current")
async def get_current():
"""Get current active account"""
token = token_service.get_current_token()
if not token:
return {"active": False}
return {
"active": True,
"accountName": token.account_name,
"email": token.raw_data.get('email'),
"provider": token.provider,
"isExpired": token.is_expired
}