e93e68db7e
- Lock down /media/: deny state.json, DBs, sessions; allowlist extensions incl. archives/docs (M-1) - parse_bool() fixes; HEAD 404; shutdown drains queue; range edge cases (M-3, M-4, M-7, M-8) - int() coercion -> 400; no filesystem paths in errors; path-only access log (M-19, L-1) - Security headers, QR TTL 60s, trusted-host allowlist, legacy add/remove via update() (L-4, L-5, L-6, L-8) - Clean continuous channels on import and migration; restart-during-drain; tombstone managers (F-1, F-3, F-4) - Durability: fsync + unique tmp + stale sweep + 0600/0700 perms (M-10, M-18) - Jobs run on dedicated loop thread; set_scrape_media passthrough; media chunked; state throttled; exact media file reuse; honest scrape failure status (M-11, M-12, M-13, M-14) - Health aggregates per-account; legacy GETs delegate post-migration (M-15, M-9) - k8s: runAsNonRoot 1000 + resource limits, no readOnlyRootFilesystem (M-16) - UI: dropped-invalid and credentials-reentry toasts; swagger XSS-safe (F-2, L-9, L-2) - CI: non-blocking pip-audit job in both workflows (L-3) - 50 tests passing; REVIEW.md updated (C-1/M-20 won't fix: local-only by design)
59 lines
1.9 KiB
JavaScript
59 lines
1.9 KiB
JavaScript
async function loadSpec() {
|
|
const response = await fetch('/openapi.json');
|
|
const spec = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(spec.error || 'Failed to load OpenAPI spec');
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
function methodPayload(operation) {
|
|
const body = operation.requestBody?.content?.['application/json']?.schema;
|
|
if (!body) return '';
|
|
return JSON.stringify(body.example || body.properties || body, null, 2);
|
|
}
|
|
|
|
function renderSpec(spec) {
|
|
const root = document.getElementById('api-docs');
|
|
const sectionTemplate = document.getElementById('api-section-template');
|
|
const methodTemplate = document.getElementById('api-method-template');
|
|
root.innerHTML = '';
|
|
|
|
Object.entries(spec.paths || {}).forEach(([path, methods]) => {
|
|
const section = sectionTemplate.content.firstElementChild.cloneNode(true);
|
|
section.querySelector('.api-path').textContent = path;
|
|
const methodsRoot = section.querySelector('.api-methods');
|
|
|
|
Object.entries(methods).forEach(([method, operation]) => {
|
|
const node = methodTemplate.content.firstElementChild.cloneNode(true);
|
|
node.querySelector('.api-verb').textContent = method.toUpperCase();
|
|
node.querySelector('.api-summary').textContent = operation.summary || '';
|
|
node.querySelector('.api-description').textContent = operation.description || '';
|
|
|
|
const body = methodPayload(operation);
|
|
const bodyNode = node.querySelector('.api-body');
|
|
if (body) {
|
|
bodyNode.textContent = body;
|
|
} else {
|
|
bodyNode.remove();
|
|
}
|
|
|
|
methodsRoot.appendChild(node);
|
|
});
|
|
|
|
root.appendChild(section);
|
|
});
|
|
}
|
|
|
|
loadSpec()
|
|
.then(renderSpec)
|
|
.catch((error) => {
|
|
console.error(error);
|
|
const docsEl = document.getElementById('api-docs');
|
|
docsEl.textContent = '';
|
|
const section = document.createElement('section');
|
|
section.className = 'panel';
|
|
section.textContent = error.message;
|
|
docsEl.appendChild(section);
|
|
});
|