- Already have an account?{" "}
+ {t("auth.alreadyHaveAccount")}{" "}
{t("auth.login")}
diff --git a/apps/journal/app/routes/legal.imprint.tsx b/apps/journal/app/routes/legal.imprint.tsx
new file mode 100644
index 0000000..d3bc4e0
--- /dev/null
+++ b/apps/journal/app/routes/legal.imprint.tsx
@@ -0,0 +1,94 @@
+import { operator } from "~/lib/operator";
+
+export function meta() {
+ return [
+ { title: "Impressum — trails.cool" },
+ { name: "robots", content: "noindex" },
+ ];
+}
+
+export default function ImprintPage() {
+ return (
+
+
Impressum
+
+
+
+ Angaben gemäß § 5 TMG
+
+
+ {operator.name}
+
+ {operator.address.street}
+
+ {operator.address.postalCode} {operator.address.city}
+
+ {operator.address.country}
+
+
+ Kontakt
+
+ E-Mail:{" "}
+
+ {operator.email}
+
+
+
+
+ Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV
+
+
+ {operator.responsiblePerson}
+
+ {operator.address.street}
+
+ {operator.address.postalCode} {operator.address.city}
+
+
+ Haftungsausschluss
+
+ Die Inhalte dieser Seiten wurden mit größtmöglicher Sorgfalt erstellt.
+ Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte
+ können wir jedoch keine Gewähr übernehmen. trails.cool befindet sich
+ in aktiver Entwicklung (Alpha) — Inhalte, Funktionen und Daten können
+ sich jederzeit ändern oder entfallen.
+
+
+
+ Als Diensteanbieter sind wir gemäß § 7 Abs. 1 TMG für eigene Inhalte
+ auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach
+ §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht
+ verpflichtet, übermittelte oder gespeicherte fremde Informationen zu
+ überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige
+ Tätigkeit hinweisen.
+
+
+
+
+
+
+
+ Legal notice (English summary)
+
+
+ trails.cool is operated from Germany by {operator.name}, reachable at
+ the address above and at{" "}
+
+ {operator.email}
+
+ . The service is currently in alpha — see the{" "}
+
+ Terms of Service
+ {" "}
+ for details.
+
+
+
+ );
+}
diff --git a/apps/journal/app/routes/legal.privacy.tsx b/apps/journal/app/routes/legal.privacy.tsx
new file mode 100644
index 0000000..c8c758d
--- /dev/null
+++ b/apps/journal/app/routes/legal.privacy.tsx
@@ -0,0 +1,238 @@
+import { operator } from "~/lib/operator";
+
+export function meta() {
+ return [
+ { title: "Datenschutzerklärung — trails.cool" },
+ { name: "robots", content: "noindex" },
+ ];
+}
+
+export default function PrivacyPage() {
+ return (
+
+
+ Datenschutzerklärung / Privacy Policy
+
+
Last updated: 2026-04-17
+
+ {/* GDPR formal sections */}
+
+
+ 1. Verantwortliche Stelle / Data Controller
+
+
+ Verantwortlich für die Datenverarbeitung auf dieser Website im Sinne
+ der DSGVO ist:
+
+
+ {operator.name}
+
+ {operator.address.street}
+
+ {operator.address.postalCode} {operator.address.city}
+
+ {operator.address.country}
+
+ E-Mail:{" "}
+
+ {operator.email}
+
+
+
+
+
+
+ 2. Rechtsgrundlagen / Legal Basis
+
+
+ Wir verarbeiten personenbezogene Daten auf Grundlage folgender
+ Rechtsgrundlagen:
+
+
+ -
+ Vertragserfüllung (Art. 6 Abs. 1 lit. b DSGVO):
+ Kontoverwaltung, Speicherung von Routen und Aktivitäten, Anmeldung
+ via Passkey / Magic Link.
+
+ -
+ Berechtigte Interessen (Art. 6 Abs. 1 lit. f DSGVO):
+ Fehlerüberwachung (Sentry) zur Sicherstellung der
+ Funktionsfähigkeit des Dienstes.
+
+ -
+ Einwilligung (Art. 6 Abs. 1 lit. a DSGVO): Bei
+ ausdrücklicher Zustimmung zu den Nutzungsbedingungen während der
+ Registrierung.
+
+
+
+
+
+
+ 3. Ihre Rechte / Your Rights
+
+
+ Als betroffene Person stehen Ihnen folgende Rechte nach DSGVO zu:
+
+
+ - Recht auf Auskunft (Art. 15 DSGVO)
+ - Recht auf Berichtigung (Art. 16 DSGVO)
+ - Recht auf Löschung (Art. 17 DSGVO)
+ - Recht auf Einschränkung der Verarbeitung (Art. 18 DSGVO)
+ - Recht auf Datenübertragbarkeit (Art. 20 DSGVO)
+ - Widerspruchsrecht (Art. 21 DSGVO)
+ - Recht auf Widerruf einer Einwilligung (Art. 7 Abs. 3 DSGVO)
+
+
+ Zur Ausübung dieser Rechte wenden Sie sich bitte an{" "}
+
+ {operator.email}
+
+ . Ein vollständiger Export Ihrer Daten ist jederzeit direkt über die
+ Einstellungen Ihres Kontos möglich (Routen als GPX, Aktivitäten als
+ GPX/JSON). Sie können Ihr Konto jederzeit selbst löschen.
+
+
+
+
+
+ 4. Beschwerderecht / Right to Complain
+
+
+ Sie haben das Recht, sich bei einer Datenschutzaufsichtsbehörde zu
+ beschweren. Zuständig für uns ist:
+
+
+ Berliner Beauftragte für Datenschutz und Informationsfreiheit
+
+ Friedrichstr. 219
+
+ 10969 Berlin
+
+
+ www.datenschutz-berlin.de
+
+
+
+
+
+
+ {/* Existing privacy manifest (plain-language) */}
+
Privacy Manifest
+
+ trails.cool is committed to privacy by design. This manifest documents
+ everything we collect, why, and how you can control it.
+
+
+
+ Planner (planner.trails.cool)
+
+ The Planner collects no personal data. Sessions are anonymous —
+ there are no user accounts, no tracking, and no analytics on your routes.
+
+
+ - No cookies (except ephemeral session state)
+ - No user accounts or login
+ - No route data is stored permanently without your action
+ - Session data is automatically deleted after 7 days of inactivity
+
+
+
+
+ Journal (trails.cool)
+
+ The Journal stores the data you explicitly provide:
+
+
+ - Account data: email, username, display name, passkey credentials
+ - Routes: name, description, GPX data, geometry
+ - Activities: title, description, date, linked routes
+
+
+ All your data is exportable at any time in open formats (GPX, JSON).
+ You can self-host your own instance and migrate your data completely.
+
+
+
+
+ Error Tracking (Sentry)
+
+ Both apps use Sentry for
+ error monitoring. This helps us find and fix bugs quickly.
+
+ What Sentry collects:
+
+ - Error details: stack traces, error messages, browser/OS info
+ - Performance traces: page load times, route navigation timing
+ - Session replays on error: a recording of the session leading up to an error (DOM snapshots, not video)
+ - User context (Journal only): only the user ID (not username or email) is attached to errors for debugging
+ - Session ID (Planner only): the anonymous session ID is attached to errors
+
+ What Sentry does NOT collect:
+
+ - Route or GPX data
+ - Passwords or passkey credentials
+ - Form input contents (masked in replays)
+
+ Data retention:
+
+ Sentry data is retained for 90 days and then automatically deleted.
+ Sentry's servers are hosted in the EU (Frankfurt).
+
+
+
+
+ Email
+
+ The Journal sends transactional emails for magic link login and welcome messages
+ via SMTP. On the official instance, emails are sent through our own mail server.
+
+ What is sent via email:
+
+ - Magic link: a one-time login link sent to your email address
+ - Welcome email: a greeting after registration
+
+
+ Self-hosted instances can configure their own SMTP server. No email content
+ is stored beyond what your mail server retains.
+
+
+
+
+ Third Parties
+
+ - Sentry (Functional Software Inc.) — error tracking, as described above
+ - OpenStreetMap — map tiles are loaded from OSM tile servers. OSM's privacy policy applies to tile requests.
+ - BRouter — routing requests are processed by our self-hosted BRouter instance. No data is sent to third parties for routing.
+ - SMTP provider — transactional emails (magic link, welcome) are delivered via SMTP. Self-hosters configure their own provider.
+
+
+
+
+ What We Don't Do
+
+ - We don't sell data
+ - We don't show ads
+ - We don't build user profiles
+ - We don't use tracking pixels or analytics
+ - We don't share data with anyone except as listed above
+
+
+
+
+ Security Practices
+
+ - Authentication: Passkey (WebAuthn) and magic link login. No passwords stored.
+ - Encryption: All traffic over HTTPS with HSTS preload. Cookies are httpOnly and secure.
+ - Headers: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options on all responses.
+ - Infrastructure: Docker containers run as non-root. Firewall restricts to HTTP/HTTPS/SSH only.
+ - CI/CD: Gitleaks secret scanning and dependency auditing on every pull request.
+ - Vulnerability reporting: See our SECURITY.md for responsible disclosure.
+
+
+
+ );
+}
diff --git a/apps/journal/app/routes/legal.terms.tsx b/apps/journal/app/routes/legal.terms.tsx
new file mode 100644
index 0000000..e36e6fc
--- /dev/null
+++ b/apps/journal/app/routes/legal.terms.tsx
@@ -0,0 +1,187 @@
+export function meta() {
+ return [
+ { title: "Terms of Service — trails.cool" },
+ { name: "robots", content: "noindex" },
+ ];
+}
+
+export default function TermsPage() {
+ return (
+
+
+ Nutzungsbedingungen / Terms of Service
+
+
+ Last updated: 2026-04-17 • Alpha version — subject to change
+
+
+ {/* Deutsche Fassung (maßgebend) */}
+
+ Deutsche Fassung
+
+
+ 1. Gegenstand und Alpha-Status
+
+
+ trails.cool ist ein experimenteller 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, gelöscht oder eingestellt
+ werden. Eine Verfügbarkeitsgarantie besteht nicht.
+
+
+
+ 2. 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)
+ sichern.
+
+
+
+ 3. Haftungsausschluss
+
+
+ Der Dienst wird ohne jegliche Gewährleistung bereitgestellt. Eine Haftung
+ für Schäden, die durch die Nutzung oder Nichtverfügbarkeit des Dienstes
+ entstehen, ist ausgeschlossen, soweit dies gesetzlich zulässig ist. Bei
+ Vorsatz und grober Fahrlässigkeit sowie bei Verletzung von Leben, Körper
+ und Gesundheit gelten die gesetzlichen Vorschriften.
+
+
+
+ 4. Nutzungsregeln
+
+
+ - Keine Verbreitung rechtswidriger Inhalte
+ - Keine missbräuchliche Nutzung (Spam, automatisierte Abfragen in
+ hoher Frequenz, Denial-of-Service)
+
+ - Kein Umgehen technischer Schutzmaßnahmen
+
+
+
+ 5. Eigenverantwortung für Daten
+
+
+ Nutzer:innen sind dafür verantwortlich, regelmäßig Sicherungskopien ihrer
+ Routen und Aktivitäten anzufertigen. GPX-Exporte sind im Journal für
+ jede Route und Aktivität verfügbar.
+
+
+
+ 6. Beendigung
+
+
+ Nutzer:innen können ihr Konto jederzeit über die Einstellungen löschen.
+ Der Betreiber kann Konten bei Verstößen gegen diese Bedingungen sperren
+ oder löschen.
+
+
+
+ 7. Änderungen
+
+
+ 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.
+
+
+
+ 8. Anwendbares Recht
+
+
+ Es gilt deutsches Recht unter Ausschluss des UN-Kaufrechts. Verbraucher:innen
+ mit gewöhnlichem Aufenthalt in der EU genießen den zwingenden Schutz
+ ihres nationalen Verbraucherrechts.
+
+
+
+
+
+ {/* English translation (informational) */}
+
+ English version
+
+ The German version is authoritative. This English text is a translation
+ for convenience.
+
+
+
+ 1. Alpha status
+
+
+ trails.cool is an experimental service for planning and documenting
+ outdoor activities. It is under active development (alpha). Features,
+ APIs, and stored data may change, be deleted, or be discontinued at any
+ time without notice. No availability is guaranteed.
+
+
+
+ 2. Database resets
+
+
+ During alpha, the operator expressly reserves the right to reset the
+ entire database or delete individual records without prior notice. Users
+ should back up important routes and activities via the GPX export.
+
+
+
+ 3. No warranty
+
+
+ The service is provided without any warranty. Liability for damages
+ arising from use or unavailability is excluded to the maximum extent
+ permitted by law. Statutory provisions apply for intent, gross
+ negligence, and injury to life, body, or health.
+
+
+
+ 4. Acceptable use
+
+
+ - No illegal content
+ - No abusive use (spam, high-frequency automated requests, denial of service)
+ - No circumvention of technical protections
+
+
+
+ 5. Data responsibility
+
+
+ Users are responsible for regularly backing up their routes and
+ activities. GPX exports are available in the Journal for every route
+ and activity.
+
+
+
+ 6. Termination
+
+
+ Users can delete their account at any time via settings. The operator
+ may suspend or delete accounts that violate these terms.
+
+
+
+ 7. Changes
+
+
+ These terms may change during alpha. Registered users will be notified
+ by email of material changes and must re-accept the updated terms.
+
+
+
+ 8. Governing law
+
+
+ German law applies, excluding the UN Convention on Contracts for the
+ International Sale of Goods. Consumers habitually residing in the EU
+ retain the mandatory protection of their national consumer law.
+
+
+
+ );
+}
diff --git a/apps/journal/app/routes/privacy.tsx b/apps/journal/app/routes/privacy.tsx
index ef38871..a99cafc 100644
--- a/apps/journal/app/routes/privacy.tsx
+++ b/apps/journal/app/routes/privacy.tsx
@@ -1,128 +1,5 @@
-import type { Route } from "./+types/privacy";
+import { redirect } from "react-router";
-export function meta(_args: Route.MetaArgs) {
- return [{ title: "Privacy — trails.cool" }];
-}
-
-export default function PrivacyPage() {
- return (
-
-
Privacy Manifest
-
- trails.cool is committed to privacy by design. This manifest documents
- everything we collect, why, and how you can control it.
-
-
-
- Planner (planner.trails.cool)
-
- The Planner collects no personal data. Sessions are anonymous —
- there are no user accounts, no tracking, and no analytics on your routes.
-
-
- - No cookies (except ephemeral session state)
- - No user accounts or login
- - No route data is stored permanently without your action
- - Session data is automatically deleted after 7 days of inactivity
-
-
-
-
- Journal (trails.cool)
-
- The Journal stores the data you explicitly provide:
-
-
- - Account data: email, username, display name, passkey credentials
- - Routes: name, description, GPX data, geometry
- - Activities: title, description, date, linked routes
-
-
- All your data is exportable at any time in open formats (GPX, JSON).
- You can self-host your own instance and migrate your data completely.
-
-
-
-
- Error Tracking (Sentry)
-
- Both apps use Sentry for
- error monitoring. This helps us find and fix bugs quickly.
-
- What Sentry collects:
-
- - Error details: stack traces, error messages, browser/OS info
- - Performance traces: page load times, route navigation timing
- - Session replays on error: a recording of the session leading up to an error (DOM snapshots, not video)
- - User context (Journal only): user ID and username are attached to errors for debugging
- - Session ID (Planner only): the anonymous session ID is attached to errors
-
- What Sentry does NOT collect:
-
- - Route or GPX data
- - Passwords or passkey credentials
- - Form input contents (masked in replays)
-
- Data retention:
-
- Sentry data is retained for 90 days and then automatically deleted.
- Sentry's servers are hosted in the EU (Frankfurt).
-
-
-
-
- Email
-
- The Journal sends transactional emails for magic link login and welcome messages
- via SMTP. On the official instance, emails are sent through our own mail server.
-
- What is sent via email:
-
- - Magic link: a one-time login link sent to your email address
- - Welcome email: a greeting after registration
-
-
- Self-hosted instances can configure their own SMTP server. No email content
- is stored beyond what your mail server retains.
-
-
-
-
- Third Parties
-
- - Sentry (Functional Software Inc.) — error tracking, as described above
- - OpenStreetMap — map tiles are loaded from OSM tile servers. OSM's privacy policy applies to tile requests.
- - BRouter — routing requests are processed by our self-hosted BRouter instance. No data is sent to third parties for routing.
- - SMTP provider — transactional emails (magic link, welcome) are delivered via SMTP. Self-hosters configure their own provider.
-
-
-
-
- What We Don't Do
-
- - We don't sell data
- - We don't show ads
- - We don't build user profiles
- - We don't use tracking pixels or analytics
- - We don't share data with anyone except as listed above
-
-
-
-
- Security Practices
-
- - Authentication: Passkey (WebAuthn) and magic link login. No passwords stored.
- - Encryption: All traffic over HTTPS with HSTS preload. Cookies are httpOnly and secure.
- - Headers: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options on all responses.
- - Infrastructure: Docker containers run as non-root. Firewall restricts to HTTP/HTTPS/SSH only.
- - CI/CD: Gitleaks secret scanning and dependency auditing on every pull request.
- - Vulnerability reporting: See our SECURITY.md for responsible disclosure.
-
-
-
-
- Last updated: March 2026. If this manifest changes, we'll note it here.
-
-
- );
+export function loader() {
+ return redirect("/legal/privacy", 301);
}
diff --git a/apps/journal/package.json b/apps/journal/package.json
index ccb61f9..0f12f8e 100644
--- a/apps/journal/package.json
+++ b/apps/journal/package.json
@@ -24,6 +24,7 @@
"@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*",
"@trails-cool/map": "workspace:*",
+ "@trails-cool/sentry-config": "workspace:*",
"@trails-cool/types": "workspace:*",
"@trails-cool/ui": "workspace:*",
"drizzle-orm": "catalog:",
diff --git a/apps/journal/server.ts b/apps/journal/server.ts
index 8495dd0..132d28e 100644
--- a/apps/journal/server.ts
+++ b/apps/journal/server.ts
@@ -1,3 +1,5 @@
+import * as Sentry from "@sentry/node";
+import { nodeSentryConfig, drop404s } from "@trails-cool/sentry-config";
import { createRequestListener } from "@react-router/node";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { createReadStream, statSync } from "node:fs";
@@ -7,6 +9,12 @@ import { httpRequestDuration, registry } from "./app/lib/metrics.server.ts";
import { createBoss, startWorker } from "@trails-cool/jobs";
import postgres from "postgres";
+Sentry.init({
+ dsn: "https://a32ffcc575d34be072e91b20f247eeee@o4509530546634752.ingest.de.sentry.io/4509530555547728",
+ ...nodeSentryConfig("journal server"),
+ beforeSend: drop404s,
+});
+
const port = Number(process.env.PORT ?? 3000);
const CLIENT_DIR = resolve(import.meta.dirname, "build", "client");
diff --git a/apps/mobile/lib/sentry.ts b/apps/mobile/lib/sentry.ts
index 7dc01b5..c21d983 100644
--- a/apps/mobile/lib/sentry.ts
+++ b/apps/mobile/lib/sentry.ts
@@ -1,12 +1,12 @@
import * as Sentry from "@sentry/react-native";
+import { mobileSentryConfig } from "@trails-cool/sentry-config";
const SENTRY_DSN = "https://af50ac7aea803216adbe21e9dd4b896b@o4509530546634752.ingest.de.sentry.io/4511209742532688";
export function initSentry() {
Sentry.init({
dsn: SENTRY_DSN,
- tracesSampleRate: __DEV__ ? 0 : 1.0,
- enabled: !__DEV__,
+ ...mobileSentryConfig(__DEV__),
});
}
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 8aa036c..a3b13dc 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -38,6 +38,7 @@
"@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*",
"@trails-cool/map-core": "workspace:*",
+ "@trails-cool/sentry-config": "workspace:*",
"@trails-cool/types": "workspace:*",
"expo": "~55.0.15",
"expo-constants": "~55.0.14",
diff --git a/apps/planner/Dockerfile b/apps/planner/Dockerfile
index 52b221d..6693d8c 100644
--- a/apps/planner/Dockerfile
+++ b/apps/planner/Dockerfile
@@ -11,6 +11,7 @@ COPY packages/ui/package.json packages/ui/
COPY packages/map/package.json packages/map/
COPY packages/gpx/package.json packages/gpx/
COPY packages/i18n/package.json packages/i18n/
+COPY packages/sentry-config/package.json packages/sentry-config/
COPY packages/api/package.json packages/api/
COPY packages/map-core/package.json packages/map-core/
COPY packages/db/package.json packages/db/
diff --git a/apps/planner/app/entry.client.tsx b/apps/planner/app/entry.client.tsx
index f4230ed..d5b4d57 100644
--- a/apps/planner/app/entry.client.tsx
+++ b/apps/planner/app/entry.client.tsx
@@ -1,30 +1,8 @@
-import * as Sentry from "@sentry/react";
-import { useEffect } from "react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
-import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from "react-router";
import { initI18nClient } from "@trails-cool/i18n";
-const sentryEnvironment = import.meta.env.VITE_SENTRY_ENVIRONMENT ??
- (import.meta.env.PROD ? "production" : "development");
-
-Sentry.init({
- dsn: "https://5215134cd78d5e6c199e29300b8425af@o4509530546634752.ingest.de.sentry.io/4511102546608208",
- integrations: [
- Sentry.reactRouterV7BrowserTracingIntegration({
- useEffect,
- useLocation,
- useNavigationType,
- createRoutesFromChildren,
- matchRoutes,
- }),
- ],
- environment: sentryEnvironment,
- tracesSampleRate: sentryEnvironment === "ci" ? 0 : 1.0,
- enabled: import.meta.env.PROD && sentryEnvironment !== "ci",
-});
-
initI18nClient();
startTransition(() => {
diff --git a/apps/planner/app/entry.server.tsx b/apps/planner/app/entry.server.tsx
index 9220df5..5db5d23 100644
--- a/apps/planner/app/entry.server.tsx
+++ b/apps/planner/app/entry.server.tsx
@@ -1,3 +1,4 @@
+import * as Sentry from "@sentry/node";
import { PassThrough } from "node:stream";
import type { AppLoadContext, EntryContext } from "react-router";
import { createReadableStreamFromReadable } from "@react-router/node";
@@ -71,6 +72,7 @@ export default function handleRequest(
responseStatusCode = 500;
if (shellRendered) {
console.error(error);
+ Sentry.captureException(error);
}
},
},
diff --git a/apps/planner/app/routes/home.tsx b/apps/planner/app/routes/home.tsx
index 1677b7f..583f012 100644
--- a/apps/planner/app/routes/home.tsx
+++ b/apps/planner/app/routes/home.tsx
@@ -61,6 +61,9 @@ export default function Home() {