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,
})