122 lines
6.7 KiB
Python
122 lines
6.7 KiB
Python
"""Server-side Docs metadata and Gatekeeper authorization helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Literal
|
|
|
|
from app.models.user import User
|
|
|
|
DocsAccess = Literal["public", "docs_user", "docs_developer", "docs_admin"]
|
|
DocsLang = Literal["zh", "en"]
|
|
|
|
VALID_DOCS_LANGS = {"zh", "en"}
|
|
DOCS_README_FILENAME = "README.md"
|
|
DEFAULT_DOCS_SLUG = "overview"
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
TECHNICAL_DOCS_ROOT = REPO_ROOT / "docs" / "technical"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DocsMetadata:
|
|
filename: str
|
|
slug: str
|
|
access: DocsAccess
|
|
group: str
|
|
order: int
|
|
zh_title: str
|
|
en_title: str
|
|
|
|
|
|
DOCS_METADATA: tuple[DocsMetadata, ...] = (
|
|
DocsMetadata(DOCS_README_FILENAME, DEFAULT_DOCS_SLUG, "public", "Overview", 0, "技术文档", "Technical Docs"),
|
|
DocsMetadata("quickstart.md", "quickstart", "public", "Manual", 1, "快速开始", "Quickstart"),
|
|
DocsMetadata("manual.md", "manual", "public", "Manual", 2, "Planet 使用手册", "Planet Manual"),
|
|
DocsMetadata("faq.md", "faq", "public", "Manual", 3, "常见问题", "FAQ"),
|
|
DocsMetadata("location-pipeline-user.md", "location-pipeline-user", "public", "Manual", 4, "Earth 位置候选采集使用手册", "Earth Location Candidate Collection User Guide"),
|
|
DocsMetadata("earth-frontend-context.md", "earth-frontend-context", "docs_developer", "Earth", 10, "Earth 前端结构", "Earth Frontend Context"),
|
|
DocsMetadata("earth-layer-style-reference.md", "earth-layer-style-reference", "docs_developer", "Earth", 11, "Earth 图层样式属性索引", "Earth Layer Style Reference"),
|
|
DocsMetadata("earth-render-layer-order.md", "earth-render-layer-order", "docs_developer", "Earth", 12, "Earth 渲染图层顺序", "Earth Render Layer Order"),
|
|
DocsMetadata("earth-satellite-footprint-policy.md", "earth-satellite-footprint-policy", "docs_developer", "Earth", 13, "Earth 卫星覆盖策略", "Earth Satellite Footprint Policy"),
|
|
DocsMetadata("earth-bgp-context.md", "earth-bgp-context", "docs_developer", "Earth", 14, "BGP 态势上下文", "BGP Context"),
|
|
DocsMetadata("earth-news-live-streams-collector-format.md", "earth-news-live-streams-collector-format", "docs_developer", "Earth", 15, "新闻直播采集格式", "News Live Streams Collector Format"),
|
|
DocsMetadata("earth-interactable-usage.md", "earth-interactable-usage", "docs_developer", "Earth", 16, "Earth 可交互图标接入", "Earth Interactable Usage"),
|
|
DocsMetadata("earth-toolbar-overlay-coordination.md", "earth-toolbar-overlay-coordination", "docs_developer", "Earth", 17, "Earth 工具栏与浮层协同", "Earth Toolbar and Overlay Coordination"),
|
|
DocsMetadata("frontend-admin-frontend-context.md", "frontend-admin-frontend-context", "docs_developer", "Frontend", 20, "控制台前端结构", "Admin Frontend Context"),
|
|
DocsMetadata("frontend-layout-guidelines.md", "frontend-layout-guidelines", "docs_developer", "Frontend", 21, "前端布局指南", "Frontend Layout Guidelines"),
|
|
DocsMetadata("docs-gatekeeper-development.md", "docs-gatekeeper-development", "docs_developer", "Frontend", 22, "Docs Gatekeeper 开发说明", "Docs Gatekeeper Development Guide"),
|
|
DocsMetadata("backend-collectors.md", "backend-collectors", "docs_developer", "Backend", 30, "数据采集系统", "Data Collectors"),
|
|
DocsMetadata("backend-system-service-control.md", "backend-system-service-control", "docs_admin", "Backend", 31, "系统服务控制", "System Service Control"),
|
|
DocsMetadata("datasource-collector-settings-connectivity.md", "datasource-collector-settings-connectivity", "docs_developer", "Backend", 32, "数据源、采集器设置与连接验证", "Datasource Collector Settings and Connectivity"),
|
|
DocsMetadata("backend-datasources-api-performance.md", "backend-datasources-api-performance", "docs_developer", "Backend", 33, "数据源 API 性能", "Datasource API Performance"),
|
|
DocsMetadata("location-pipeline-development.md", "location-pipeline-development", "docs_developer", "Backend", 34, "通用位置估算管线开发说明", "Shared Location Resolution Pipeline Development Guide"),
|
|
DocsMetadata("agents-aiprovider.md", "agents-aiprovider", "docs_developer", "Agents", 40, "AI Provider 指南", "AI Provider Guide"),
|
|
DocsMetadata("ops-runbook.md", "ops-runbook", "docs_admin", "Ops", 49, "Planet 运维手册", "Planet Ops Runbook"),
|
|
DocsMetadata("ops-docker-compose-buildx-upgrade.md", "ops-docker-compose-buildx-upgrade", "docs_admin", "Ops", 50, "Docker + Compose + Buildx 升级", "Docker + Compose + Buildx Upgrade"),
|
|
DocsMetadata("ops-planet-sh-startup.md", "ops-planet-sh-startup", "docs_admin", "Ops", 51, "planet.sh 启动机制", "planet.sh Startup"),
|
|
)
|
|
|
|
DOCS_BY_SLUG = {entry.slug: entry for entry in DOCS_METADATA}
|
|
|
|
|
|
def get_user_gatekeeper_groups(user: User | None) -> set[str]:
|
|
if user is None:
|
|
return set()
|
|
|
|
role = user.role.value if hasattr(user.role, "value") else str(user.role or "")
|
|
if role == "super_admin":
|
|
return {"docs_user", "docs_developer", "docs_admin"}
|
|
if role == "admin":
|
|
return {"docs_user", "docs_developer", "docs_admin"}
|
|
|
|
groups = set()
|
|
raw_groups = user.gatekeeper_groups or []
|
|
if isinstance(raw_groups, list):
|
|
groups.update(str(group) for group in raw_groups)
|
|
|
|
if "docs_admin" in groups:
|
|
groups.update({"docs_developer", "docs_user"})
|
|
if "docs_developer" in groups:
|
|
groups.add("docs_user")
|
|
return groups
|
|
|
|
|
|
def can_read_doc(entry: DocsMetadata, user: User | None) -> bool:
|
|
if entry.access == "public":
|
|
return True
|
|
return entry.access in get_user_gatekeeper_groups(user)
|
|
|
|
|
|
def doc_path_for(entry: DocsMetadata, lang: str) -> Path:
|
|
if lang not in VALID_DOCS_LANGS:
|
|
raise ValueError("Unsupported docs language")
|
|
return TECHNICAL_DOCS_ROOT / lang / entry.filename
|
|
|
|
|
|
def title_for(entry: DocsMetadata, lang: str) -> str:
|
|
return entry.zh_title if lang == "zh" else entry.en_title
|
|
|
|
|
|
def catalog_for_user(user: User | None) -> list[dict]:
|
|
items: list[dict] = []
|
|
for entry in DOCS_METADATA:
|
|
if not can_read_doc(entry, user):
|
|
continue
|
|
for lang in sorted(VALID_DOCS_LANGS):
|
|
if not doc_path_for(entry, lang).exists():
|
|
continue
|
|
items.append(
|
|
{
|
|
"slug": entry.slug,
|
|
"filename": entry.filename,
|
|
"lang": lang,
|
|
"title": title_for(entry, lang),
|
|
"group": entry.group,
|
|
"order": entry.order,
|
|
"access": entry.access,
|
|
}
|
|
)
|
|
return sorted(items, key=lambda item: (item["lang"], item["order"], item["title"]))
|