""" Weibo Account CRUD router. All endpoints require JWT authentication and enforce resource ownership. """ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from shared.models import get_db, Account, User from shared.crypto import encrypt_cookie, decrypt_cookie, derive_key from shared.config import shared_settings from shared.response import success_response, error_response from api_service.app.dependencies import get_current_user from api_service.app.schemas.account import ( AccountCreate, AccountUpdate, AccountResponse, ) router = APIRouter(prefix="/api/v1/accounts", tags=["accounts"]) def _encryption_key() -> bytes: return derive_key(shared_settings.COOKIE_ENCRYPTION_KEY) def _account_to_dict(account: Account) -> dict: return AccountResponse.model_validate(account).model_dump(mode="json") async def _get_owned_account( account_id: str, user: User, db: AsyncSession, ) -> Account: """Fetch an account and verify it belongs to the current user.""" result = await db.execute(select(Account).where(Account.id == account_id)) account = result.scalar_one_or_none() if account is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found") if account.user_id != user.id: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied") return account # ---- CREATE ---- @router.post("", status_code=status.HTTP_201_CREATED) async def create_account( body: AccountCreate, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): key = _encryption_key() ciphertext, iv = encrypt_cookie(body.cookie, key) account = Account( user_id=user.id, weibo_user_id=body.weibo_user_id, remark=body.remark, encrypted_cookies=ciphertext, iv=iv, status="pending", ) db.add(account) await db.commit() await db.refresh(account) return success_response(_account_to_dict(account), "Account created") # ---- LIST ---- @router.get("") async def list_accounts( user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(Account).where(Account.user_id == user.id) ) accounts = result.scalars().all() return success_response( [_account_to_dict(a) for a in accounts], "Accounts retrieved", ) # ---- DETAIL ---- @router.get("/{account_id}") async def get_account( account_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): account = await _get_owned_account(account_id, user, db) return success_response(_account_to_dict(account), "Account retrieved") # ---- UPDATE ---- @router.put("/{account_id}") async def update_account( account_id: str, body: AccountUpdate, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): account = await _get_owned_account(account_id, user, db) if body.remark is not None: account.remark = body.remark if body.cookie is not None: key = _encryption_key() ciphertext, iv = encrypt_cookie(body.cookie, key) account.encrypted_cookies = ciphertext account.iv = iv await db.commit() await db.refresh(account) return success_response(_account_to_dict(account), "Account updated") # ---- DELETE ---- @router.delete("/{account_id}") async def delete_account( account_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): account = await _get_owned_account(account_id, user, db) await db.delete(account) await db.commit() return success_response(None, "Account deleted")