""" 用户相关 API 路由 """ import os from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional, Literal from middleware.auth import get_current_user from database.models import User, utc_now from database import get_async_db from sqlalchemy.ext.asyncio import AsyncSession router = APIRouter(prefix="/api/user", tags=["user"]) # 是否开启测试订阅(仅用于微信支付审核未通过前的测试) ENABLE_TEST_SUBSCRIPTION = os.getenv("ENABLE_TEST_SUBSCRIPTION", "").lower() in ("1", "true", "yes") class UserProfileResponse(BaseModel): """用户资料响应""" id: str phone: str email: Optional[str] nickname: str avatar_url: Optional[str] subscription_type: str created_at: str birth_year: Optional[int] = None birth_place: Optional[str] = None grew_up_place: Optional[str] = None occupation: Optional[str] = None class UpdateUserProfileRequest(BaseModel): """更新用户基础资料请求""" birth_year: Optional[int] = None birth_place: Optional[str] = None grew_up_place: Optional[str] = None occupation: Optional[str] = None class TestSubscriptionRequest(BaseModel): """测试订阅请求""" action: Literal["activate", "deactivate"] plan_id: Optional[str] = "pro" # activate 时生效,仅支持 pro / pro_plus class TestSubscriptionResponse(BaseModel): """测试订阅响应""" success: bool message: str subscription_type: str @router.get("/profile", response_model=UserProfileResponse) async def get_user_profile( current_user: User = Depends(get_current_user) ): """ 获取当前用户资料 与 /api/auth/me 功能相同,但路径不同以满足前端需求 """ return UserProfileResponse( id=current_user.id, phone=current_user.phone, email=current_user.email, nickname=current_user.nickname, avatar_url=current_user.avatar_url, subscription_type=current_user.subscription_type, created_at=current_user.created_at.isoformat(), birth_year=current_user.birth_year, birth_place=current_user.birth_place, grew_up_place=current_user.grew_up_place, occupation=current_user.occupation, ) @router.put("/profile", response_model=UserProfileResponse) async def update_user_profile( body: UpdateUserProfileRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_async_db), ): """更新用户基础资料(出生年份、出生地、成长地、职业)""" if body.birth_year is not None: current_user.birth_year = body.birth_year if body.birth_place is not None: current_user.birth_place = body.birth_place if body.grew_up_place is not None: current_user.grew_up_place = body.grew_up_place if body.occupation is not None: current_user.occupation = body.occupation await db.commit() await db.refresh(current_user) return UserProfileResponse( id=current_user.id, phone=current_user.phone, email=current_user.email, nickname=current_user.nickname, avatar_url=current_user.avatar_url, subscription_type=current_user.subscription_type, created_at=current_user.created_at.isoformat(), birth_year=current_user.birth_year, birth_place=current_user.birth_place, grew_up_place=current_user.grew_up_place, occupation=current_user.occupation, ) @router.post("/test-subscription", response_model=TestSubscriptionResponse) async def test_subscription( body: TestSubscriptionRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_async_db), ): """ 测试订阅开关(仅当 ENABLE_TEST_SUBSCRIPTION=1 时可用)。 - activate:将当前用户设为付费套餐(pro 或 pro_plus),用于在微信支付审核通过前测试付费后额度。 - deactivate:恢复为免费体验版。 """ if not ENABLE_TEST_SUBSCRIPTION: raise HTTPException(status_code=404, detail="测试订阅功能未开放") now = utc_now() if body.action == "activate": if body.plan_id not in ("pro", "pro_plus"): raise HTTPException(status_code=400, detail="plan_id 仅支持 pro 或 pro_plus") current_user.subscription_type = body.plan_id current_user.subscription_expires_at = now + timedelta(days=365) await db.flush() return TestSubscriptionResponse( success=True, message=f"已开启测试订阅:{body.plan_id}", subscription_type=body.plan_id, ) # deactivate current_user.subscription_type = "free" current_user.subscription_expires_at = None await db.flush() return TestSubscriptionResponse( success=True, message="已关闭测试订阅,恢复免费体验版", subscription_type="free", )