129 lines
7.9 KiB
Python
129 lines
7.9 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.core.enums import UserRole
|
|
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("manual.md", "manual", "public", "Manual", 1, "智能星球使用手册", "Intelligent Planet Manual"),
|
|
DocsMetadata("quickstart.md", "quickstart", "public", "Manual", 2, "快速开始", "Quickstart"),
|
|
DocsMetadata("faq.md", "faq", "public", "Manual", 3, "常见问题", "FAQ"),
|
|
DocsMetadata("platform-data-flows.md", "platform-data-flows", "docs_developer", "Architecture", 5, "业务架构与数据流转", "Business Architecture and Data Flows"),
|
|
DocsMetadata("naming-glossary.md", "naming-glossary", "docs_developer", "Architecture", 6, "命名与术语对照", "Naming Glossary"),
|
|
DocsMetadata("earth-frontend-context.md", "earth-frontend-context", "docs_developer", "Earth", 10, "智能星球前端结构", "Intelligent Planet Frontend Context"),
|
|
DocsMetadata("earth-layer-style-reference.md", "earth-layer-style-reference", "docs_developer", "Earth", 11, "智能星球图层样式属性索引", "Intelligent Planet Layer Style Reference"),
|
|
DocsMetadata("earth-render-layer-order.md", "earth-render-layer-order", "docs_developer", "Earth", 12, "智能星球渲染图层顺序", "Intelligent Planet Render Layer Order"),
|
|
DocsMetadata("earth-satellite-footprint-policy.md", "earth-satellite-footprint-policy", "docs_developer", "Earth", 13, "智能星球卫星覆盖策略", "Intelligent Planet Satellite Footprint Policy"),
|
|
DocsMetadata("earth-bgp-context.md", "earth-bgp-context", "docs_developer", "Earth", 14, "BGP 态势上下文", "BGP Context"),
|
|
DocsMetadata("earth-interactable-usage.md", "earth-interactable-usage", "docs_developer", "Earth", 16, "智能星球可交互图标接入", "Intelligent Planet Interactable Usage"),
|
|
DocsMetadata("earth-interactable-clustering.md", "earth-interactable-clustering", "docs_developer", "Earth", 17, "智能星球可交互图标聚类策略", "Intelligent Planet Interactable Clustering"),
|
|
DocsMetadata("earth-toolbar-overlay-coordination.md", "earth-toolbar-overlay-coordination", "docs_developer", "Earth", 18, "智能星球工具栏与浮层协同", "Intelligent Planet Toolbar and Overlay Coordination"),
|
|
DocsMetadata("earth-news-sources.md", "earth-news-sources", "docs_developer", "Earth", 19, "智能星球新闻源配置", "Intelligent Planet News Source Configuration"),
|
|
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("tactile-ui-components.md", "tactile-ui-components", "docs_developer", "Frontend", 24, "Tactile UI 组件库", "Tactile UI Components"),
|
|
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("data-job-earth-sync-architecture.md", "data-job-earth-sync-architecture", "docs_developer", "Backend", 34, "数据作业与 Outbox 技术架构", "Data Jobs and Outbox Architecture"),
|
|
DocsMetadata("backend-enum-contracts.md", "backend-enum-contracts", "docs_developer", "Backend", 35, "后端枚举与字符串兼容契约", "Backend Enum and String Compatibility Contract"),
|
|
DocsMetadata("location-pipeline-development.md", "location-pipeline-development", "docs_developer", "Backend", 35, "通用位置估算管线开发说明", "Shared Location Resolution Pipeline Development Guide"),
|
|
DocsMetadata("earth-news-live-streams-collector-format.md", "earth-news-live-streams-collector-format", "docs_developer", "Backend", 36, "新闻直播采集格式", "News Live Streams Collector Format"),
|
|
DocsMetadata("docs-gatekeeper-development.md", "docs-gatekeeper-development", "docs_developer", "Backend", 37, "Docs Gatekeeper 开发说明", "Docs Gatekeeper 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, "智能星球运维手册", "Intelligent 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 == UserRole.SUPER_ADMIN.value:
|
|
return {"docs_user", "docs_developer", "docs_admin"}
|
|
if role == UserRole.ADMIN.value:
|
|
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"]))
|