insert copyright headers for images
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Bilder, Tonaufnahmen und Nutzungsrechte
|
||||
|
||||
Stand: 4. Oktober 2026. Grundlage der Änderungen: Repository-Commit `7409072`.
|
||||
|
||||
## Ergebnis der Recherche
|
||||
|
||||
Die Betreiber können Nutzungsrechte erhalten; das Urheberrecht selbst bleibt
|
||||
bei den Urhebern (§§ 29 und 31 UrhG). Eine Nutzungserlaubnis und die
|
||||
Urheberbezeichnung sind verschiedene Fragen. Die Übertragung von Nutzungsrechten
|
||||
beseitigt das Recht auf Anerkennung der Urheberschaft aus § 13 UrhG nicht.
|
||||
Ein pauschaler Verzicht auf Namensnennung ist deshalb nicht Bestandteil dieses
|
||||
Entwurfs. Ein BGH-Fall zu professionellen Stockfotografen begründet keine
|
||||
allgemeine Erlaubnis, die Namen von Kindern oder anderen Beitragenden wegzulassen.
|
||||
|
||||
Der eigenständig formulierte Entwurf in `public/index.html` begrenzt die
|
||||
Rechteeinräumung auf Wegwichtel: Prüfung, Bereitstellung der Strecken, Anzeige,
|
||||
Wiedergabe, technisch notwendige Anpassungen und die vorgesehene Offline-Nutzung.
|
||||
Das einfache Nutzungsrecht schließt den Urheber nicht von eigener Nutzung aus.
|
||||
Eine räumliche Beschränkung wäre für öffentlich erreichbare Inhalte unpraktisch;
|
||||
inhaltlich bleibt die Erlaubnis auf diesen App-Zweck begrenzt. Werbung außerhalb
|
||||
der App und eigenständiger Verkauf sind nicht umfasst.
|
||||
|
||||
Die Datenschutzerklärung informiert getrennt über personenbezogene Aufnahmen,
|
||||
Öffentlichkeit, Speicherung, Einwilligungen und Widerruf. Sie ist kein
|
||||
Lizenzvertrag. Eine Erlaubnis des Fotografen ersetzt nicht die Rechte einer
|
||||
abgebildeten Person; bei Tonaufnahmen müssen auch Rechte an Musik, Text,
|
||||
Darbietung und Aufnahme geklärt sein. Bei minderjährigen Urhebern ist die
|
||||
Zustimmung der gesetzlichen Vertreter zu prüfen (§ 107 BGB).
|
||||
|
||||
## Umsetzung in der App
|
||||
|
||||
Unter Informationen gibt es die neue Nutzungsvereinbarung. Sie und ihre
|
||||
Dialogüberschrift bleiben ebenso wie Impressum und Datenschutz statisch auf
|
||||
Deutsch. Nur die Menübeschriftung und der verständliche Hinweis im Editor
|
||||
werden übersetzt. Beim Streckenformular kann die Vereinbarung vor dem
|
||||
Einsenden geöffnet werden. Das lokale Speichern räumt keine Rechte ein.
|
||||
|
||||
Der Text ist ein **prüfbarer Entwurf**, keine Zusicherung einer rechtssicheren
|
||||
AGB-Regelung. Es wurde noch kein System implementiert, das Zustimmung, Version
|
||||
der Vereinbarung und Urheberbezeichnung je Aufnahme erfasst und serverseitig
|
||||
prüft. Der Hinweis im Editor allein ersetzt diesen Nachweis nicht. Bestehende
|
||||
Aufnahmen erhalten durch die Codeänderung keine nachträgliche Lizenz.
|
||||
Die notwendigen Arbeiten stehen in
|
||||
[TODO-MEDIENRECHTE.md](agents/TODO-MEDIENRECHTE.md).
|
||||
|
||||
Vor verbindlicher Verwendung sind insbesondere diese Platzhalter zu ersetzen:
|
||||
|
||||
- vollständiger Name und Anschrift des tatsächlichen Betreibers als Lizenznehmer;
|
||||
- Kontakt für Rechtefragen, Widerruf und Entfernung;
|
||||
- Rechtsgrundlagen, Empfänger, Hostinganbieter sowie Lösch- und Sicherungsfristen.
|
||||
|
||||
Außerdem müssen die Texte und das Zustimmungsverfahren zum tatsächlichen
|
||||
Betriebsmodell der Schule passen und rechtlich geprüft werden. Die bereits
|
||||
vorhandenen Kontaktangaben im Impressum wurden nicht verändert. Die frühere
|
||||
pauschale Rechtezuordnung zur Schule wurde durch die Trennung von Urheberschaft
|
||||
und Nutzungsrechten ersetzt.
|
||||
|
||||
## HTTP-Rechtehinweise
|
||||
|
||||
`X-Copyright` ist ein HTTP-Antwortheader, kein HTML-Element. Die Werte stehen
|
||||
zentral in `src/services/copyright.js` und enthalten für zuverlässige
|
||||
Übertragung nur ASCII-Zeichen.
|
||||
|
||||
- Hochgeladene Streckenbilder: Hinweis auf die Rechteeinräumung **unter Zustimmung**
|
||||
zur Nutzungsvereinbarung und den Fortbestand der Urheberrechte. Der Wortlaut
|
||||
beschreibt die Regel; er ist kein Beweis, dass eine konkrete Person tatsächlich
|
||||
berechtigt war oder zugestimmt hat. Das gilt auch für alte Uploads.
|
||||
- Dateien unter `public/images/group-symbols/`: eigener Quellenhinweis auf Envato
|
||||
mit dem vom Betreiber angegebenen Eintrag.
|
||||
- Andere statische Bilder einschließlich SVG und ICO: allgemeiner Hinweis auf
|
||||
die jeweilige Lizenz; keine Behauptung einer Lizenz durch Nutzer-Uploads.
|
||||
- JSON-Metadaten, HTML, CSS und JavaScript erhalten keinen Bild-Rechteheader.
|
||||
|
||||
Zusätzliche Hinweise sind getrennt: `X-Copyright-Notice` erklärt bei allen
|
||||
Bildantworten die Grenzen des technischen Hinweises; `X-Copyright-Source`
|
||||
enthält bei den Gruppensymbolen den Envato-Link. Persönliche Kontaktdaten
|
||||
werden dabei nicht öffentlich in Headern ausgegeben.
|
||||
|
||||
Die Hinweise werden auch für HEAD, Teilabrufe und unveränderte Antworten mit
|
||||
Status 304 ausgegeben. Dieselben Node-Endpunkte werden über den geschützten
|
||||
Moderationspfad verwendet. Leitet Nginx statische Dateien unmittelbar aus,
|
||||
muss der Header auch dort konfiguriert werden; die Node-Einstellungen greifen
|
||||
nur für Antworten, die Node ausliefert.
|
||||
|
||||
Der Header ersetzt **keine sichtbare Urheberbezeichnung**, macht ein Bild
|
||||
nicht kopiergeschützt und räumt Besuchern keine freie Weiterverwendung ein.
|
||||
Die vorhandene Offline-Speicherung bewahrt Antwortheader. Bereits gespeicherte
|
||||
App-Kopien müssen neu gespeichert werden, um die neuen Hinweise zu enthalten.
|
||||
|
||||
## Envato-Gruppensymbole
|
||||
|
||||
Quelle laut Betreiber:
|
||||
https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077
|
||||
|
||||
Der Eintrag war beim Rechercheabruf nicht zugänglich. Deshalb wurde kein
|
||||
Urhebername oder individueller Lizenzstatus aus dem Eintrag behauptet.
|
||||
„Quelle: Envato“ bezeichnet die Plattform; es ist keine Behauptung, Envato sei
|
||||
selbst Urheber der Grafik. Die tatsächliche Lizenz und der Lizenznachweis für
|
||||
Wegwichtel müssen vom Betreiber geprüft und aufbewahrt werden. Auflagen des
|
||||
konkreten Downloads sind auch bei öffentlich ausgelieferten PNGs und ihrer
|
||||
Offline-Nutzung zu berücksichtigen.
|
||||
|
||||
Die vorhandenen Grafikdateien wurden nicht verändert und gehören nicht zum
|
||||
Änderungsarchiv. Der Envato-Link steht zusätzlich bei den Bildquellen im
|
||||
Impressum.
|
||||
|
||||
## Quellen
|
||||
|
||||
Gesetzliche Grundlagen:
|
||||
|
||||
- [§ 13 UrhG – Anerkennung der Urheberschaft](https://www.gesetze-im-internet.de/urhg/__13.html)
|
||||
- [§ 29 UrhG – Rechtsgeschäfte über das Urheberrecht](https://www.gesetze-im-internet.de/urhg/__29.html)
|
||||
- [§ 31 UrhG – Einräumung von Nutzungsrechten](https://www.gesetze-im-internet.de/urhg/__31.html)
|
||||
- [§ 305 BGB – Einbeziehung von AGB](https://www.gesetze-im-internet.de/bgb/__305.html)
|
||||
- [§ 107 BGB – Zustimmung bei Minderjährigen](https://www.gesetze-im-internet.de/bgb/__107.html)
|
||||
|
||||
Rechtsinformationen und Datenschutz:
|
||||
|
||||
- [eRecht24: Nutzungsrechte](https://www.e-recht24.de/urheberrecht/13261-nutzungsrechte.html)
|
||||
- [IT-Recht Kanzlei: Bildnachweise im Impressum](https://www.it-recht-kanzlei.de/urheber-foto-bildnachweis-impressum.html)
|
||||
- [WBS.LEGAL: BGH zum Verzicht auf Namensnennung in AGB](https://www.wbs.legal/urheberrecht/bgh-verzicht-auf-namensnennung-des-urhebers-durch-agb-ist-grundsaetzlich-moeglich-69950/)
|
||||
- [VG Bild-Kunst: Nutzungsrechte](https://www.bildkunst.de/lizenzen/bildrechte_klaeren/nutzungsrechte/)
|
||||
- [LfDI Baden-Württemberg: Veröffentlichung von Fotos](https://www.baden-wuerttemberg.datenschutz.de/faq-veroeffentlichung-von-fotos-speziell-fuer-vereine/)
|
||||
- [Envato: Lizenz-FAQ](https://help.elements.envato.com/hc/en-us/articles/360000629346-Envato-Elements-License-FAQ)
|
||||
|
||||
Die fremden Muster wurden nicht kopiert. Der App-Entwurf ist für den hier
|
||||
beschriebenen Zweck eigenständig formuliert.
|
||||
@@ -26,6 +26,10 @@ Die in der Anwendung enthaltenen Texte sind Vorlagen und ersetzen keine Prüfung
|
||||
- Einwilligungen der Sorgeberechtigten für erkennbare Kinder
|
||||
- Freigaben für Sprachaufnahmen und Namensnennungen
|
||||
- dokumentierte Nutzungsrechte an Bildern, Texten, Tonaufnahmen und Routendaten
|
||||
- geprüfte Nutzungsvereinbarung mit identifiziertem Betreiber und belegbarer Zustimmung
|
||||
- passende Urheberbezeichnung je Bild und Tonaufnahme, sichtbar bei der Aufnahme
|
||||
- getrennte Nachweise für personenbezogene Einwilligungen und urheberrechtliche Lizenzen
|
||||
- konkreter Lizenznachweis für die Envato-Gruppensymbole
|
||||
- redaktionelle Prüfung vor Veröffentlichung
|
||||
- festgelegtes Verfahren für Korrektur- und Löschanfragen
|
||||
|
||||
@@ -41,3 +45,7 @@ Die in der Anwendung enthaltenen Texte sind Vorlagen und ersetzen keine Prüfung
|
||||
## Konfiguration der Vorlagentexte
|
||||
|
||||
In `public/index.html` sind alle mit eckigen Klammern gekennzeichneten Platzhalter zu ersetzen. Nicht benötigte Abschnitte sind zu entfernen; zusätzliche gesetzliche oder organisatorische Pflichtangaben sind zu ergänzen.
|
||||
|
||||
Die Formulierungen und offenen Schritte stehen in
|
||||
[Bilder-und-Ton-Nutzungsrechte.md](Bilder-und-Ton-Nutzungsrechte.md) und
|
||||
[TODO-MEDIENRECHTE.md](agents/TODO-MEDIENRECHTE.md).
|
||||
|
||||
@@ -56,7 +56,7 @@ Die App richtet sich an Grundschulkinder und Menschen ohne technische Vorkenntni
|
||||
- Alle eigenen Bedienungstexte liegen zentral in der Klasse `WegwichtelTexts`
|
||||
in `public/js/texts-de.js`. Keine neuen sichtbaren Texte direkt in HTML oder
|
||||
Funktionscode schreiben.
|
||||
- Ausnahme: Impressum und Datenschutzerklärung bleiben ausschließlich statisch
|
||||
- Ausnahme: Impressum, Datenschutzerklärung und Nutzungsvereinbarung bleiben ausschließlich statisch
|
||||
auf Deutsch in `public/index.html` und tragen `lang="de"`. Ihre Texte und
|
||||
Kontaktangaben gehören nicht in Sprachdateien und erhalten keine i18n-Attribute.
|
||||
Auch ihre Dialogüberschriften bleiben beim Sprachwechsel auf Deutsch.
|
||||
|
||||
+2
-2
@@ -35,9 +35,9 @@ HTML verwendet `data-i18n`, `data-i18n-title`, `data-i18n-aria-label`,
|
||||
stehen Beschriftungen bereits vor dem Laden der Skripte bereit. Nach Änderungen
|
||||
die erzeugte `public/index.html` mit ins Repository übernehmen.
|
||||
|
||||
Impressum und Datenschutzerklärung sind davon ausgenommen. Ihre rechtlichen
|
||||
Impressum, Datenschutzerklärung und Nutzungsvereinbarung sind davon ausgenommen. Ihre rechtlichen
|
||||
Texte und Kontaktangaben stehen ausschließlich statisch auf Deutsch in
|
||||
`public/index.html`, ohne i18n-Attribute und mit `lang="de"` an beiden Artikeln.
|
||||
`public/index.html`, ohne i18n-Attribute und mit `lang="de"` an den rechtlichen Artikeln.
|
||||
Änderungen an diesen Angaben direkt im HTML vornehmen. Auch die zugehörigen
|
||||
Dialogüberschriften bleiben auf Deutsch. Die Menüeinträge und die übrige
|
||||
Bedienoberfläche werden weiterhin übersetzt.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# TODO: Einräumung und Nachweis von Medienrechten
|
||||
|
||||
Ausgangspunkt: [Recherche und umgesetzte Hinweise](../Bilder-und-Ton-Nutzungsrechte.md).
|
||||
Die statischen Rechtstexte und X-Copyright sind eingebunden; ein belegbares
|
||||
Zustimmungs- und Namensnennungssystem ist noch nicht umgesetzt.
|
||||
|
||||
## Vor verbindlicher Verwendung und Freigabe zu erledigen
|
||||
|
||||
1. Den tatsächlichen Lizenznehmer, Rechtekontakt, Rechtsgrundlagen und Fristen
|
||||
ergänzen. Den Entwurf und die Zustimmung für Kinder rechtlich prüfen lassen.
|
||||
2. Die relevante Vereinbarung vor dem Einsenden zugänglich machen und eine
|
||||
nicht vorausgewählte, ausdrückliche Zustimmung einholen. Eine bloße
|
||||
Datenschutzerklärung und eine zufällige Gerätekennung sind kein Rechtenachweis.
|
||||
3. Je Bild und Tonaufnahme die gewünschte Urheberbezeichnung und die Erklärung
|
||||
über Berechtigung erfassen. Nicht automatisch den Streckenersteller mit dem
|
||||
Fotografen, Sprecher, Musiker oder Rechteinhaber gleichsetzen.
|
||||
4. Die Urheberbezeichnung direkt bei der jeweiligen Aufnahme sichtbar anzeigen,
|
||||
auch bei Offline-Nutzung. Ein Header oder ein allgemeines Impressum ersetzt
|
||||
die vereinbarte Bild-/Tonzuordnung nicht. Ein Verzicht darf nur auf gesonderter,
|
||||
belegbarer Vereinbarung beruhen, nicht auf einem leeren Namensfeld.
|
||||
5. Zustimmung auch auf dem Server prüfen: Version der Vereinbarung,
|
||||
serverseitiger Zeitpunkt, konkretes Bild/konkrete Tonaufnahme und berechtigte
|
||||
zuordnungsfähige Person. Nicht auf eine ausschließlich clientseitige Checkbox
|
||||
vertrauen. Eine kleine Rechte-Tabelle mit referenzierten Medien oder
|
||||
unveränderlichen Dateihashes evaluieren; Zugriff auf Belege beschränken.
|
||||
Nicht pauschal zusätzliche Gerätefingerprints oder IP-Adressen sammeln.
|
||||
6. Neue und ersetzte Dateien neu erfassen; reine Beschriftungsänderungen von
|
||||
neuer Rechteeinräumung unterscheiden. Bild-, Audio-, JSON-, API- und
|
||||
Python-Werkzeug-Uploads sowie erneutes Einsenden berücksichtigen.
|
||||
7. Bestandsmedien separat prüfen. Fehlenden Rechtebeleg nicht rückwirkend aus
|
||||
einem Upload oder aus dem neuen Header konstruieren. Veröffentlichung ohne
|
||||
erforderlichen Beleg sperren oder nachträglich eine echte Erlaubnis einholen.
|
||||
8. Widerruf, begründete Beschwerden und Beendigung der Bereitstellung behandeln;
|
||||
personenbezogene Einwilligung und Lizenz nicht gleichsetzen. Papierkorb,
|
||||
Sicherungsfristen und verbleibende Offline-Kopien berücksichtigen.
|
||||
9. Für die Envato-Gruppensymbole Autor, konkrete Lizenz und Nachweis prüfen.
|
||||
Die Quelle ist vom Betreiber benannt; der einzelne Eintrag war nicht abrufbar.
|
||||
Quelle: https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077
|
||||
|
||||
## Bereits umgesetzt und beizubehalten
|
||||
|
||||
- Rechtstexte ausschließlich statisch auf Deutsch; Bedienhinweise in Deutsch
|
||||
und Englisch mit semantischen i18n-Schlüsseln.
|
||||
- Unterschiedliche Header für Nutzerbilder, Envato-Gruppensymbole und andere
|
||||
App-Bilder; keine Upload-Lizenzbehauptung für statische Bibliotheksdateien.
|
||||
- Headertexte bleiben ein Hinweis, kein Eigentums-, Zustimmungs- oder
|
||||
Kopierschutznachweis. Bei später belegten Zustimmungen kann der individuelle
|
||||
Header um Urheberbezeichnung und Vertragsversion ergänzt werden; persönliche
|
||||
Kontaktdaten dürfen dabei nicht öffentlich werden.
|
||||
- Tests prüfen Bildantworten einschließlich HEAD, Range und 304 sowie die
|
||||
Nicht-Übersetzung rechtlicher Seiten. Schemaänderungen müssen zusätzlich
|
||||
mit der Laufzeitdatenbankprüfung abgestimmt werden.
|
||||
+33
-2
@@ -19,6 +19,7 @@
|
||||
<nav id="info-dropdown" class="info-dropdown" aria-label="Hilfe und Informationen" hidden data-i18n-aria-label="info.dropdown-label">
|
||||
<button type="button" data-info-target="privacy" data-i18n="info.privacy">Datenschutzerklärung</button>
|
||||
<button type="button" data-info-target="imprint" data-i18n="info.imprint">Impressum</button>
|
||||
<button type="button" data-info-target="terms" data-i18n="info.terms">Nutzungsvereinbarung</button>
|
||||
<button type="button" data-info-target="technology" data-i18n="info.storage">Speichern und Berechtigungen</button>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -342,7 +343,13 @@
|
||||
<p>Wegwichtel verwendet keine Cookies. Es zählt keine Besuche für Werbung und verfolgt nicht, welche anderen Seiten du besuchst. Auf deinem Gerät wird gespeichert, ob du „Meinen Fortschritt merken“ eingeschaltet hast.</p>
|
||||
<h3>7. Inhalte von Schülerinnen und Schülern</h3>
|
||||
<p>Bilder, Texte und Tonaufnahmen dürfen nur geteilt werden, wenn das erlaubt ist. Wenn Kinder auf Bildern oder in Aufnahmen erkannt werden können, müssen die Regeln der Schule beachtet werden. Auch die Eltern oder anderen Sorgeberechtigten müssen die nötige Erlaubnis geben.</p>
|
||||
<h3>8. Deine Rechte</h3>
|
||||
<h3>8. Eingesandte Bilder und Tonaufnahmen</h3>
|
||||
<p>Wenn du „Zur Prüfung senden“ wählst, erhalten die Betreiber deine Streckenangaben, Bilder und Tonaufnahmen sowie die zugehörige Gerätekennung. Sie verwenden diese Angaben zur Prüfung und Bereitstellung deiner Strecke. Freigegebene Bilder und Tonaufnahmen können im Internet weltweit angesehen oder angehört werden. Wer eine Strecke für unterwegs speichert, kann auch ihre Bilder und Tonaufnahmen auf seinem Gerät speichern.</p>
|
||||
<p>Die Nutzungsvereinbarung regelt, welche Nutzungsrechte du für diese Zwecke einräumst. Die Urheberrechte bleiben bei den jeweiligen Urhebern. Eine Nutzungserlaubnis für ein Bild oder eine Tonaufnahme ersetzt keine erforderliche Einwilligung der darin erkennbaren oder hörbaren Personen. Diese Datenschutzerklärung allein erteilt keine solche Erlaubnis.</p>
|
||||
<p>Personenbezogene Aufnahmen werden nur veröffentlicht, wenn eine passende Rechtsgrundlage vorliegt. Soweit eine Einwilligung erforderlich ist, muss sie vor der Veröffentlichung eingeholt werden. Bei Kindern sind insbesondere die erforderlichen Zustimmungen ihrer Sorgeberechtigten zu beachten. Eine Einwilligung kann jederzeit für die Zukunft widerrufen werden; die Rechtmäßigkeit der bis dahin erfolgten Verarbeitung bleibt davon unberührt.</p>
|
||||
<p>Bei einem Widerruf oder einer berechtigten Löschanfrage prüfen die Betreiber die weitere Nutzung und entfernen die betroffenen Aufnahmen, soweit dies rechtlich erforderlich ist, aus der öffentlichen Bereitstellung. Bereits gespeicherte Kopien auf fremden Geräten können sie nicht vollständig zurückholen. Nicht öffentliche Sicherungskopien und Nachweise dürfen nur im rechtlich erforderlichen Umfang und für die dafür festgelegte Dauer aufbewahrt werden.</p>
|
||||
<p><strong>[Vor Veröffentlichung ergänzen: Rechtsgrundlagen für Einsendung, Prüfung, Veröffentlichung und Rechtenachweise; Empfänger und Hostinganbieter; Lösch- und Sicherungsfristen; Kontakt für Widerruf und Entfernung.]</strong></p>
|
||||
<h3>9. Deine Rechte</h3>
|
||||
<p>Du kannst fragen, welche Daten über dich gespeichert sind. Unter den gesetzlichen Voraussetzungen kannst du verlangen, dass sie berichtigt, gelöscht, nur noch eingeschränkt genutzt oder dir zur Weitergabe bereitgestellt werden. Du kannst der Nutzung widersprechen und eine erteilte Erlaubnis für die Zukunft zurücknehmen. Wenn du dich über den Umgang mit deinen Daten beschweren möchtest, kannst du dich an die folgende Datenschutzbehörde wenden.</p>
|
||||
<p><span>Zuständige Datenschutzaufsicht:</span><br>
|
||||
<strong>Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)</strong><br><span>Postfach 90 04 55, 99107 Erfurt</span><br><span>Häßlerstraße 8, 99096 Erfurt</span><br><span>Telefon: 0361 / 57 311 29 00</span><br><span>Telefax: 0361 / 57 311 29 04</span><br><span>E-Mail: poststelle@datenschutz.thueringen.de</span><br><span>Website: www.tlfdi.de</span></p>
|
||||
@@ -367,8 +374,30 @@
|
||||
<p>Je nachdem, wer Wegwichtel betreibt, können weitere Angaben nötig sein. Dazu gehören zum Beispiel die Verantwortlichen für den Betrieb der Website. Diese Angaben müssen noch geprüft werden.</p>
|
||||
<h3>Haftung für Inhalte und Verweise</h3>
|
||||
<p>Die Inhalte werden sorgfältig erstellt und regelmäßig geprüft. Falls etwas falsch, veraltet oder nicht erlaubt ist, melde dich beim Kontakt oben. Für andere verlinkte Seiten sind deren Betreiber verantwortlich.</p>
|
||||
<h3>Bildquelle der Gruppensymbole</h3>
|
||||
<p>Die Wichtel-Gruppensymbole stammen aus einer Grafik von <a href="https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077" rel="noreferrer">Envato</a>. Ihre Verwendung richtet sich nach der jeweiligen Envato-Lizenz. Envato wird hier als Quelle genannt.</p>
|
||||
<h3>Urheber- und Nutzungsrechte</h3>
|
||||
<p>Wenn nichts anderes angegeben ist, liegen die Rechte bei der Grundschule Herpf. Du darfst Texte, Bilder, Tonaufnahmen und Wege nur so nutzen, wie es jeweils erlaubt wurde. Auch die Rechte der beteiligten Kinder, Familien, Lehrkräfte und anderen Personen bleiben bestehen.</p>
|
||||
<p>Die Urheberrechte an Bildern, Tonaufnahmen und anderen Beiträgen bleiben bei den jeweiligen Urhebern. Die Betreiber nutzen eingesandte Aufnahmen im Rahmen der vereinbarten Nutzungsrechte. Die Veröffentlichung erlaubt anderen Personen keine freie Weiterverwendung. Gesetzlich erlaubte Nutzungen und ausdrücklich erteilte Lizenzen bleiben unberührt.</p>
|
||||
</article>
|
||||
<article class="info-page" data-info-page="terms" lang="de" hidden>
|
||||
<p class="legal-warning"><strong>Entwurf:</strong> Diese Vereinbarung muss vor ihrer verbindlichen Verwendung rechtlich geprüft werden. Betreiber, Kontakt und das Verfahren zur Zustimmung und Urhebernennung sind noch zu ergänzen.</p>
|
||||
<p>Stand: 4. Oktober 2026</p>
|
||||
<h3>1. Für wen diese Vereinbarung gilt</h3>
|
||||
<p>Diese Vereinbarung regelt das Einsenden von Bildern und Tonaufnahmen an Wegwichtel. Nutzungsrechte werden dem Betreiber <strong>[vollständiger Name und Anschrift des tatsächlichen Betreibers ergänzen]</strong> eingeräumt. Die bloße Nutzung der App oder das Speichern eines Entwurfs auf deinem Gerät räumt keine Rechte ein.</p>
|
||||
<h3>2. Erlaubnis für Bilder und Tonaufnahmen</h3>
|
||||
<p>Mit dem Hochladen deiner Bilder und Tonaufnahmen unter Zustimmung zu dieser Nutzungsvereinbarung räumst du dem Betreiber ein einfaches, nicht ausschließliches, räumlich unbeschränktes Nutzungsrecht für den Betrieb von Wegwichtel ein. Es umfasst das Speichern und Vervielfältigen, die redaktionelle Prüfung sowie nach Freigabe das öffentliche Zugänglichmachen und Wiedergeben im Zusammenhang mit den Strecken der App. Dazu gehören die Anzeige von Bildern, das Abspielen von Tonaufnahmen sowie die von der App angebotene Speicherung ausgewählter Strecken auf Geräten für die Nutzung ohne Internet.</p>
|
||||
<p>Der Betreiber darf die dafür notwendigen technischen Anpassungen vornehmen, etwa Dateiformate umwandeln, Dateien komprimieren und Bildgrößen anpassen. Inhaltliche Veränderungen oder entstellende Bearbeitungen sind dadurch nicht erlaubt. Beauftragte Hosting- und technische Dienstleister dürfen die Aufnahmen nur soweit verwenden, wie dies zur Bereitstellung von Wegwichtel erforderlich ist. Besucher erhalten nur die für die Nutzung innerhalb der App und ihrer vorgesehenen Offline-Funktion erforderliche Erlaubnis.</p>
|
||||
<p>Die Rechteeinräumung gilt für die Dauer der vereinbarten Bereitstellung deiner Aufnahmen in Wegwichtel. Nach einer vereinbarten Beendigung stellt der Betreiber die weitere öffentliche Nutzung ein. Gesetzlich erforderliche nicht öffentliche Nachweise dürfen im erforderlichen Umfang aufbewahrt werden. Bereits rechtmäßig auf Geräten gespeicherte Kopien können nicht vollständig zurückgerufen werden. Gesetzliche Rückrufrechte und Ansprüche bleiben unberührt.</p>
|
||||
<p>Eine Vergütung für die hier beschriebene freiwillige Bereitstellung ist nicht vereinbart. Unabdingbare gesetzliche Vergütungsansprüche bleiben unberührt. Nutzungen für Werbung außerhalb von Wegwichtel, den Verkauf der Aufnahmen oder eigenständige Veröffentlichungen auf anderen Plattformen sind nicht umfasst und benötigen eine gesonderte Vereinbarung.</p>
|
||||
<h3>3. Urheberschaft und Namensnennung</h3>
|
||||
<p>Das Urheberrecht wird nicht übertragen. Das Recht auf Anerkennung der Urheberschaft und auf die vereinbarte Urheberbezeichnung bleibt bestehen. Für jede Aufnahme ist vor der Veröffentlichung anzugeben, welcher Name oder welches Pseudonym als Urheberbezeichnung verwendet werden soll. Diese Bezeichnung ist bei der Aufnahme sichtbar zu nennen. Ein Verzicht auf Namensnennung wird nicht durch das Hochladen unterstellt. Eine abweichende Vereinbarung ist gesondert mit dem berechtigten Urheber zu treffen.</p>
|
||||
<h3>4. Du musst die nötigen Rechte haben</h3>
|
||||
<p>Du darfst nur Aufnahmen senden, die du selbst erstellt hast oder für die du alle erforderlichen Erlaubnisse besitzt, um diese Nutzungsrechte einzuräumen. Bei Tonaufnahmen sind auch die Rechte an Musik, gesprochenen Texten, Darbietungen und der Aufnahme selbst zu beachten. Die Erlaubnis zum privaten Anhören oder zum eigenen Gebrauch reicht für eine Veröffentlichung in Wegwichtel nicht aus.</p>
|
||||
<p>Auch die Rechte erkennbarer oder hörbarer Personen müssen beachtet werden. Eine Nutzungserlaubnis des Fotografen ersetzt nicht die erforderliche Einwilligung der abgebildeten Person. Kinder brauchen für die Rechteeinräumung die erforderliche Zustimmung ihrer gesetzlichen Vertreter. Sorgeberechtigte dürfen Rechte anderer Urheber nicht ohne deren Erlaubnis einräumen.</p>
|
||||
<h3>5. Prüfung, Entfernung und Beschwerden</h3>
|
||||
<p>Die Betreiber sind nicht verpflichtet, eingesandte Aufnahmen zu veröffentlichen. Wenn Rechte unklar sind oder eine berechtigte Beanstandung vorliegt, dürfen sie die Veröffentlichung aussetzen oder die Aufnahme entfernen. Fragen zu Urheberschaft, Nutzungsrechten, Namensnennung oder Entfernung richtest du an <strong>[Kontakt für Rechtefragen ergänzen]</strong>. Datenschutzrechtliche Einwilligungen und Widerrufe werden getrennt von den urheberrechtlichen Nutzungsrechten geprüft.</p>
|
||||
<h3>6. Hinweis bei ausgelieferten Bildern</h3>
|
||||
<p>Der technische Rechtehinweis bei Bildern informiert über diese Nutzungsregeln. Er ersetzt weder die Urhebernennung bei der Aufnahme noch einen Nachweis über die tatsächlich erteilte Nutzungserlaubnis. Er verhindert auch nicht das Kopieren einer Aufnahme.</p>
|
||||
</article>
|
||||
<article class="info-page" data-info-page="technology" hidden>
|
||||
<h3 data-i18n="storage-help.progress-title">Was merkt sich Wegwichtel?</h3>
|
||||
@@ -464,6 +493,8 @@
|
||||
</details>
|
||||
<p id="editor-route-activated" class="editor-muted" data-i18n="editor.new-draft-hint">Deine neue Strecke bleibt zuerst auf diesem Gerät. Mit „Zur Prüfung senden“ kannst du sie zur Freigabe einreichen.</p>
|
||||
|
||||
<p class="editor-muted" data-i18n="editor.rights-hint">Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.</p>
|
||||
<button id="editor-rights-open" type="button" data-i18n="editor.rights-open">Vereinbarung für Bilder und Ton lesen</button>
|
||||
<div class="editor-form-actions">
|
||||
<button id="editor-route-save" class="editor-primary-button" type="submit" data-i18n="editor.save-route">Strecke speichern</button>
|
||||
<button id="editor-route-upload" class="editor-secondary-button" type="button" hidden data-i18n="editor.submit">Zur Prüfung senden</button>
|
||||
|
||||
@@ -44,6 +44,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
routeName: 'editor-route-name',
|
||||
routeSave: 'editor-route-save',
|
||||
routeUpload: 'editor-route-upload',
|
||||
rightsOpen: 'editor-rights-open',
|
||||
status: 'editor-status'
|
||||
});
|
||||
|
||||
@@ -1448,6 +1449,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
activate(byId(ids.newRoute), newRoute);
|
||||
activate(byId(ids.routeBack), () => guarded(loadRoutes).then(showListView));
|
||||
activate(byId(ids.routeUpload), () => guarded(uploadLocalRoute));
|
||||
activate(byId(ids.rightsOpen), () => ns.LegalUi?.open('terms'));
|
||||
activate(byId(ids.addPoi), newPoi);
|
||||
activate(byId(ids.poiAudioAdd), () => guarded(() => chooseMedia('audio', ids.poiAudio)));
|
||||
activate(byId(ids.poiImagesAdd), () => guarded(() => chooseMedia('image', ids.poiImages)));
|
||||
|
||||
@@ -7,11 +7,13 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const pageTitles = Object.freeze({
|
||||
privacy: 'Datenschutzerklärung',
|
||||
imprint: 'Impressum',
|
||||
terms: 'Nutzungsvereinbarung',
|
||||
get technology() { return ns.I18n.t("info.storage"); }
|
||||
});
|
||||
function menuEntries() { return [
|
||||
['privacy', ns.I18n.t("info.privacy")],
|
||||
['imprint', ns.I18n.t("info.imprint")],
|
||||
['terms', ns.I18n.t("info.terms")],
|
||||
['technology', ns.I18n.t("info.storage")]
|
||||
]; }
|
||||
const selectors = Object.freeze({
|
||||
@@ -439,7 +441,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function updateDialogTitle(pageName) {
|
||||
if (!elements?.title) return;
|
||||
const isLegalPage = pageName === 'privacy' || pageName === 'imprint';
|
||||
const isLegalPage = pageName !== 'technology';
|
||||
if (isLegalPage) delete elements.title.dataset.i18n;
|
||||
else elements.title.dataset.i18n = 'info.storage';
|
||||
setAttribute(elements.title, 'lang', isLegalPage ? 'de' : ns.I18n.language);
|
||||
|
||||
@@ -131,6 +131,8 @@ globalThis.Wegwichtel ??= {};
|
||||
"no-stations": "Noch keine Stationen angelegt.",
|
||||
"loading-routes": "Strecken werden geladen …",
|
||||
"submitted": "Erneut zur Prüfung senden",
|
||||
"rights-hint": "Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.",
|
||||
"rights-open": "Vereinbarung für Bilder und Ton lesen",
|
||||
"submit": "Zur Prüfung senden",
|
||||
"submitted-hint": "Du kannst Änderungen erneut zur Prüfung senden, solange die Strecke noch nicht freigegeben wurde.",
|
||||
"draft-hint": "Dieser Entwurf bleibt auf deinem Gerät. Mit „Zur Prüfung senden“ reichst du ihn zur Freigabe ein.",
|
||||
@@ -205,6 +207,7 @@ globalThis.Wegwichtel ??= {};
|
||||
"pumpkin": "Kürbis"
|
||||
},
|
||||
"info": {
|
||||
"terms": "Nutzungsvereinbarung",
|
||||
"privacy": "Datenschutzerklärung",
|
||||
"imprint": "Impressum",
|
||||
"storage": "Speichern und Berechtigungen",
|
||||
|
||||
@@ -131,6 +131,8 @@ globalThis.Wegwichtel ??= {};
|
||||
"no-stations": "There are no stops yet.",
|
||||
"loading-routes": "Loading trails …",
|
||||
"submitted": "Send changes for review",
|
||||
"rights-hint": "By sending pictures or recordings, you allow Wegwichtel to use them under its terms of use. Only send recordings you are allowed to share this way. Children need their parent or guardian’s permission.",
|
||||
"rights-open": "Read the agreement for pictures and recordings",
|
||||
"submit": "Send for review",
|
||||
"submitted-hint": "You can send changes for review until the route has been approved.",
|
||||
"draft-hint": "This draft stays on your device. Choose “Send for review” to submit it for approval.",
|
||||
@@ -205,6 +207,7 @@ globalThis.Wegwichtel ??= {};
|
||||
"pumpkin": "Pumpkin"
|
||||
},
|
||||
"info": {
|
||||
"terms": "Terms of use",
|
||||
"privacy": "Privacy notice",
|
||||
"imprint": "Legal notice",
|
||||
"storage": "Saving and permissions",
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { offlineManifest } from './src/services/offline-manifest.js';
|
||||
import { setAppImageCopyright } from './src/services/copyright.js';
|
||||
import { config, ensureRuntimeDirectories } from './src/config.js';
|
||||
import { openDatabase } from './src/database.js';
|
||||
import { createApiRouter } from './src/routes/api.js';
|
||||
@@ -30,6 +31,7 @@ app.use(express.static(publicDirectory, {
|
||||
lastModified: true,
|
||||
maxAge: 0,
|
||||
setHeaders(res, filePath) {
|
||||
setAppImageCopyright(res, filePath);
|
||||
const extension = path.extname(filePath).toLowerCase();
|
||||
const authoredAsset = ['.html', '.css', '.js'].includes(extension);
|
||||
res.setHeader(
|
||||
|
||||
+5
-2
@@ -1,6 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import { inspectUpload, upload } from '../middleware/upload.js';
|
||||
import { config } from '../config.js';
|
||||
import { UPLOAD_COPYRIGHT_HEADERS } from '../services/copyright.js';
|
||||
import {
|
||||
listRoutes, getRoute, getRouteGpxFile, createRoute, updateRoute, appendRoute,
|
||||
listPois, getPoi, createPoi, updatePoi, deletePoi, softDeleteRoute, restoreRoute
|
||||
@@ -17,12 +18,13 @@ const numberOrUndefined = value => {
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
};
|
||||
|
||||
function sendFileResource(res, file, contentType = null) {
|
||||
function sendFileResource(res, file, contentType = null, headers = {}) {
|
||||
if (contentType) res.type(contentType);
|
||||
res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(file.filename)}`);
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate');
|
||||
return res.sendFile(file.absolutePath, {
|
||||
headers,
|
||||
acceptRanges: true,
|
||||
cacheControl: true,
|
||||
immutable: false,
|
||||
@@ -132,7 +134,8 @@ export function createApiRouter(db) {
|
||||
res.json(getPicture(db, routeId, poiId, pictureId));
|
||||
return;
|
||||
}
|
||||
sendFileResource(res, getPictureFile(db, routeId, poiId, pictureId));
|
||||
const file = getPictureFile(db, routeId, poiId, pictureId);
|
||||
sendFileResource(res, file, null, UPLOAD_COPYRIGHT_HEADERS);
|
||||
});
|
||||
|
||||
api.post('/routes/:routeId/pois/:poiId/pictures', requireWritableRoute('routeId'), upload.single('picture'), inspectUpload('picture', 'picture', { allowJson: true }), (req, res) => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import path from 'node:path';
|
||||
|
||||
// These are policy notices, not evidence of a particular uploader's rights.
|
||||
// ASCII keeps values safe across HTTP servers and reverse proxies.
|
||||
export const UPLOAD_COPYRIGHT_NOTICE = 'Urheberrechte verbleiben bei den Urhebern. Nutzungsrechte an Upload-Bildern werden den Wegwichtel-Betreibern mit dem Hochladen unter Zustimmung zur Nutzungsvereinbarung eingeraeumt. Keine freie Weiterverwendung.';
|
||||
export const APP_IMAGE_COPYRIGHT_NOTICE = 'Wegwichtel-App-Grafik: Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess der jeweiligen Lizenz.';
|
||||
export const ENVATO_SYMBOL_SOURCE = 'https://app.envato.com/search/graphics/341b34af-8f28-4222-bc16-32ce99291077';
|
||||
export const GROUP_SYMBOL_COPYRIGHT_NOTICE = 'Quelle: Envato. Urheberrechte verbleiben bei den jeweiligen Urhebern. Nutzung gemaess Envato-Lizenz.';
|
||||
export const COPYRIGHT_HEADER_LIMITS = 'Dieser technische Hinweis ersetzt weder die sichtbare Urheberbezeichnung noch einen Nachweis ueber tatsaechlich eingeraeumte Nutzungsrechte.';
|
||||
|
||||
export const UPLOAD_COPYRIGHT_HEADERS = Object.freeze({
|
||||
'X-Copyright': UPLOAD_COPYRIGHT_NOTICE,
|
||||
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
|
||||
});
|
||||
const APP_IMAGE_COPYRIGHT_HEADERS = Object.freeze({
|
||||
'X-Copyright': APP_IMAGE_COPYRIGHT_NOTICE,
|
||||
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS
|
||||
});
|
||||
const GROUP_SYMBOL_COPYRIGHT_HEADERS = Object.freeze({
|
||||
'X-Copyright': GROUP_SYMBOL_COPYRIGHT_NOTICE,
|
||||
'X-Copyright-Notice': COPYRIGHT_HEADER_LIMITS,
|
||||
'X-Copyright-Source': ENVATO_SYMBOL_SOURCE
|
||||
});
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.ico', '.avif']);
|
||||
|
||||
export function setAppImageCopyright(res, filePath) {
|
||||
if (IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase())) {
|
||||
const isGroupSymbol = path.basename(path.dirname(filePath)) === 'group-symbols';
|
||||
const headers = isGroupSymbol ? GROUP_SYMBOL_COPYRIGHT_HEADERS : APP_IMAGE_COPYRIGHT_HEADERS;
|
||||
for (const [name, value] of Object.entries(headers)) res.setHeader(name, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { APP_IMAGE_COPYRIGHT_NOTICE, GROUP_SYMBOL_COPYRIGHT_NOTICE, UPLOAD_COPYRIGHT_NOTICE, COPYRIGHT_HEADER_LIMITS, ENVATO_SYMBOL_SOURCE, setAppImageCopyright } from '../src/services/copyright.js';
|
||||
|
||||
test('image notices distinguish Envato symbols from other assets and skip non-image files', () => {
|
||||
const headers = new Map();
|
||||
const res = { setHeader: (name, value) => headers.set(name, value) };
|
||||
for (const extension of ['png', 'jpg', 'jpeg', 'webp', 'gif', 'svg', 'ico', 'avif', 'PNG']) {
|
||||
headers.clear();
|
||||
setAppImageCopyright(res, path.join('public', 'assets', `graphic.${extension}`));
|
||||
assert.equal(headers.get('X-Copyright'), APP_IMAGE_COPYRIGHT_NOTICE);
|
||||
assert.equal(headers.get('X-Copyright-Notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(headers.has('X-Copyright-Source'), false);
|
||||
}
|
||||
setAppImageCopyright(res, path.join('public', 'images', 'group-symbols', '🐦.png'));
|
||||
assert.equal(headers.get('X-Copyright'), GROUP_SYMBOL_COPYRIGHT_NOTICE);
|
||||
assert.equal(headers.get('X-Copyright-Notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(headers.get('X-Copyright-Source'), ENVATO_SYMBOL_SOURCE);
|
||||
for (const extension of ['js', 'css', 'html', 'txt', 'mp3']) {
|
||||
headers.clear();
|
||||
setAppImageCopyright(res, path.join('public', 'assets', `file.${extension}`));
|
||||
assert.equal(headers.has('X-Copyright'), false);
|
||||
}
|
||||
for (const value of [APP_IMAGE_COPYRIGHT_NOTICE, GROUP_SYMBOL_COPYRIGHT_NOTICE, UPLOAD_COPYRIGHT_NOTICE, COPYRIGHT_HEADER_LIMITS, ENVATO_SYMBOL_SOURCE]) {
|
||||
assert.match(value, /^[\x20-\x7e]+$/, 'Header values must not contain Unicode or line breaks');
|
||||
}
|
||||
});
|
||||
@@ -225,6 +225,7 @@ function buildEditorContext() {
|
||||
'editor-route-list-view',
|
||||
'editor-route-name',
|
||||
'editor-route-upload',
|
||||
'editor-rights-open',
|
||||
'editor-status'
|
||||
];
|
||||
const elements = new Map(ids.map(id => [id, new FakeElement(id)]));
|
||||
@@ -365,6 +366,7 @@ function buildEditorContext() {
|
||||
Wegwichtel: {
|
||||
App: { currentPosition: () => ({ lat: 50.123456789, lon: 10.987654321, accuracy: 4.321 }) },
|
||||
Config: { apiBase: '/api/' },
|
||||
LegalUi: { open: name => { controls.legalPage = name; } },
|
||||
RouteRecorder: {
|
||||
beginEditing(id) { controls.recording = id; },
|
||||
endEditing() { controls.recording = null; },
|
||||
@@ -396,6 +398,9 @@ test('public editor workflow saves new route and GPX Station locally without POS
|
||||
assert.equal(elements.get('editor-button').attributes.get('aria-expanded'), 'true');
|
||||
assert.ok(calls.some(call => call.method === 'GET' && call.url === '/api/routes?activatedOnly=true'));
|
||||
|
||||
elements.get('editor-rights-open').dispatchEvent(pointerEvent());
|
||||
assert.equal(controls.legalPage, 'terms');
|
||||
|
||||
await context.Wegwichtel.EditorUi.openNewLocalRoute();
|
||||
assert.equal(elements.get('editor-route-gpx-group').hidden, true);
|
||||
elements.get('editor-route-name').value = 'Neue lokale Strecke';
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ test('German legal pages and contacts remain static when the app uses English',
|
||||
const i18n = await loadI18n(context);
|
||||
const html = await fs.readFile(path.join(root, 'public/index.html'), 'utf8');
|
||||
const englishHtml = localizeHtml(html, key => i18n.t(key));
|
||||
for (const name of ['privacy', 'imprint']) {
|
||||
for (const name of ['privacy', 'imprint', 'terms']) {
|
||||
const pattern = new RegExp(`<article[^>]+data-info-page="${name}"[^>]*>[\\s\\S]*?</article>`);
|
||||
const page = pattern.exec(html)?.[0];
|
||||
assert.ok(page, name);
|
||||
@@ -22,7 +22,7 @@ test('German legal pages and contacts remain static when the app uses English',
|
||||
assert.equal(pattern.exec(englishHtml)?.[0], page);
|
||||
}
|
||||
for (const catalog of context.Wegwichtel.TextCatalogs.values()) {
|
||||
for (const key of ['privacy', 'imprint', 'contacts']) {
|
||||
for (const key of ['privacy', 'imprint', 'terms', 'contacts']) {
|
||||
assert.equal(Object.hasOwn(catalog, key), false, key);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-2
@@ -31,7 +31,7 @@ test('start screen exposes cookie status, optional local progress and deliberate
|
||||
assert.match(storage, /cookiesEnabled: \(\) => false/);
|
||||
});
|
||||
|
||||
test('information button opens scrollable overlay pages for privacy, imprint and technology', async () => {
|
||||
test('information button opens scrollable overlay pages for privacy, imprint, terms and technology', async () => {
|
||||
const html = await read('public/index.html');
|
||||
const css = await read('public/css/app.css');
|
||||
const ui = await read('public/js/legal-ui.js');
|
||||
@@ -41,6 +41,7 @@ test('information button opens scrollable overlay pages for privacy, imprint and
|
||||
assert.match(html, /<dialog id="info-dialog"/);
|
||||
assert.match(html, /data-info-target="privacy"/);
|
||||
assert.match(html, /data-info-target="imprint"/);
|
||||
assert.match(html, /data-info-target="terms"/);
|
||||
assert.match(html, /data-info-target="technology"/);
|
||||
assert.match(html, /<h3>Technisch verantwortlich<\/h3>\s*<p>Florian Zumpe<br>/);
|
||||
assert.match(html, /\[Anschrift von Florian Zumpe ergänzen\]/);
|
||||
@@ -137,7 +138,8 @@ test('legal overlay handlers synchronize switches without advancing the start sc
|
||||
const privacyPage = new FakeElement();
|
||||
const imprintPage = new FakeElement();
|
||||
const technologyPage = new FakeElement();
|
||||
const pages = { privacy: privacyPage, imprint: imprintPage, technology: technologyPage };
|
||||
const termsPage = new FakeElement();
|
||||
const pages = { privacy: privacyPage, imprint: imprintPage, terms: termsPage, technology: technologyPage };
|
||||
const targetButtons = Object.keys(pages).map(name => {
|
||||
const button = new FakeElement();
|
||||
button.dataset.infoTarget = name;
|
||||
@@ -234,6 +236,18 @@ test('legal overlay handlers synchronize switches without advancing the start sc
|
||||
assert.equal(elements.get('info-dialog-title').textContent, 'Impressum');
|
||||
assert.equal(elements.get('info-dialog-title').attributes.get('lang'), 'de');
|
||||
|
||||
targetButtons[2].dispatchEvent(pointerEvent());
|
||||
assert.equal(termsPage.hidden, false);
|
||||
assert.equal(imprintPage.hidden, true);
|
||||
assert.equal(elements.get('info-dialog-title').textContent, 'Nutzungsvereinbarung');
|
||||
assert.equal(elements.get('info-dialog-title').attributes.get('lang'), 'de');
|
||||
assert.equal(Object.hasOwn(elements.get('info-dialog-title').dataset, 'i18n'), false);
|
||||
|
||||
targetButtons[3].dispatchEvent(pointerEvent());
|
||||
assert.equal(technologyPage.hidden, false);
|
||||
assert.equal(elements.get('info-dialog-title').textContent, 'Saving and permissions');
|
||||
assert.equal(elements.get('info-dialog-title').attributes.get('lang'), 'en');
|
||||
|
||||
progressToggles[0].checked = true;
|
||||
progressToggles[0].dispatchEvent(new Event('change'));
|
||||
assert.deepEqual(changes, [true]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { parseGpx } from '../src/services/gpx.js';
|
||||
import { UPLOAD_COPYRIGHT_NOTICE, APP_IMAGE_COPYRIGHT_NOTICE, GROUP_SYMBOL_COPYRIGHT_NOTICE, COPYRIGHT_HEADER_LIMITS, ENVATO_SYMBOL_SOURCE } from '../src/services/copyright.js';
|
||||
import { SAMPLE_GPX } from '../test-support/gpx-fixtures.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
@@ -144,6 +145,7 @@ test('the npm-started local API is exercised through a write-auth proxy on loopb
|
||||
|
||||
const indexResponse = await fetch(`${baseUrl}/`);
|
||||
const indexHtml = await indexResponse.text();
|
||||
assert.equal(indexResponse.headers.get('x-copyright'), null);
|
||||
assert.equal(indexResponse.status, 200);
|
||||
assert.match(indexResponse.headers.get('cache-control') || '', /no-store/);
|
||||
assert.match(indexHtml, /css\/app\.css\?v=0\.12\.14/);
|
||||
@@ -164,6 +166,35 @@ test('the npm-started local API is exercised through a write-auth proxy on loopb
|
||||
assert.match(appStyleResponse.headers.get('cache-control') || '', /no-store/);
|
||||
await appStyleResponse.arrayBuffer();
|
||||
|
||||
assert.equal(appStyleResponse.headers.get('x-copyright'), null);
|
||||
const iconResponse = await fetch(`${baseUrl}/images/icons/play.svg`);
|
||||
assert.equal(iconResponse.status, 200);
|
||||
assert.equal(iconResponse.headers.get('x-copyright'), APP_IMAGE_COPYRIGHT_NOTICE);
|
||||
assert.equal(iconResponse.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(iconResponse.headers.get('x-copyright-source'), null);
|
||||
await iconResponse.arrayBuffer();
|
||||
const symbols = await fs.readdir(path.join(root, 'public/images/group-symbols'));
|
||||
for (const name of symbols.filter(name => name.endsWith('.png'))) {
|
||||
const symbolUrl = `${baseUrl}/images/group-symbols/${encodeURIComponent(name)}`;
|
||||
const symbol = await fetch(symbolUrl);
|
||||
assert.equal(symbol.status, 200, name);
|
||||
assert.equal(symbol.headers.get('x-copyright'), GROUP_SYMBOL_COPYRIGHT_NOTICE, name);
|
||||
assert.equal(symbol.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(symbol.headers.get('x-copyright-source'), ENVATO_SYMBOL_SOURCE);
|
||||
const etag = symbol.headers.get('etag');
|
||||
await symbol.arrayBuffer();
|
||||
const symbolHead = await fetch(symbolUrl, { method: 'HEAD' });
|
||||
assert.equal(symbolHead.status, 200);
|
||||
assert.equal(symbolHead.headers.get('x-copyright'), GROUP_SYMBOL_COPYRIGHT_NOTICE);
|
||||
assert.equal(symbolHead.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(symbolHead.headers.get('x-copyright-source'), ENVATO_SYMBOL_SOURCE);
|
||||
const symbolCached = await fetch(symbolUrl, { headers: { 'If-None-Match': etag, 'Cache-Control': 'max-age=0' } });
|
||||
assert.equal(symbolCached.status, 304);
|
||||
assert.equal(symbolCached.headers.get('x-copyright'), GROUP_SYMBOL_COPYRIGHT_NOTICE);
|
||||
assert.equal(symbolCached.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(symbolCached.headers.get('x-copyright-source'), ENVATO_SYMBOL_SOURCE);
|
||||
}
|
||||
|
||||
const pythonHealthCheck = await runProcess('python3', ['-c', `
|
||||
from common import ApiClient
|
||||
result = ApiClient('${LOCAL_API_URL}').request('GET', '/api/health')
|
||||
@@ -328,6 +359,32 @@ assert result['ok'] is True
|
||||
const { response: pictureFileResponse, body: pictureFile } = await requestBuffer(`${baseUrl}${picture.url}`);
|
||||
assert.match(pictureFileResponse.headers.get('content-type') || '', /image\/png/);
|
||||
assert.deepEqual(pictureFile, PNG_FILE);
|
||||
assert.equal(pictureFileResponse.headers.get('x-copyright'), UPLOAD_COPYRIGHT_NOTICE);
|
||||
assert.equal(pictureFileResponse.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(pictureFileResponse.headers.get('x-copyright-source'), null);
|
||||
const pictureHead = await fetch(`${baseUrl}${picture.url}`, { method: 'HEAD' });
|
||||
assert.equal(pictureHead.status, 200);
|
||||
assert.equal(pictureHead.headers.get('x-copyright'), UPLOAD_COPYRIGHT_NOTICE);
|
||||
assert.equal(pictureHead.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(pictureHead.headers.get('x-copyright-source'), null);
|
||||
const pictureRange = await fetch(`${baseUrl}${picture.url}`, { headers: { Range: 'bytes=0-7' } });
|
||||
assert.equal(pictureRange.status, 206);
|
||||
assert.equal(pictureRange.headers.get('x-copyright'), UPLOAD_COPYRIGHT_NOTICE);
|
||||
assert.equal(pictureRange.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(pictureRange.headers.get('x-copyright-source'), null);
|
||||
assert.deepEqual(Buffer.from(await pictureRange.arrayBuffer()), PNG_FILE.subarray(0, 8));
|
||||
const pictureCached = await fetch(`${baseUrl}${picture.url}`, { headers: { 'If-None-Match': pictureFileResponse.headers.get('etag'), 'Cache-Control': 'max-age=0' } });
|
||||
assert.equal(pictureCached.status, 304);
|
||||
assert.equal(pictureCached.headers.get('x-copyright'), UPLOAD_COPYRIGHT_NOTICE);
|
||||
assert.equal(pictureCached.headers.get('x-copyright-notice'), COPYRIGHT_HEADER_LIMITS);
|
||||
assert.equal(pictureCached.headers.get('x-copyright-source'), null);
|
||||
const wrongPicture = await fetch(`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/pictures/999999`);
|
||||
assert.equal(wrongPicture.status, 404);
|
||||
assert.equal(wrongPicture.headers.get('x-copyright'), null);
|
||||
await wrongPicture.arrayBuffer();
|
||||
const metadataHeaders = await fetch(`${baseUrl}${picture.url}?metadata=true`);
|
||||
assert.equal(metadataHeaders.headers.get('x-copyright'), null);
|
||||
await metadataHeaders.arrayBuffer();
|
||||
const { body: pictureMetadata } = await requestJson(`${baseUrl}${picture.url}?metadata=true`);
|
||||
assert.equal(pictureMetadata.caption, 'Erste Bildbeschreibung');
|
||||
assert.equal(pictureMetadata.poiId, poi.id);
|
||||
|
||||
Reference in New Issue
Block a user