Merge pull request #251 from trails-cool/docs/legal-archive
Add docs/legal-archive + scripts/render-legal.ts
This commit is contained in:
commit
18fc023cb6
10 changed files with 836 additions and 0 deletions
53
docs/legal-archive/README.md
Normal file
53
docs/legal-archive/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Legal archive
|
||||
|
||||
Frozen snapshots of the Terms of Service, Privacy Policy, and Impressum at
|
||||
each version. Kept so we can always answer "what did the Terms / Privacy
|
||||
text say on a given date" without digging through git blame.
|
||||
|
||||
## Why
|
||||
|
||||
- **Terms**: users accept a specific version at registration
|
||||
(`users.terms_accepted_at` + `users.terms_version`). The version string
|
||||
is the date in this folder — so there is always a file here matching
|
||||
every value that exists in that column.
|
||||
- **Privacy**: users don't "accept" a privacy policy, but GDPR Art. 13/14
|
||||
requires us to tell users how their data is processed *at the time* it's
|
||||
processed. If a regulator or user asks what the policy said on
|
||||
YYYY-MM-DD, this folder answers.
|
||||
- **Impressum**: less legally critical, but free to snapshot for symmetry.
|
||||
|
||||
## When to add a file
|
||||
|
||||
Whenever any of these three texts change materially, run the snapshot step
|
||||
as part of the change:
|
||||
|
||||
1. Bump `TERMS_VERSION` / `PRIVACY_LAST_UPDATED` in
|
||||
[`apps/journal/app/lib/legal.ts`](../../apps/journal/app/lib/legal.ts)
|
||||
to today's date.
|
||||
2. Re-render the affected page(s) into this folder as
|
||||
`<doc>-YYYY-MM-DD.md`.
|
||||
3. Commit the bump **and** the new snapshot in the same PR.
|
||||
|
||||
A trivial wording tweak or typo fix does not need a new snapshot; only
|
||||
changes that affect meaning / behaviour / purposes / third parties / legal
|
||||
basis / retention / etc.
|
||||
|
||||
## File naming
|
||||
|
||||
`<doc>-YYYY-MM-DD.md` where `<doc>` is one of `terms`, `privacy`,
|
||||
`imprint`, and the date matches the `Last updated` line in the
|
||||
corresponding legal page on the day the snapshot was taken.
|
||||
|
||||
## How to render
|
||||
|
||||
Snapshots are extracted from the TSX source by stripping JSX tags /
|
||||
attributes and resolving the `operator.*` placeholders plus the
|
||||
constants from `apps/journal/app/lib/legal.ts`. One-liner (from repo
|
||||
root):
|
||||
|
||||
```bash
|
||||
node --experimental-strip-types scripts/render-legal.ts <doc> \
|
||||
> docs/legal-archive/<doc>-YYYY-MM-DD.md
|
||||
```
|
||||
|
||||
where `<doc>` is one of `terms`, `privacy`, `imprint`.
|
||||
83
docs/legal-archive/imprint-2026-04-19.md
Normal file
83
docs/legal-archive/imprint-2026-04-19.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Impressum — trails.cool
|
||||
|
||||
Impressum / Legal Notice
|
||||
|
||||
Die deutsche Fassung ist maßgeblich. / The German version is authoritative.
|
||||
|
||||
Anbieter nach § 5 TMG
|
||||
|
||||
Ullrich Schäfer
|
||||
|
||||
Mehringdamm 87
|
||||
|
||||
10965 Berlin
|
||||
|
||||
Germany
|
||||
|
||||
Kontakt
|
||||
|
||||
E-Mail:
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
Inhaltlich verantwortlich nach § 18 Abs. 2 MStV
|
||||
|
||||
Ullrich Schäfer
|
||||
|
||||
Mehringdamm 87
|
||||
|
||||
10965 Berlin
|
||||
|
||||
Streitbeilegung
|
||||
|
||||
Die Europäische Kommission stellt eine Plattform zur
|
||||
Online-Streitbeilegung (OS) bereit:
|
||||
|
||||
https://ec.europa.eu/consumers/odr
|
||||
|
||||
.
|
||||
|
||||
Wir sind nicht bereit oder verpflichtet, an
|
||||
Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle
|
||||
teilzunehmen.
|
||||
|
||||
Alpha-Status
|
||||
|
||||
trails.cool befindet sich in aktiver Entwicklung (Alpha). Inhalte,
|
||||
Funktionen und Daten können sich jederzeit ändern oder entfallen.
|
||||
|
||||
English version
|
||||
|
||||
Service provider (§ 5 TMG)
|
||||
|
||||
Ullrich Schäfer
|
||||
|
||||
Mehringdamm 87
|
||||
|
||||
10965 Berlin
|
||||
|
||||
Germany
|
||||
|
||||
Contact
|
||||
|
||||
Email:
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
Content responsibility (§ 18 (2) MStV)
|
||||
|
||||
Ullrich Schäfer, address as above.
|
||||
|
||||
Dispute resolution
|
||||
|
||||
The European Commission provides an online dispute resolution platform at
|
||||
|
||||
https://ec.europa.eu/consumers/odr
|
||||
|
||||
. We are not willing or obliged to participate in dispute resolution
|
||||
proceedings before a consumer arbitration board.
|
||||
|
||||
Alpha status
|
||||
|
||||
trails.cool is under active development (alpha). Features and data
|
||||
may change or be removed at any time.
|
||||
358
docs/legal-archive/privacy-2026-04-19.md
Normal file
358
docs/legal-archive/privacy-2026-04-19.md
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
# Privacy Policy — trails.cool
|
||||
|
||||
Datenschutzerklärung / Privacy Policy
|
||||
|
||||
Stand / Last updated: 2026-04-19. Die deutsche Fassung ist maßgeblich.
|
||||
The German version is authoritative; English summaries follow each
|
||||
section.
|
||||
|
||||
1. Verantwortlicher
|
||||
|
||||
Verantwortlich für die Datenverarbeitung im Sinne der DSGVO ist:
|
||||
|
||||
Ullrich Schäfer
|
||||
|
||||
Mehringdamm 87
|
||||
|
||||
10965 Berlin
|
||||
|
||||
Germany
|
||||
|
||||
E-Mail:
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
English.
|
||||
Data controller under GDPR is the party named
|
||||
above. Contact for any data-protection matter: legal@trails.cool.
|
||||
|
||||
2. Erhobene Daten und Zwecke
|
||||
|
||||
Wir verarbeiten nur die Daten, die für den Betrieb der jeweiligen
|
||||
Funktion erforderlich sind. trails.cool besteht aus zwei Teilen:
|
||||
dem
|
||||
Journal
|
||||
(trails.cool, mit Konto) und dem
|
||||
|
||||
Planner
|
||||
(planner.trails.cool, anonym).
|
||||
|
||||
Kontodaten (Journal):
|
||||
E-Mail-Adresse,
|
||||
Benutzername, Anzeigename, Passkey-Public-Key. Zweck:
|
||||
Kontoverwaltung und Anmeldung.
|
||||
|
||||
Nutzerinhalte (Journal):
|
||||
Routen (GPX-Daten,
|
||||
Geometrie, Titel, Beschreibung) und Aktivitäten (Titel,
|
||||
Beschreibung, Datum, Verknüpfung zu Routen). Zweck: Speicherung
|
||||
und Anzeige innerhalb des Dienstes.
|
||||
|
||||
Anmeldedaten (Journal):
|
||||
kurzlebige Magic-Link-Token
|
||||
(zur E-Mail-basierten Anmeldung). Zweck: Authentifizierung.
|
||||
|
||||
Sitzungscookie (Journal):
|
||||
eine zufällige
|
||||
Sitzungs-ID nach dem Einloggen. Zweck: Authentifizierung während
|
||||
der Sitzung.
|
||||
|
||||
Planner-Sitzungsdaten:
|
||||
anonyme Sitzungs-ID,
|
||||
kollaborativer Zustand (Wegpunkte, Notizen). Keine Zuordnung zu
|
||||
einer Person. Zweck: gemeinsames Planen von Routen.
|
||||
|
||||
Server-Logfiles:
|
||||
IP-Adresse, Zeitstempel,
|
||||
HTTP-Methode, Pfad, Statuscode, User-Agent. Zweck: Sicherheit,
|
||||
Betrieb, Fehlersuche. Details siehe Abschnitt 4.
|
||||
|
||||
Fehlerdaten (Sentry):
|
||||
Stacktraces,
|
||||
Fehlermeldungen, Browser-/OS-Information aus dem User-Agent,
|
||||
Performance-Metriken. Bei eingeloggten Journal-Nutzer:innen
|
||||
zusätzlich die Nutzer-ID (keine E-Mail, kein Benutzername).
|
||||
IP-Adressen werden nicht aktiv gespeichert, ebenso keine Cookies
|
||||
und keine Formulareingaben.
|
||||
|
||||
English.
|
||||
The Journal stores only what you provide (account
|
||||
details and your own routes/activities) plus short-lived auth
|
||||
artefacts. The Planner is anonymous and holds only ephemeral
|
||||
session state. Server logs and Sentry error data are covered
|
||||
separately below.
|
||||
|
||||
3. Rechtsgrundlagen
|
||||
|
||||
Art. 6 Abs. 1 lit. b DSGVO (Vertragserfüllung):
|
||||
|
||||
Kontoführung, Anmeldung per Passkey oder Magic Link, Speicherung
|
||||
und Anzeige der von Ihnen erstellten Routen und Aktivitäten,
|
||||
Versand notwendiger Transaktions-E-Mails, Bereitstellung anonymer
|
||||
Planner-Sitzungen. Ein Konto sowie die Bestätigung der
|
||||
Nutzungsbedingungen sind Bestandteil dieses Nutzungsvertrags –
|
||||
sie sind
|
||||
keine
|
||||
Einwilligung im Sinne von Art. 6 Abs. 1
|
||||
lit. a DSGVO.
|
||||
|
||||
Art. 6 Abs. 1 lit. f DSGVO (berechtigte Interessen):
|
||||
|
||||
kurzzeitige Server-Logfiles zur Sicherung des Betriebs und zur
|
||||
Missbrauchsabwehr, Rate-Limiting, Fehlermonitoring über Sentry
|
||||
zur Sicherstellung der Funktionsfähigkeit des Dienstes.
|
||||
|
||||
English.
|
||||
Contract (Art. 6(1)(b)) covers everything
|
||||
account- and content-related; legitimate interests (Art. 6(1)(f))
|
||||
cover short-lived server logs, rate-limiting, and error monitoring.
|
||||
We do
|
||||
not
|
||||
rely on consent for any of this.
|
||||
|
||||
4. Server-Logfiles
|
||||
|
||||
Beim Aufruf der Dienste werden automatisch technische Informationen
|
||||
protokolliert:
|
||||
|
||||
IP-Adresse
|
||||
|
||||
Zeitstempel
|
||||
|
||||
HTTP-Methode, Pfad, Statuscode
|
||||
|
||||
User-Agent (Browser, Betriebssystem)
|
||||
|
||||
Zweck:
|
||||
Sicherheit, Betrieb und Fehlersuche.
|
||||
|
||||
Rechtsgrundlage:
|
||||
Art. 6 Abs. 1 lit. f DSGVO.
|
||||
|
||||
Speicherdauer:
|
||||
maximal 14 Tage, danach automatische
|
||||
Löschung. Eine Zusammenführung dieser Daten mit anderen Datenquellen
|
||||
findet nicht statt.
|
||||
|
||||
English.
|
||||
HTTP requests to our servers are logged (IP,
|
||||
timestamp, method, path, status, user-agent) for up to 14 days for
|
||||
operational and security purposes under Art. 6(1)(f), then deleted.
|
||||
|
||||
5. Speicherdauer
|
||||
|
||||
Konto und zugehörige Inhalte: bis zur Löschung durch Sie
|
||||
|
||||
Planner-Sitzungen: automatische Löschung nach 7 Tagen Inaktivität
|
||||
|
||||
Magic-Link-Token: 15 Minuten
|
||||
|
||||
Server-Logfiles: maximal 14 Tage
|
||||
|
||||
Fehlerdaten (Sentry): 90 Tage
|
||||
|
||||
Hinweis Alpha: Während der Alpha-Phase behält sich der Betreiber
|
||||
ausdrücklich vor, die Datenbank zurückzusetzen oder einzelne
|
||||
Datensätze zu löschen. Dies kann zu Datenverlust führen, bevor Sie
|
||||
eine Löschung veranlassen. Details dazu in den Nutzungsbedingungen.
|
||||
|
||||
English.
|
||||
Account and content kept until you delete them.
|
||||
Ephemeral data (sessions, magic-link tokens, logs, Sentry events)
|
||||
deleted automatically on the schedules above. Alpha caveat: the
|
||||
operator may reset the database or delete individual records
|
||||
during alpha, which can cause data loss before you request
|
||||
deletion. See the Terms of Service.
|
||||
|
||||
6. Empfänger und Drittanbieter
|
||||
|
||||
Wir geben personenbezogene Daten nur an die unten genannten
|
||||
Auftragsverarbeiter und Dritten weiter, und auch dort nur im
|
||||
jeweils notwendigen Umfang.
|
||||
|
||||
Sentry
|
||||
(Functional Software Inc.) – Fehler- und
|
||||
Performance-Monitoring. Was übermittelt wird: Stacktraces,
|
||||
Fehlertext, Browser-/OS-Informationen aus dem User-Agent,
|
||||
Performance-Daten; bei eingeloggten Journal-Nutzer:innen
|
||||
zusätzlich die Nutzer-ID. IP-Adressen werden nicht aktiv
|
||||
gespeichert (
|
||||
sendDefaultPii
|
||||
ist deaktiviert), ebenso
|
||||
keine Cookies und keine vollständigen HTTP-Header. Keine
|
||||
Session-Replays. Sentry agiert als externer Dienstleister
|
||||
(Auftragsverarbeiter) für die Fehlerbehandlung. Da Sentry ein
|
||||
Anbieter mit Sitz in den USA ist, kann im Einzelfall eine
|
||||
Übermittlung personenbezogener Daten in ein Drittland im Sinne
|
||||
der Art. 44 ff. DSGVO stattfinden; wir stützen uns hierbei auf
|
||||
die von Sentry bereitgestellten
|
||||
Standardvertragsklauseln.
|
||||
|
||||
OpenStreetMap
|
||||
– Kartenkacheln werden beim Anzeigen
|
||||
der Karten direkt vom Browser von OSM-Tile-Servern geladen. Dabei
|
||||
werden
|
||||
IP-Adresse und User-Agent
|
||||
an OSM übertragen.
|
||||
Dies ist notwendig, um überhaupt eine Karte darzustellen; es gilt
|
||||
die
|
||||
|
||||
OSM Foundation Privacy Policy
|
||||
|
||||
.
|
||||
|
||||
Overpass API
|
||||
(POI-Daten) – POI-Abfragen laufen
|
||||
serverseitig über unsere eigene Route
|
||||
/api/overpass
|
||||
.
|
||||
Der Upstream-Dienst sieht nur unsere Server-IP, nicht die Ihrer
|
||||
Nutzer:innen. Aktueller Upstream:
|
||||
overpass.private.coffee
|
||||
|
||||
, der ohne Query-Logs arbeitet. Eine selbst gehostete Instanz ist
|
||||
geplant.
|
||||
|
||||
BRouter
|
||||
– Routenberechnung läuft auf einer von uns
|
||||
selbst gehosteten Instanz. Keine Weitergabe an Dritte.
|
||||
|
||||
E-Mail-Versand
|
||||
– Transaktions-E-Mails (Magic
|
||||
Link, Willkommensnachricht) werden über einen konfigurierten
|
||||
externen SMTP-Dienst versendet. Dabei wird die E-Mail-Adresse
|
||||
der Empfänger:in an diesen Dienst übergeben. Selbst-betriebene
|
||||
Instanzen konfigurieren ihren eigenen Mailserver.
|
||||
|
||||
Hosting
|
||||
– Die Dienste werden in Rechenzentren
|
||||
innerhalb der EU betrieben. Ein Auftragsverarbeitungsvertrag mit
|
||||
dem Hoster besteht.
|
||||
|
||||
English.
|
||||
Third parties and what they receive: Sentry (error
|
||||
details, no IPs/cookies); OpenStreetMap tile servers
|
||||
(your IP and user-agent, directly from your browser, to load map
|
||||
tiles); Overpass (via our server-side proxy, so upstream only sees
|
||||
our server); BRouter (self-hosted, no third party involved); SMTP
|
||||
provider (your email address for magic link / welcome mail);
|
||||
hosting provider in the EU under a DPA.
|
||||
|
||||
7. Ihre Rechte
|
||||
|
||||
Als betroffene Person stehen Ihnen die folgenden Rechte zu:
|
||||
|
||||
Auskunft (Art. 15 DSGVO)
|
||||
|
||||
Berichtigung (Art. 16 DSGVO)
|
||||
|
||||
Löschung (Art. 17 DSGVO)
|
||||
|
||||
Einschränkung der Verarbeitung (Art. 18 DSGVO)
|
||||
|
||||
Datenübertragbarkeit (Art. 20 DSGVO)
|
||||
|
||||
Widerspruch gegen Verarbeitung auf Basis berechtigter Interessen (Art. 21 DSGVO)
|
||||
|
||||
Zur Ausübung dieser Rechte genügt eine formlose E-Mail an
|
||||
|
||||
legal@trails.cool
|
||||
|
||||
. Einen vollständigen Export Ihrer Routen und Aktivitäten können Sie
|
||||
jederzeit direkt in den Kontoeinstellungen herunterladen (GPX bzw.
|
||||
JSON). Ihr Konto samt Inhalten können Sie dort ebenfalls selbst
|
||||
löschen.
|
||||
|
||||
English.
|
||||
You have the standard GDPR rights (access,
|
||||
rectification, erasure, restriction, portability, objection). Email
|
||||
us to exercise them. Data exports and account deletion are also
|
||||
available directly in your account settings.
|
||||
|
||||
8. Beschwerderecht
|
||||
|
||||
Sie haben das Recht, sich bei einer Datenschutzaufsichtsbehörde zu
|
||||
beschweren. Für uns zuständig ist:
|
||||
|
||||
Berliner Beauftragte für Datenschutz und Informationsfreiheit
|
||||
|
||||
Friedrichstr. 219
|
||||
|
||||
10969 Berlin
|
||||
|
||||
www.datenschutz-berlin.de
|
||||
|
||||
English.
|
||||
You have the right to lodge a complaint with a
|
||||
supervisory authority; ours is the Berlin data-protection
|
||||
commissioner (address above).
|
||||
|
||||
Privacy Manifest
|
||||
|
||||
A plain-language summary of what we do, for readers who prefer
|
||||
behaviour over paragraphs. Binding text is above.
|
||||
|
||||
Planner (planner.trails.cool)
|
||||
|
||||
Anonymous by design. No account, no identifier, nothing persisted
|
||||
past the session.
|
||||
|
||||
No cookies, no localStorage, no sessionStorage
|
||||
|
||||
No browser-side error tracking (Sentry is not loaded)
|
||||
|
||||
Session data is deleted automatically after 7 days of inactivity
|
||||
|
||||
Journal (trails.cool)
|
||||
|
||||
Holds only what you put in. Exportable any time, deletable any time.
|
||||
|
||||
Account: email, username, display name, passkey public key
|
||||
|
||||
Routes: GPX, geometry, title, description
|
||||
|
||||
Activities: title, description, date, linked route
|
||||
|
||||
GPX / JSON export available per object and overall
|
||||
|
||||
Sentry
|
||||
|
||||
Journal server: always on
|
||||
|
||||
Journal browser: only after login, torn down on logout
|
||||
|
||||
Planner: server only; the browser never loads Sentry
|
||||
|
||||
IPs not actively stored, no cookies, no full headers (
|
||||
sendDefaultPii: false
|
||||
)
|
||||
|
||||
No replays (replay integration not installed, sample rates 0)
|
||||
|
||||
User ID only (no email/username) on Journal-logged-in events
|
||||
|
||||
What we don't do
|
||||
|
||||
Sell data
|
||||
|
||||
Show ads
|
||||
|
||||
Build user profiles
|
||||
|
||||
Run tracking pixels, analytics, or A/B tests
|
||||
|
||||
Security
|
||||
|
||||
Auth via passkey (WebAuthn) or magic link — no passwords stored
|
||||
|
||||
HTTPS + HSTS preload on all origins; session cookies httpOnly + Secure
|
||||
|
||||
CSP, X-Frame-Options, X-Content-Type-Options on every response
|
||||
|
||||
Containers run non-root; host firewall restricts to HTTP/HTTPS/SSH
|
||||
|
||||
Gitleaks + dependency audit on every PR
|
||||
|
||||
Vulnerability reports:
|
||||
|
||||
SECURITY.md
|
||||
157
docs/legal-archive/terms-2026-04-19.md
Normal file
157
docs/legal-archive/terms-2026-04-19.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Terms of Service — trails.cool
|
||||
|
||||
Nutzungsbedingungen / Terms of Service
|
||||
|
||||
Stand / Last updated: 2026-04-19 • Alpha — subject to change. Die
|
||||
deutsche Fassung ist maßgeblich. The German version is authoritative;
|
||||
English summaries follow each section.
|
||||
|
||||
1. Gegenstand und Alpha-Status
|
||||
|
||||
trails.cool ist ein experimenteller, derzeit kostenloser Dienst zur Planung
|
||||
und Dokumentation von Outdoor-Aktivitäten. Der Dienst befindet sich
|
||||
in aktiver Entwicklung (Alpha). Funktionen, Schnittstellen und
|
||||
gespeicherte Daten können jederzeit ohne Vorankündigung geändert,
|
||||
unterbrochen, gelöscht oder eingestellt werden. Eine
|
||||
Verfügbarkeitsgarantie besteht nicht.
|
||||
|
||||
English.
|
||||
trails.cool is an experimental service,
|
||||
currently free of charge, in
|
||||
active alpha development. Features, data, and availability can
|
||||
change at any time without notice. No availability is guaranteed.
|
||||
|
||||
2. Mindestalter
|
||||
|
||||
Die Nutzung eines Journal-Kontos ist erst ab einem Alter von 16
|
||||
Jahren zulässig. Anonyme Nutzung des Planners unterliegt keiner
|
||||
Altersbeschränkung.
|
||||
|
||||
English.
|
||||
A Journal account is available only to users
|
||||
aged 16 or older. Anonymous use of the Planner has no age
|
||||
requirement.
|
||||
|
||||
3. Dienstverfügbarkeit
|
||||
|
||||
Der Betreiber kann den Dienst jederzeit ändern, einschränken,
|
||||
unterbrechen oder einstellen, auch einzelne Funktionen oder
|
||||
einzelne Konten. Wartungs- und Ausfallzeiten sind möglich.
|
||||
|
||||
English.
|
||||
The operator may modify, limit, interrupt, or
|
||||
discontinue the service (or parts of it, or individual accounts)
|
||||
at any time. Downtime and maintenance windows may occur.
|
||||
|
||||
4. Zurücksetzen der Datenbank
|
||||
|
||||
Während der Alpha-Phase behält sich der Betreiber ausdrücklich das
|
||||
Recht vor, die gesamte Datenbank zurückzusetzen oder einzelne
|
||||
Datensätze ohne vorherige Benachrichtigung zu löschen.
|
||||
Nutzer:innen sollten wichtige Routen und Aktivitäten selbstständig
|
||||
über die Export-Funktion (GPX bzw. JSON) sichern.
|
||||
|
||||
English.
|
||||
During alpha, the operator may reset the entire
|
||||
database or delete individual records without notice. Back up
|
||||
anything important via the GPX / JSON export.
|
||||
|
||||
5. Eigenverantwortung für Daten
|
||||
|
||||
Nutzer:innen sind für die Sicherung ihrer Inhalte selbst
|
||||
verantwortlich. GPX- und JSON-Exporte sind im Journal jederzeit
|
||||
für jede Route und Aktivität verfügbar. Der Betreiber schuldet
|
||||
keine Datenwiederherstellung.
|
||||
|
||||
English.
|
||||
You are responsible for backing up your own
|
||||
content. Exports are always available in the Journal; the
|
||||
operator owes no data recovery.
|
||||
|
||||
6. Inhalte und Nutzungsrechte
|
||||
|
||||
Die von Ihnen eingestellten Inhalte (Routen, Aktivitäten,
|
||||
Beschreibungen, GPX-Dateien) bleiben Ihr Eigentum. Sie räumen dem
|
||||
Betreiber lediglich das einfache, nicht übertragbare Recht ein,
|
||||
diese Inhalte zum Zweck des Betriebs des Dienstes zu speichern,
|
||||
zu verarbeiten und Ihnen wieder anzuzeigen. Eine darüber
|
||||
hinausgehende Nutzung, Veröffentlichung oder Weitergabe findet
|
||||
nicht statt.
|
||||
|
||||
English.
|
||||
You retain ownership of everything you upload.
|
||||
You grant the operator a limited, non-transferable licence to
|
||||
store, process, and display your content solely for operating the
|
||||
service; no other use, publication, or sharing.
|
||||
|
||||
7. Nutzungsregeln
|
||||
|
||||
Keine Verbreitung rechtswidriger Inhalte
|
||||
|
||||
Keine missbräuchliche Nutzung (Spam, hochfrequente automatisierte
|
||||
Abfragen, Denial-of-Service)
|
||||
|
||||
Kein Umgehen technischer Schutzmaßnahmen
|
||||
|
||||
Kein massenhaftes automatisiertes Auslesen von Daten
|
||||
|
||||
English.
|
||||
No illegal content, no abuse (spam, flooding,
|
||||
DoS), no circumvention of technical protections, no bulk
|
||||
automated data extraction.
|
||||
|
||||
8. Kontobeendigung
|
||||
|
||||
Sie können Ihr Konto jederzeit über die Einstellungen löschen;
|
||||
damit werden auch die zugehörigen Inhalte gelöscht. Der Betreiber
|
||||
kann Konten bei Verstößen gegen diese Bedingungen nach
|
||||
angemessener Interessenabwägung sperren oder löschen.
|
||||
|
||||
English.
|
||||
Delete your account (and its content) any time
|
||||
via settings. The operator may suspend or delete accounts that
|
||||
violate these terms, balancing interests reasonably.
|
||||
|
||||
9. Haftung
|
||||
|
||||
Der Dienst wird ohne Gewährleistung bereitgestellt. Der Betreiber
|
||||
haftet uneingeschränkt bei Vorsatz und grober Fahrlässigkeit sowie
|
||||
bei Verletzung von Leben, Körper oder Gesundheit. Im Übrigen ist
|
||||
die Haftung für leicht fahrlässige Pflichtverletzungen auf den
|
||||
vertragstypischen, vorhersehbaren Schaden bei Verletzung
|
||||
wesentlicher Vertragspflichten (Kardinalpflichten) begrenzt. Eine
|
||||
darüber hinausgehende Haftung – insbesondere für Datenverlust –
|
||||
ist ausgeschlossen, soweit gesetzlich zulässig.
|
||||
|
||||
English.
|
||||
No warranty. Unlimited liability for intent,
|
||||
gross negligence, and injury to life, body, or health. For
|
||||
slight negligence, liability is limited to foreseeable damages
|
||||
arising from breach of material contractual duties. Liability
|
||||
beyond that — in particular for data loss — is excluded to the
|
||||
extent permitted by law.
|
||||
|
||||
10. Änderungen der Bedingungen
|
||||
|
||||
Diese Nutzungsbedingungen können sich während der Alpha-Phase
|
||||
ändern. Bei wesentlichen Änderungen werden registrierte
|
||||
Nutzer:innen per E-Mail informiert und müssen die neuen
|
||||
Bedingungen erneut bestätigen.
|
||||
|
||||
English.
|
||||
These terms may change during alpha. Registered
|
||||
users will be emailed about material changes and prompted to
|
||||
re-accept.
|
||||
|
||||
11. Anwendbares Recht
|
||||
|
||||
Es gilt deutsches Recht unter Ausschluss des UN-Kaufrechts. Für
|
||||
Verbraucher:innen mit gewöhnlichem Aufenthalt in einem Mitgliedstaat
|
||||
der Europäischen Union bleibt der zwingende Schutz des Rechts ihres
|
||||
Aufenthaltslandes unberührt.
|
||||
|
||||
English.
|
||||
German law applies, excluding the UN Convention
|
||||
on Contracts for the International Sale of Goods. Consumers
|
||||
habitually residing in an EU member state retain the mandatory
|
||||
protection of their national law.
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
|
|
@ -654,6 +654,12 @@ importers:
|
|||
|
||||
packages/ui: {}
|
||||
|
||||
scripts:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
version: 22.19.17
|
||||
|
||||
packages:
|
||||
|
||||
'@adobe/css-tools@4.4.4':
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
- "scripts"
|
||||
|
||||
catalog:
|
||||
react: ^19.2.5
|
||||
|
|
|
|||
31
scripts/README.md
Normal file
31
scripts/README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# scripts
|
||||
|
||||
One-off CLIs for maintenance tasks that don't belong inside any of the
|
||||
apps or packages. Small enough to live in a single file each.
|
||||
|
||||
This folder is a pnpm workspace (`@trails-cool/scripts`) purely so TypeScript
|
||||
has a home to resolve `@types/node` from — it isn't published, bundled, or
|
||||
deployed. Each script is a self-contained `.ts` file run with
|
||||
`node --experimental-strip-types`.
|
||||
|
||||
## Contents
|
||||
|
||||
| Script | Purpose |
|
||||
|---|---|
|
||||
| `render-legal.ts` | Render a legal page (Terms / Privacy / Imprint) from its TSX source to plain markdown for `docs/legal-archive/`. See [`docs/legal-archive/README.md`](../docs/legal-archive/README.md). |
|
||||
| `check-dockerfiles.sh` | Bash script run in CI to verify every workspace package is COPY'd into each app's Dockerfile. |
|
||||
|
||||
## Adding a script
|
||||
|
||||
1. Drop the file in this folder (`.ts` preferred; shell is fine for
|
||||
filesystem/docker glue).
|
||||
2. For TypeScript, `tsconfig.json` already picks up every `*.ts` in this
|
||||
directory.
|
||||
3. Run it with `node --experimental-strip-types scripts/<name>.ts <args>`.
|
||||
4. Document purpose + invocation in the table above.
|
||||
|
||||
## Why not per-package scripts?
|
||||
|
||||
Some things (Dockerfile audits, legal-archive rendering, DB one-offs) don't
|
||||
belong inside any single workspace. Keeping them here avoids cross-package
|
||||
dependencies and keeps the app/package roots focused on product code.
|
||||
13
scripts/package.json
Normal file
13
scripts/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "@trails-cool/scripts",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:"
|
||||
}
|
||||
}
|
||||
126
scripts/render-legal.ts
Executable file
126
scripts/render-legal.ts
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env -S node --experimental-strip-types
|
||||
/**
|
||||
* Render a trails.cool legal page (Terms / Privacy / Impressum) from its TSX
|
||||
* source into plain markdown suitable for `docs/legal-archive/`.
|
||||
*
|
||||
* Usage (from repo root):
|
||||
* node --experimental-strip-types scripts/render-legal.ts terms > docs/legal-archive/terms-YYYY-MM-DD.md
|
||||
* node --experimental-strip-types scripts/render-legal.ts privacy > docs/legal-archive/privacy-YYYY-MM-DD.md
|
||||
* node --experimental-strip-types scripts/render-legal.ts imprint > docs/legal-archive/imprint-YYYY-MM-DD.md
|
||||
*
|
||||
* Strips JSX tags + attributes and resolves `operator.*` placeholders from
|
||||
* operator.ts plus `TERMS_VERSION` / `PRIVACY_LAST_UPDATED` from legal.ts so
|
||||
* the output is a clean human-readable snapshot.
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const REPO_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
||||
const OPERATOR_FILE = join(REPO_ROOT, "apps/journal/app/lib/operator.ts");
|
||||
const LEGAL_FILE = join(REPO_ROOT, "apps/journal/app/lib/legal.ts");
|
||||
|
||||
const PAGES = {
|
||||
terms: { file: "apps/journal/app/routes/legal.terms.tsx", title: "Terms of Service — trails.cool" },
|
||||
privacy: { file: "apps/journal/app/routes/legal.privacy.tsx", title: "Privacy Policy — trails.cool" },
|
||||
imprint: { file: "apps/journal/app/routes/legal.imprint.tsx", title: "Impressum — trails.cool" },
|
||||
} as const;
|
||||
|
||||
type Page = keyof typeof PAGES;
|
||||
|
||||
function loadOperator(): Record<string, string> {
|
||||
const src = readFileSync(OPERATOR_FILE, "utf8");
|
||||
const fields = ["name", "street", "postalCode", "city", "country", "email", "responsiblePerson"];
|
||||
const out: Record<string, string> = {};
|
||||
for (const f of fields) {
|
||||
const m = src.match(new RegExp(`${f}:\\s*"([^"]+)"`));
|
||||
out[f] = m ? m[1]! : "";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function loadLegalConstants(): Record<string, string> {
|
||||
const src = readFileSync(LEGAL_FILE, "utf8");
|
||||
const out: Record<string, string> = {};
|
||||
for (const m of src.matchAll(/export const (\w+)\s*=\s*"([^"]+)"/g)) {
|
||||
out[m[1]!] = m[2]!;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function escapeRegex(s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
function render(src: string, operator: Record<string, string>, legal: Record<string, string>): string {
|
||||
// Resolve operator placeholders
|
||||
src = src.replace(/\{operator\.address\.(\w+)\}/g, (_, k: string) => operator[k] ?? "");
|
||||
src = src.replace(/\{operator\.(\w+)\}/g, (_, k: string) => operator[k] ?? "");
|
||||
|
||||
// Resolve legal.ts constants
|
||||
for (const [name, value] of Object.entries(legal)) {
|
||||
src = src.replace(new RegExp(`\\{${escapeRegex(name)}\\}`, "g"), value);
|
||||
}
|
||||
|
||||
// Unwrap template literals and explicit single-space expressions
|
||||
src = src.replace(/\{`(.*?)`\}/gs, "$1");
|
||||
src = src.replace(/\{"\s*"\}/g, " ");
|
||||
|
||||
// Strip common JSX attributes
|
||||
src = src.replace(/\s(?:className|href|target|rel|id|name|content|htmlFor)="[^"]*"/g, "");
|
||||
|
||||
// Drop any remaining {expr} blocks (unresolved expressions)
|
||||
src = src.replace(/\{[^{}]*\}/g, "");
|
||||
|
||||
// Replace tags with newlines to preserve paragraph structure
|
||||
src = src.replace(/<[^>]+>/g, "\n");
|
||||
|
||||
// HTML entity decode (minimal)
|
||||
const entities: Array<[string, string]> = [
|
||||
["'", "'"],
|
||||
["&", "&"],
|
||||
["<", "<"],
|
||||
[">", ">"],
|
||||
[""", '"'],
|
||||
[" ", " "],
|
||||
];
|
||||
for (const [e, r] of entities) src = src.split(e).join(r);
|
||||
|
||||
// Collapse blank lines
|
||||
const lines = src.split("\n").map((l) => l.replace(/\s+$/, ""));
|
||||
const out: string[] = [];
|
||||
let prevBlank = false;
|
||||
for (const l of lines) {
|
||||
if (l.trim() === "") {
|
||||
if (!prevBlank && out.length) out.push("");
|
||||
prevBlank = true;
|
||||
} else {
|
||||
out.push(l.trim());
|
||||
prevBlank = false;
|
||||
}
|
||||
}
|
||||
return out.join("\n").trim();
|
||||
}
|
||||
|
||||
function main(): number {
|
||||
const arg = process.argv[2];
|
||||
if (!arg || !(arg in PAGES)) {
|
||||
process.stderr.write(`Usage: render-legal.ts {${Object.keys(PAGES).join("|")}}\n`);
|
||||
return 2;
|
||||
}
|
||||
const page = arg as Page;
|
||||
const operator = loadOperator();
|
||||
const legal = loadLegalConstants();
|
||||
let src = readFileSync(join(REPO_ROOT, PAGES[page].file), "utf8");
|
||||
|
||||
// Slice out the JSX tree: everything between `return (` and the closing `);`
|
||||
src = src.split(/return \(\s*\n/).slice(-1)[0]!;
|
||||
src = src.replace(/\);\s*\}\s*$/, "").trimEnd();
|
||||
|
||||
const body = render(src, operator, legal);
|
||||
process.stdout.write(`# ${PAGES[page].title}\n\n${body}\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
process.exit(main());
|
||||
8
scripts/tsconfig.json
Normal file
8
scripts/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue