|
import logging
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
|
from devplacepy.templating import templates
|
|
from devplacepy.utils import get_current_user, not_found, is_admin as user_is_admin
|
|
from devplacepy.seo import base_seo_context, site_url, website_schema
|
|
from devplacepy.docs_api import api_doc_pages, render_group, build_services_group
|
|
from devplacepy.services.manager import service_manager
|
|
from devplacepy import docs_search, docs_export, docs_live
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
SECTION_GENERAL = "General"
|
|
SECTION_API = "API"
|
|
SECTION_ADMIN = "Administration"
|
|
SECTION_DEVII = "Devii internals"
|
|
SECTION_PROD = "Production"
|
|
|
|
DOCS_PAGES = [
|
|
# General - how to use the site and Devii (everyone)
|
|
{"slug": "index", "title": "Overview", "kind": "prose", "section": SECTION_GENERAL},
|
|
{"slug": "devii", "title": "Devii Assistant", "kind": "prose", "section": SECTION_GENERAL},
|
|
{"slug": "dashboard", "title": "Your dashboard", "kind": "live", "section": SECTION_GENERAL},
|
|
# API - developer reference (everyone); admin-only groups are routed to Administration below
|
|
{"slug": "authentication", "title": "Authentication", "kind": "prose", "section": SECTION_API},
|
|
*[{**page, "section": (SECTION_ADMIN if page.get("admin") else SECTION_API)} for page in api_doc_pages()],
|
|
# Devii internals - technical reference for the Devii assistant (admins only)
|
|
{"slug": "devii-internals", "title": "Devii internals", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
{"slug": "devii-architecture", "title": "Architecture and sessions", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
{"slug": "devii-tools", "title": "Tools and scopes", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
{"slug": "devii-data", "title": "Data and persistence", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
{"slug": "devii-security", "title": "Security and limits", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
{"slug": "devii-config", "title": "Configuration and CLI", "kind": "prose", "admin": True, "section": SECTION_DEVII},
|
|
# Production - deployment and operations reference (admins only)
|
|
{"slug": "production", "title": "Production overview", "kind": "prose", "admin": True, "section": SECTION_PROD},
|
|
{"slug": "production-deploy", "title": "Deploy and update", "kind": "prose", "admin": True, "section": SECTION_PROD},
|
|
{"slug": "production-nginx", "title": "nginx and networking", "kind": "prose", "admin": True, "section": SECTION_PROD},
|
|
{"slug": "production-concurrency", "title": "Multi-worker and concurrency", "kind": "prose", "admin": True, "section": SECTION_PROD},
|
|
]
|
|
_PAGES_BY_SLUG = {page["slug"]: page for page in DOCS_PAGES}
|
|
|
|
|
|
@router.get("/docs")
|
|
async def docs_root(request: Request):
|
|
return RedirectResponse(url="/docs/index.html", status_code=302)
|
|
|
|
|
|
def _export_context(request: Request):
|
|
user = get_current_user(request)
|
|
is_admin = user_is_admin(user)
|
|
return (
|
|
is_admin,
|
|
site_url(request),
|
|
user.get("username") if user else "",
|
|
user.get("api_key") if user else "",
|
|
)
|
|
|
|
|
|
@router.get("/docs/download.md")
|
|
async def docs_download_md(request: Request):
|
|
is_admin, base, username, api_key = _export_context(request)
|
|
markdown = docs_export.build_markdown(is_admin, base, username, api_key)
|
|
return Response(
|
|
content=markdown,
|
|
media_type="text/markdown; charset=utf-8",
|
|
headers={"Content-Disposition": 'attachment; filename="devplace-docs.md"'},
|
|
)
|
|
|
|
|
|
@router.get("/docs/download.html")
|
|
async def docs_download_html(request: Request):
|
|
is_admin, base, username, api_key = _export_context(request)
|
|
markdown = docs_export.build_markdown(is_admin, base, username, api_key)
|
|
return HTMLResponse(
|
|
content=docs_export.build_html(markdown),
|
|
headers={"Content-Disposition": 'attachment; filename="devplace-docs.html"'},
|
|
)
|
|
|
|
|
|
@router.get("/docs/{slug}.html", response_class=HTMLResponse)
|
|
async def docs_page(request: Request, slug: str):
|
|
user = get_current_user(request)
|
|
is_admin = user_is_admin(user)
|
|
base = site_url(request)
|
|
visible_pages = [p for p in DOCS_PAGES if not p.get("admin") or is_admin]
|
|
|
|
if slug == "search":
|
|
query = request.query_params.get("q", "")
|
|
results = docs_search.search(query, user=user, is_admin=is_admin)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Search - Documentation",
|
|
description="Search the DevPlace developer documentation.",
|
|
robots="noindex",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Docs", "url": "/docs/index.html"},
|
|
{"name": "Search", "url": "/docs/search.html"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return templates.TemplateResponse(request, "docs_base.html", {
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"pages": visible_pages,
|
|
"current": "search",
|
|
"kind": "search",
|
|
"page_title_doc": "Search",
|
|
"search_query": query,
|
|
"search_results": results,
|
|
"base": base,
|
|
})
|
|
|
|
page = _PAGES_BY_SLUG.get(slug)
|
|
if not page:
|
|
raise not_found("Documentation page not found")
|
|
if page.get("admin") and not is_admin:
|
|
raise not_found("Documentation page not found")
|
|
if page["kind"] == "live":
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title=f"{page['title']} - Documentation",
|
|
description="Your live DevPlace stats, activity, and site data.",
|
|
robots="noindex",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Docs", "url": "/docs/index.html"},
|
|
{"name": page["title"], "url": f"/docs/{slug}.html"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return templates.TemplateResponse(request, "docs_base.html", {
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"pages": visible_pages,
|
|
"current": slug,
|
|
"kind": "live",
|
|
"facts": docs_live.build_live_facts(user),
|
|
"page_title_doc": page["title"],
|
|
"base": base,
|
|
})
|
|
if page.get("dynamic"):
|
|
group = build_services_group(service_manager.describe_all(), base)
|
|
else:
|
|
group = render_group(slug, base, user.get("username") if user else "", user.get("api_key") if user else "")
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title=f"{page['title']} - Documentation",
|
|
description="DevPlace developer documentation.",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Docs", "url": "/docs/index.html"},
|
|
{"name": page["title"], "url": f"/docs/{slug}.html"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return templates.TemplateResponse(request, "docs_base.html", {
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"pages": visible_pages,
|
|
"current": slug,
|
|
"kind": page["kind"],
|
|
"group": group,
|
|
"page_title_doc": page["title"],
|
|
"base": base,
|
|
})
|