Image rights / copyright detection system: SQLite store, HTTP app, search integrations (Naver, Google Custom Search, Google Cloud Vision web detection), image analysis (fingerprints, face/person detection, evidence enrichment, risk scoring), an admin/review layer, governance and retention policies, batch jobs, and a browser-based operator GUI. This baseline incorporates a full code-review remediation pass (46 fixes; 358 tests passing). Highlights: CRITICAL - Prevent evidence cascade-delete during the schema-constraint migration by disabling FK enforcement around the table rebuild. Security - Sandbox served media (neutralize stored XSS from uploaded/collected SVGs) via CSP + nosniff on the untrusted media routes. - Strip embedded EXIF/GPS from external image derivatives before they are sent to third-party APIs. - Return a clean 404 (not an uncaught StopIteration) for PATCH on an unknown provider. Correctness - LLM-summary failures no longer add +30 to the risk score. - Decode only explicit JS escapes so Korean image URLs are not mangled. - Consume search quota only after a successful request. - Naver/Google adapters map responses inside the failure boundary, so a malformed response degrades to evidence instead of crashing enrichment. - Domain-aware provider attribution; face-box IoU de-duplication; count searches (not result items); per-box crop isolation; clamp evidence confidence and Google CSE num; real submittedEpoch; and more. Robustness - Offline LLM connect fast-fails (short connect timeout) so seed/reload requests are not stalled; full read timeout preserved for generation. - Malformed numeric env vars fall back to defaults instead of crashing startup. Performance - Per-submission evidence reads (no full-table scan per rescore), audit-log LIMIT, lazy active-store lookup, hoisted timestamps. Tests - ~24 regression tests added pinning the above fixes. Runtime data (data/, outputs/, *.sqlite3, *.log), secrets (.env), and node_modules are gitignored.
47 lines
1.6 KiB
JavaScript
47 lines
1.6 KiB
JavaScript
async function hydrateMetrics() {
|
|
try {
|
|
const response = await fetch("/api/bootstrap");
|
|
if (!response.ok) return;
|
|
const payload = await response.json();
|
|
const submissions = payload.submissions || [];
|
|
const providers = payload.providers || [];
|
|
const knowledge = payload.knowledgeEntries || [];
|
|
const evidenceCount = submissions.reduce((total, submission) => total + (submission.evidence || []).length, 0);
|
|
const activeProviders = providers.filter((provider) => provider.enabled).length;
|
|
setText("metric-submissions", submissions.length);
|
|
setText("metric-evidence", evidenceCount);
|
|
setText("metric-knowledge", knowledge.length);
|
|
setText("metric-providers", `${activeProviders}/${providers.length || 0}`);
|
|
} catch {
|
|
// The page still works as a static artifact if the API is unavailable.
|
|
}
|
|
}
|
|
|
|
function setText(id, value) {
|
|
const target = document.getElementById(id);
|
|
if (target) target.textContent = String(value);
|
|
}
|
|
|
|
function mountRevealObserver() {
|
|
const targets = document.querySelectorAll(".reveal");
|
|
if (!("IntersectionObserver" in window)) {
|
|
targets.forEach((target) => target.classList.add("visible"));
|
|
return;
|
|
}
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (!entry.isIntersecting) return;
|
|
entry.target.classList.add("visible");
|
|
observer.unobserve(entry.target);
|
|
});
|
|
},
|
|
{ rootMargin: "0px 0px -10% 0px", threshold: 0.12 },
|
|
);
|
|
targets.forEach((target) => observer.observe(target));
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
mountRevealObserver();
|
|
hydrateMetrics();
|
|
});
|