from fastapi import APIRouter, Header, HTTPException import httpx from app.schemas import ( AuthLogin, AuthRegister, AuthResponse, Destination, GoogleLoginRequest, FavoriteRequest, FavoriteResponse, ProfileStats, UserProfile, UserPlanPayload, ) from app.services.auth import ( get_favorites, get_plan, get_user_by_token, login_demo, login_google, login_user, register_user, save_plan, toggle_favorite, ) from app.services.pocketbase import pb router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/register", response_model=AuthResponse) async def register(body: AuthRegister): result = register_user(body.email, body.password, body.name) if not result: raise HTTPException(400, "该邮箱已注册") return AuthResponse(**result) @router.post("/login", response_model=AuthResponse) async def login(body: AuthLogin): result = login_user(body.email, body.password) if not result: raise HTTPException(401, "邮箱或密码错误") return AuthResponse(**result) @router.get("/me", response_model=UserProfile) async def me(authorization: str | None = Header(None)): token = _extract_token(authorization) user = get_user_by_token(token) if not user: raise HTTPException(401, "未登录") return UserProfile(**user) @router.get("/demo", response_model=AuthResponse) async def demo_login(): """一键体验演示账号""" result = login_demo() if not result: raise HTTPException(500, "演示账号不可用") return AuthResponse(**result) @router.post("/google", response_model=AuthResponse) async def google_login(body: GoogleLoginRequest): try: async with httpx.AsyncClient(timeout=10) as client: res = await client.get( "https://oauth2.googleapis.com/tokeninfo", params={"id_token": body.id_token}, ) res.raise_for_status() data = res.json() except httpx.HTTPError: raise HTTPException(401, "Google 登录验证失败") email = data.get("email") verified = data.get("email_verified") if not email or verified not in (True, "true"): raise HTTPException(401, "无效的 Google 账号") name = data.get("name") or email.split("@")[0] result = login_google(email, name) if not result: raise HTTPException(500, "登录失败") return AuthResponse(**result) @router.get("/favorites", response_model=FavoriteResponse) async def list_favorites(authorization: str | None = Header(None)): token = _extract_token(authorization) if not get_user_by_token(token): raise HTTPException(401, "未登录") return FavoriteResponse(slugs=get_favorites(token)) @router.get("/favorites/detail", response_model=list[Destination]) async def favorite_details(authorization: str | None = Header(None)): token = _extract_token(authorization) user = get_user_by_token(token) if not user: raise HTTPException(401, "未登录") slugs = get_favorites(token) all_dests = await pb.get_destinations() return [Destination(**d) for d in all_dests if d["slug"] in slugs] @router.get("/profile/stats", response_model=ProfileStats) async def profile_stats(authorization: str | None = Header(None)): token = _extract_token(authorization) user = get_user_by_token(token) if not user: raise HTTPException(401, "未登录") favs = get_favorites(token) plan = get_plan(token) or {} cities = len(plan.get("items") or []) levels = [(0, "🌱 新手游民"), (1, "🎒 背包客"), (2, "✈️ 飞行游民"), (3, "🌍 环球游民")] explored = max(len(favs), cities) level = levels[min(explored, 3)][1] return ProfileStats( favorites_count=len(favs), destinations_explored=explored, member_since="2026", nomad_level=level, ) @router.post("/favorites", response_model=FavoriteResponse) async def add_favorite(body: FavoriteRequest, authorization: str | None = Header(None)): token = _extract_token(authorization) if not get_user_by_token(token): raise HTTPException(401, "未登录") dest = await pb.get_destination(body.destination_slug) if not dest: raise HTTPException(404, "目的地不存在") slugs = toggle_favorite(token, body.destination_slug) return FavoriteResponse(slugs=slugs) @router.get("/plan", response_model=UserPlanPayload) async def read_plan(authorization: str | None = Header(None)): token = _extract_token(authorization) if not get_user_by_token(token): raise HTTPException(401, "未登录") plan = get_plan(token) if not plan: raise HTTPException(401, "未登录") return UserPlanPayload(**plan) @router.put("/plan", response_model=UserPlanPayload) async def write_plan(body: UserPlanPayload, authorization: str | None = Header(None)): token = _extract_token(authorization) if not get_user_by_token(token): raise HTTPException(401, "未登录") saved = save_plan( token, [item.model_dump() for item in body.items], body.meta.model_dump(), body.updated_at or None, ) if not saved: raise HTTPException(401, "未登录") return UserPlanPayload(**saved) def _extract_token(authorization: str | None) -> str: if not authorization: return "" if authorization.startswith("Bearer "): return authorization[7:] return authorization