Files
mindfulness/server/app/api/v1/legal.py
雷汀岚 e552e22de9 chore: App 名稱 Hey Mama → Dear Mama + 相關修復
- 品牌與顯示:app.json、Info.plist、package scheme、iOS 產物 DearMama.app
- 協議與條款:隱私協議/使用條款全文、設計文檔、server legal_docs + legal API、push 預設 title
- 多語言:all.json / zh-TW / zh-CN / en / es / pt 的 consent、widget 標題與引導文案
- 小工具:EmotionWidget.swift 品牌文案、Dear Mama.xcscheme
- CocoaPods:project.pbxproj objectVersion 70→56 以通過 pod install
- Metro:react-native-text-size 用 require + extraNodeModules 解析
- textWrap:measureWidthImpl 改為 require 載入 react-native-text-size

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 16:16:02 +08:00

132 lines
4.4 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.
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 = "Dear Mama | Privacy Policy" if resolved == "en" else "Dear 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 = "Dear Mama Terms of Use" if resolved == "en" else "Dear Mama 使用條款"
page = render_as_simple_html(title=title, content=content)
return HTMLResponse(content=page, headers={"Content-Language": "en" if resolved == "en" else "zh-Hant"})