Generate docs/openapi.json (3.1, 362 operations) from the API inventory via scripts/gen_openapi.py, and serve it with a self-hosted Redoc bundle at /api/docs - no CDN, works on the air-gapped box. Also serve docs/llms.txt (a concise LLM entrypoint) at /api/docs/llms.txt. New core 'docs' blueprint; staticdocs/ excluded from the naming check (vendored minified JS).
62 lines
2.1 KiB
Python
62 lines
2.1 KiB
Python
"""Interactive API docs: self-hosted Redoc over the generated OpenAPI spec.
|
|
|
|
Served at /api/docs (relative to the mount). The Redoc bundle is vendored in
|
|
staticdocs/ so this works fully offline on the air-gapped prod box - no CDN.
|
|
The spec is docs/openapi.json in the repo (regenerate with
|
|
scripts/gen_openapi.py after API changes).
|
|
"""
|
|
|
|
import os
|
|
|
|
from flask import Blueprint, Response, send_file, url_for
|
|
|
|
docs_bp = Blueprint('docs', __name__)
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
# shopdb/core/api -> repo root
|
|
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(_HERE)))
|
|
_SPEC = os.path.join(_REPO_ROOT, 'docs', 'openapi.json')
|
|
_LLMS = os.path.join(_REPO_ROOT, 'docs', 'llms.txt')
|
|
_REDOC = os.path.join(_HERE, 'staticdocs', 'redoc.standalone.js')
|
|
|
|
|
|
@docs_bp.route('/', strict_slashes=False)
|
|
def docs_index():
|
|
"""Redoc page. url_for keeps the asset URLs correct under any mount."""
|
|
spec_url = url_for('docs.openapi_spec')
|
|
redoc_url = url_for('docs.redoc_js')
|
|
page = (
|
|
'<!doctype html><html><head>'
|
|
'<title>ShopDB API</title>'
|
|
'<meta charset="utf-8">'
|
|
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
'<style>body{margin:0;padding:0}</style></head><body>'
|
|
'<redoc spec-url="%s"></redoc>'
|
|
'<script src="%s"></script>'
|
|
'</body></html>' % (spec_url, redoc_url)
|
|
)
|
|
return Response(page, mimetype='text/html')
|
|
|
|
|
|
@docs_bp.route('/openapi.json')
|
|
def openapi_spec():
|
|
"""The generated OpenAPI 3.1 spec (machine + LLM readable)."""
|
|
if not os.path.isfile(_SPEC):
|
|
return Response('{"error":"openapi.json not generated"}',
|
|
status=404, mimetype='application/json')
|
|
return send_file(_SPEC, mimetype='application/json')
|
|
|
|
|
|
@docs_bp.route('/redoc.standalone.js')
|
|
def redoc_js():
|
|
"""Vendored Redoc bundle (offline)."""
|
|
return send_file(_REDOC, mimetype='application/javascript')
|
|
|
|
|
|
@docs_bp.route('/llms.txt')
|
|
def llms_txt():
|
|
"""Concise LLM-oriented API guide."""
|
|
if not os.path.isfile(_LLMS):
|
|
return Response('llms.txt not found', status=404, mimetype='text/plain')
|
|
return send_file(_LLMS, mimetype='text/plain')
|