From d3311cce06b8df2167148bade7e38accd3d2ebe7 Mon Sep 17 00:00:00 2001 From: Florian Zumpe Date: Sun, 4 Oct 2026 10:14:40 +0200 Subject: [PATCH] insert copyright headers for images --- docs/Bilder-und-Ton-Nutzungsrechte.md | 127 ++++++++++++++++++++++++++ docs/PUBLICATION-CHECKLIST.md | 8 ++ docs/agents/AGENTS.md | 2 +- docs/agents/I18N.md | 4 +- docs/agents/TODO-MEDIENRECHTE.md | 52 +++++++++++ public/index.html | 35 ++++++- public/js/editor-ui.js | 2 + public/js/legal-ui.js | 4 +- public/js/texts-de.js | 3 + public/js/texts-en.js | 3 + server.js | 2 + src/routes/api.js | 7 +- src/services/copyright.js | 33 +++++++ test/copyright.test.js | 28 ++++++ test/editor-ui.test.js | 5 + test/i18n.test.js | 4 +- test/legal-ui.test.js | 18 +++- test/media-api.test.js | 57 ++++++++++++ 18 files changed, 382 insertions(+), 12 deletions(-) create mode 100644 docs/Bilder-und-Ton-Nutzungsrechte.md create mode 100644 docs/agents/TODO-MEDIENRECHTE.md create mode 100644 src/services/copyright.js create mode 100644 test/copyright.test.js diff --git a/docs/Bilder-und-Ton-Nutzungsrechte.md b/docs/Bilder-und-Ton-Nutzungsrechte.md new file mode 100644 index 0000000..2a92b16 --- /dev/null +++ b/docs/Bilder-und-Ton-Nutzungsrechte.md @@ -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. diff --git a/docs/PUBLICATION-CHECKLIST.md b/docs/PUBLICATION-CHECKLIST.md index fb7d63a..37110cd 100644 --- a/docs/PUBLICATION-CHECKLIST.md +++ b/docs/PUBLICATION-CHECKLIST.md @@ -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). diff --git a/docs/agents/AGENTS.md b/docs/agents/AGENTS.md index 8e66ba1..aa20a75 100644 --- a/docs/agents/AGENTS.md +++ b/docs/agents/AGENTS.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. diff --git a/docs/agents/I18N.md b/docs/agents/I18N.md index ba8b64b..c4a7132 100644 --- a/docs/agents/I18N.md +++ b/docs/agents/I18N.md @@ -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. diff --git a/docs/agents/TODO-MEDIENRECHTE.md b/docs/agents/TODO-MEDIENRECHTE.md new file mode 100644 index 0000000..d5eaae5 --- /dev/null +++ b/docs/agents/TODO-MEDIENRECHTE.md @@ -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. diff --git a/public/index.html b/public/index.html index b941bde..69de95b 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,7 @@ @@ -342,7 +343,13 @@

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.

7. Inhalte von Schülerinnen und Schülern

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.

-

8. Deine Rechte

+

8. Eingesandte Bilder und Tonaufnahmen

+

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.

+

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.

+

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.

+

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.

+

[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.]

+

9. Deine Rechte

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.

Zuständige Datenschutzaufsicht:
Thüringer Landesbeauftragter für den Datenschutz und die Informationsfreiheit (TlfDI)
Postfach 90 04 55, 99107 Erfurt
Häßlerstraße 8, 99096 Erfurt
Telefon: 0361 / 57 311 29 00
Telefax: 0361 / 57 311 29 04
E-Mail: poststelle@datenschutz.thueringen.de
Website: www.tlfdi.de

@@ -367,8 +374,30 @@

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.

Haftung für Inhalte und Verweise

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.

+

Bildquelle der Gruppensymbole

+

Die Wichtel-Gruppensymbole stammen aus einer Grafik von Envato. Ihre Verwendung richtet sich nach der jeweiligen Envato-Lizenz. Envato wird hier als Quelle genannt.

Urheber- und Nutzungsrechte

-

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.

+

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.

+ + `); 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); } } diff --git a/test/legal-ui.test.js b/test/legal-ui.test.js index 122e7c4..ba46023 100644 --- a/test/legal-ui.test.js +++ b/test/legal-ui.test.js @@ -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, /Technisch verantwortlich<\/h3>\s*

Florian Zumpe
/); 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]); diff --git a/test/media-api.test.js b/test/media-api.test.js index b8bfa18..79a6eb6 100644 --- a/test/media-api.test.js +++ b/test/media-api.test.js @@ -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);