Files
weibo_signin/backend/api_service/app/routers/accounts.py
2026-03-09 14:05:00 +08:00

140 lines
3.8 KiB
Python

"""
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")