Files
life-echo/api/routers/user.py
2026-03-01 10:12:23 +01:00

152 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
用户相关 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",
)