132 lines
4.4 KiB
Python
132 lines
4.4 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Literal, Optional
|
||
|
||
from fastapi import APIRouter, Request
|
||
from fastapi.responses import HTMLResponse
|
||
from pydantic import BaseModel, HttpUrl
|
||
|
||
from app.core.config import get_settings
|
||
from app.legal_docs import PRIVACY_POLICY_MD, TERMS_OF_USE_MD, choose_content_by_lang, render_as_simple_html
|
||
|
||
|
||
router = APIRouter(prefix="/v1/legal", tags=["legal"])
|
||
|
||
INTERNAL_SENTINEL = "__internal__"
|
||
|
||
|
||
# 当前多语言仅支持 EN / TC(繁体)
|
||
ResolvedLang = Literal["en", "tc"]
|
||
|
||
|
||
class LegalLinksResponse(BaseModel):
|
||
privacyPolicyUrl: HttpUrl
|
||
termsOfUseUrl: HttpUrl
|
||
resolvedLang: ResolvedLang
|
||
|
||
|
||
def _resolve_lang(accept_language: Optional[str]) -> ResolvedLang:
|
||
"""
|
||
从 Accept-Language 里做一个轻量语言解析。
|
||
|
||
说明:
|
||
- 只关心:en / tc(繁体)
|
||
- 解析失败或缺失:回退 en
|
||
"""
|
||
|
||
if not accept_language:
|
||
return "en"
|
||
s = accept_language.lower()
|
||
|
||
# 目前只支持 EN / TC:只要是中文或显式 tc,都归到 tc
|
||
if "tc" in s:
|
||
return "tc"
|
||
if "zh" in s or "hant" in s or "tw" in s or "hk" in s or "mo" in s:
|
||
return "tc"
|
||
|
||
return "en"
|
||
|
||
|
||
def _pick_urls(lang: ResolvedLang) -> tuple[str, str, ResolvedLang]:
|
||
settings = get_settings()
|
||
|
||
if lang == "tc":
|
||
privacy = settings.legal_privacy_url_tc or settings.legal_privacy_url_en
|
||
terms = settings.legal_terms_url_tc or settings.legal_terms_url_en
|
||
# 如果未配置 tc 链接:
|
||
# - 若走内置页面(__internal__),可直接展示 tc 内容,因此 resolved=tc
|
||
# - 否则回退到 en 链接,因此 resolved=en
|
||
if settings.legal_privacy_url_tc or settings.legal_terms_url_tc:
|
||
resolved: ResolvedLang = "tc"
|
||
elif privacy == INTERNAL_SENTINEL or terms == INTERNAL_SENTINEL:
|
||
resolved = "tc"
|
||
else:
|
||
resolved = "en"
|
||
return privacy, terms, resolved
|
||
|
||
return settings.legal_privacy_url_en, settings.legal_terms_url_en, "en"
|
||
|
||
|
||
def _join_base_url(base_url: str, path: str) -> str:
|
||
base = (base_url or "").rstrip("/")
|
||
p = (path or "").strip()
|
||
if not p.startswith("/"):
|
||
p = "/" + p
|
||
return base + p
|
||
|
||
|
||
def _normalize_internal_url(request: Request, url: str, internal_path: str) -> str:
|
||
"""
|
||
将内置哨兵值替换为当前服务的可访问绝对 URL。
|
||
"""
|
||
|
||
if url != INTERNAL_SENTINEL:
|
||
return url
|
||
return _join_base_url(str(request.base_url), internal_path)
|
||
|
||
|
||
@router.get("/links", response_model=LegalLinksResponse)
|
||
async def get_legal_links(request: Request) -> LegalLinksResponse:
|
||
"""
|
||
获取协议链接(隐私协议 / 使用协议)。
|
||
|
||
- 语言来源:Accept-Language
|
||
- 默认兜底:en
|
||
"""
|
||
|
||
accept_language = request.headers.get("accept-language")
|
||
lang = _resolve_lang(accept_language)
|
||
privacy, terms, resolved = _pick_urls(lang)
|
||
privacy = _normalize_internal_url(request, privacy, "/v1/legal/privacy")
|
||
terms = _normalize_internal_url(request, terms, "/v1/legal/terms")
|
||
return LegalLinksResponse(privacyPolicyUrl=privacy, termsOfUseUrl=terms, resolvedLang=resolved)
|
||
|
||
|
||
@router.get("/privacy", response_class=HTMLResponse)
|
||
async def get_privacy_policy(request: Request) -> HTMLResponse:
|
||
"""
|
||
内置隐私协议页面(用于未配置外部托管链接时的兜底)。
|
||
"""
|
||
|
||
accept_language = request.headers.get("accept-language")
|
||
lang = _resolve_lang(accept_language)
|
||
content, resolved = choose_content_by_lang(PRIVACY_POLICY_MD, lang)
|
||
title = "Hey Mama | Privacy Policy" if resolved == "en" else "Hey Mama|隱私權政策"
|
||
page = render_as_simple_html(title=title, content=content)
|
||
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"})
|
||
|
||
|
||
@router.get("/terms", response_class=HTMLResponse)
|
||
async def get_terms_of_use(request: Request) -> HTMLResponse:
|
||
"""
|
||
内置使用协议页面(用于未配置外部托管链接时的兜底)。
|
||
"""
|
||
|
||
accept_language = request.headers.get("accept-language")
|
||
lang = _resolve_lang(accept_language)
|
||
content, resolved = choose_content_by_lang(TERMS_OF_USE_MD, lang)
|
||
title = "Hey Mama – Terms of Use" if resolved == "en" else "Hey Mama 使用條款"
|
||
page = render_as_simple_html(title=title, content=content)
|
||
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"})
|
||
|