feat: automate live import timer and add legal source attribution page

This commit is contained in:
Oliver 2026-02-15 15:55:04 +01:00
parent 9b3fe95683
commit da7196fa78
No known key found for this signature in database
5 changed files with 97 additions and 20 deletions

View file

@ -98,6 +98,15 @@ Admin-Setup (nur beim ersten Start):
- Kein Device Fingerprinting
- Missbrauchsschutz nur ueber `hashed_device = HMAC_SHA256(device_token, server_salt)`
## Attribution (Rechtliches)
Unter `Attribution` muessen die verwendeten Datenquellen und Lizenzen klar genannt werden:
- OpenStreetMap-Mitwirkende + ODbL-Hinweis
- Geocoding-Dienst (Nominatim) als Quelle
- Jede OpenData-Quelle mit Lizenzhinweis (z. B. Datenlizenz Deutschland Zero/By)
In der App ist dafuer die Seite `quellen.html` vorgesehen.
## Produktion
- `STAYSENSE_SERVER_SALT` zwingend als geheimes Env setzen

View file

@ -8,4 +8,4 @@ Type=oneshot
User=www-data
Group=www-data
WorkingDirectory=/opt/staysense/backend
ExecStart=/usr/bin/python3 /opt/staysense/backend/run_import_jobs.py --config /opt/staysense/docs/open_data_sources.json --prune-legacy
ExecStart=/usr/bin/python3 /opt/staysense/backend/run_import_jobs.py --config /opt/staysense/docs/open_data_sources_nrw_live.json --prune-legacy

View file

@ -103,21 +103,6 @@ function initialize() {
btn.addEventListener("click", () => sendSignal(btn.dataset.signal));
});
document.getElementById("show-attribution").addEventListener("click", (e) => {
e.preventDefault();
legalOutputEl.textContent = "Kartendaten: OpenStreetMap-Mitwirkende (ODbL). Open Data NRW: jeweilige Quellen mit Namensnennung.";
});
document.getElementById("show-privacy").addEventListener("click", (e) => {
e.preventDefault();
legalOutputEl.textContent = "Kein Login, keine IP-Speicherung, kein Fingerprinting. Missbrauchsschutz via gehashtem lokalem Zufallstoken (HMAC-SHA256).";
});
document.getElementById("show-imprint").addEventListener("click", (e) => {
e.preventDefault();
legalOutputEl.textContent = "MVP-Hinweis: Impressum im Produktionsbetrieb verpflichtend mit Anbieterkennzeichnung.";
});
adminSetupSubmitEl.addEventListener("click", adminBootstrap);
adminLoginSubmitEl.addEventListener("click", adminLogin);
adminLogoutEl.addEventListener("click", adminLogout);

View file

@ -182,11 +182,11 @@
<h3>Rechtliches</h3>
<ul class="legal">
<li><a href="#" id="show-attribution">Attribution (OSM/ODbL)</a></li>
<li><a href="#" id="show-privacy">Datenschutz</a></li>
<li><a href="#" id="show-imprint">Impressum</a></li>
<li><a href="quellen.html" id="show-attribution">Attribution / Quellen</a></li>
<li><a href="https://vanityontour.de/datenschutz/" id="show-privacy" target="_blank" rel="noopener noreferrer">Datenschutz</a></li>
<li><a href="https://vanityontour.de/impressum/" id="show-imprint" target="_blank" rel="noopener noreferrer">Impressum</a></li>
</ul>
<div id="legal-output" class="legal-output"></div>
<div id="legal-output" class="legal-output">Attribution: Details und Datenquellen siehe „Attribution / Quellen“.</div>
</section>
</main>

83
src/quellen.html Normal file
View file

@ -0,0 +1,83 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>StaySense Quellen & Attribution</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="background haze-a"></div>
<div class="background haze-b"></div>
<header class="top">
<h1>Quellen & Attribution</h1>
<p>Transparenz zu Datenquellen, Lizenzen und rechtlichen Hinweisen.</p>
<small><a href="/" style="color: inherit">Zurück zur App</a></small>
</header>
<main class="grid" style="max-width: 980px">
<section class="panel">
<h2>Attribution (Pflichtangaben)</h2>
<ul class="legal">
<li>OpenStreetMap-Mitwirkende (ODbL): <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer">openstreetmap.org/copyright</a></li>
<li>Nominatim-Geocoding (OSM Foundation): <a href="https://nominatim.org/release-docs/develop/api/Overview/" target="_blank" rel="noopener noreferrer">API-Dokumentation</a></li>
<li>OpenData NRW/Kommunen: jeweilige Datensatz-Lizenz und Namensnennung je Quelle</li>
</ul>
</section>
<section class="panel">
<h2>Live-Datenquellen (API)</h2>
<p class="small" id="source-status">Lade Datenquellen ...</p>
<div id="source-list" class="admin-list">-</div>
</section>
<section class="panel">
<h2>Rechtliches</h2>
<ul class="legal">
<li><a href="https://vanityontour.de/impressum/" target="_blank" rel="noopener noreferrer">Impressum</a></li>
<li><a href="https://vanityontour.de/datenschutz/" target="_blank" rel="noopener noreferrer">Datenschutz</a></li>
</ul>
</section>
</main>
<script>
const DEFAULT_API_BASE =
window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"
? "http://127.0.0.1:8787"
: "/api";
const API_BASE = window.STAYSENSE_API_BASE || DEFAULT_API_BASE;
async function loadSources() {
const statusEl = document.getElementById("source-status");
const listEl = document.getElementById("source-list");
try {
const response = await fetch(`${API_BASE}/health`, { cache: "no-store" });
if (!response.ok) {
throw new Error("health_failed");
}
const payload = await response.json();
const sources = payload.sources || [];
if (!sources.length) {
statusEl.textContent = "Keine Quellenmetadaten vorhanden.";
listEl.textContent = "Keine Einträge.";
return;
}
statusEl.textContent = `Geladen: ${sources.length} Quelle(n).`;
listEl.innerHTML = "";
sources.forEach((src) => {
const div = document.createElement("div");
div.className = "admin-list-item";
div.textContent = `${src.source_name} | ${src.record_count} records | ${src.imported_at} | ${src.notes}`;
listEl.appendChild(div);
});
} catch {
statusEl.textContent = "Quellen konnten nicht geladen werden.";
listEl.textContent = "API nicht erreichbar.";
}
}
loadSources();
</script>
</body>
</html>