"""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 = ( '' 'ShopDB API' '' '' '' '' '' '' % (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')