ReviewOS

also looking at this

stacks/lastsoulultra

LSU's own coaching surface, and what auditing the site turned up

#12
Merged glennmichael123 wants to merge claude/last-soul-ultra-web-6e0718 into main
86 files +3943 -1190

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

resources/data/legal.tsdeleted+0-100
Changes to resources/data/legal.ts
@@ -1,100 +0,0 @@
1/**
2 * The legal pages, verbatim.
3 *
4 * German, and left in German: these are the organiser's binding wording and a
5 * redesign restyles a policy, it does not translate or rewrite one. A
6 * translation would read as authoritative while carrying none of the legal
7 * weight, which is worse than asking a reader to translate it themselves.
8 *
9 * Each page is a list of blocks so the template can set headings and
10 * paragraphs properly instead of dumping vendor HTML into the page.
11 */
12
13export interface LegalBlock {
14 kind: 'heading' | 'text'
15 value: string
16}
17
18export interface LegalPage {
19 slug: string
20 title: string
21 /** English one-liner, so the nav and the page header stay readable. */
22 blurb: string
23 /** The language the body is actually in, for the `lang` attribute. */
24 lang: 'de' | 'en'
25 blocks: LegalBlock[]
26}
27
28export const legalPages: LegalPage[] = [
29 {
30 slug: 'imprint',
31 title: 'imprint',
32 blurb: 'Who runs the race, and where they are registered.',
33 lang: 'en',
34 blocks: [
35 { kind: 'heading', value: 'Legal Notice' },
36 { kind: 'text', value: 'Last Soul GmbH\nEhrenfeldstr. 14\n44789 Bochum\nGermany' },
37 { kind: 'heading', value: 'Represented by the Managing Director' },
38 { kind: 'text', value: 'Kim Gottwald' },
39 { kind: 'heading', value: 'Contact' },
40 { kind: 'text', value: 'Email: mail@lastsoulultra.com' },
41 { kind: 'heading', value: 'Commercial Register' },
42 { kind: 'text', value: 'Registered with the Commercial Register of the Local Court (Amtsgericht) Bochum\nCommercial Register Number: HRB 23048' },
43 { kind: 'heading', value: 'Tax Number' },
44 { kind: 'text', value: '1130 413 50751' },
45 ],
46 },
47 {
48 slug: 'privacy',
49 title: 'privacy policy',
50 blurb: 'Datenschutzerklärung. Stand: Januar 2026.',
51 lang: 'de',
52 blocks: [
53 { kind: 'heading', value: '1. Verantwortlicher' },
54 { kind: 'text', value: 'Last Soul GmbH\nDeutschland\nE-Mail: mail@lastsoulultra.com\nGeschäftsführer: Kim Gottwald' },
55 { kind: 'heading', value: '2. Allgemeine Hinweise' },
56 { kind: 'text', value: 'Der Schutz Ihrer personenbezogenen Daten ist uns wichtig. Wir verarbeiten Ihre Daten ausschließlich im Rahmen der geltenden Datenschutzgesetze, insbesondere der DSGVO und des BDSG.' },
57 { kind: 'heading', value: '3. Hosting' },
58 { kind: 'text', value: 'Unsere Website wird bei der Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland, auf Servern in Deutschland gehostet. Hetzner verarbeitet personenbezogene Daten ausschließlich in unserem Auftrag im Rahmen einer Auftragsverarbeitung gemäß Art. 28 DSGVO.' },
59 { kind: 'heading', value: '4. Server-Logfiles' },
60 { kind: 'text', value: 'Beim Besuch unserer Website werden automatisch Daten wie IP-Adresse, Browsertyp, Betriebssystem, Referrer-URL sowie Datum und Uhrzeit des Zugriffs verarbeitet. Die Verarbeitung erfolgt zur Sicherstellung des technischen Betriebs und der Sicherheit auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO.' },
61 { kind: 'heading', value: '5. Cookies und Analyse' },
62 { kind: 'text', value: 'Diese Website nutzt keine Analyse-, Tracking- oder Werbedienste. Gesetzt wird ausschließlich ein technisch notwendiges Cookie (X-CSRF-Token) zum Schutz von Formularen vor Cross-Site-Request-Forgery; es läuft nach zwei Stunden ab und dient keiner Analyse. Da keine einwilligungspflichtigen Cookies zum Einsatz kommen, ist kein Cookie-Banner erforderlich. Schriften und Bilder werden von unserem eigenen Server ausgeliefert, sodass beim Aufruf der Seiten keine Inhalte Dritter nachgeladen werden und Ihre IP-Adresse nicht an Dritte übertragen wird.' },
63 { kind: 'heading', value: '6. Newsletter' },
64 { kind: 'text', value: 'Wenn Sie unseren Newsletter abonnieren, speichern wir Ihre E-Mail-Adresse sowie den Zeitpunkt und die Herkunft der Anmeldung. Die Anmeldung erfolgt im Double-Opt-in-Verfahren: Sie erhalten zunächst eine E-Mail mit einem Bestätigungslink, und erst mit dessen Aufruf wird die Anmeldung wirksam. Bis dahin wird die Adresse nicht für den Versand verwendet. Rechtsgrundlage ist Ihre Einwilligung nach Art. 6 Abs. 1 lit. a DSGVO. Sie können diese jederzeit widerrufen, über den Abmeldelink in jeder E-Mail oder per Nachricht an uns.' },
65 { kind: 'heading', value: '7. Event-Anmeldung' },
66 { kind: 'text', value: 'Bei der Anmeldung zu unseren Events verarbeiten wir die von Ihnen angegebenen Daten zur Organisation und Durchführung der Veranstaltung.' },
67 { kind: 'heading', value: '8. Ihre Rechte' },
68 { kind: 'text', value: 'Sie haben jederzeit das Recht auf Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung, Datenübertragbarkeit sowie Widerspruch gegen die Verarbeitung Ihrer personenbezogenen Daten.' },
69 { kind: 'heading', value: '9. Beschwerderecht' },
70 { kind: 'text', value: 'Sie haben das Recht, sich bei einer Datenschutzaufsichtsbehörde zu beschweren.' },
71 { kind: 'heading', value: '10. Stand der Datenschutzerklärung' },
72 { kind: 'text', value: 'Stand: Januar 2026' },
73 ],
74 },
75 {
76 slug: 'terms',
77 title: 'terms & conditions',
78 blurb: 'Haftungsausschluss. Must be signed before the start.',
79 lang: 'de',
80 blocks: [
81 { kind: 'heading', value: 'Disclaimer' },
82 { kind: 'text', value: 'RAD RACE GmbH, Fischmarkt 9, 22767 Hamburg im Auftrag des Veranstalters Last Soul GmbH, Alte Jakobsstraße 85-86, 10179 Berlin' },
83 { kind: 'text', value: 'Mit dem Betreten des Veranstaltungsgeländes und der Teilnahme an der Veranstaltung LAST SOUL ULTRA erkennen alle Teilnehmer und Besucher den folgenden Haftungsausschluss:' },
84 { kind: 'text', value: 'Die Veranstaltung Last Soul Ultra findet auf öffentlichen Flächen statt, darunter Straßen und Wege, die für den übrigen Verkehr zugänglich sind. Alle Teilnehmer sind verpflichtet, sich strikt an die geltenden Straßenverkehrsregeln (StVO oder gleichwertige lokale Vorschriften) zu halten. Dazu gehören unter anderem die Beachtung von Verkehrszeichen, Ampeln und Vorfahrtsregeln sowie das Treffen geeigneter Vorsichtsmaßnahmen zur Gewährleistung der eigenen Sicherheit und der Sicherheit anderer. Die Teilnehmer werden hiermit darauf hingewiesen, dass die Nichtbeachtung dieser Vorschriften das Risiko schwerer Verletzungen oder tödlicher Unfälle erheblich erhöht.' },
85 { kind: 'text', value: 'Die Streckenabschnitte wurden größtenteils von den Veranstaltern auf sicherheitsrelevante Aspekte geprüft jedoch nicht zu 100% von daher bleibt die Verantwortung für die eigene Sicherheit beim Teilnehmer. Dieser Verpflichtet sich, die eigene Geschwindigkeit an die Beschaffenheiten der Strecke anzupassen so dass auch auf Hindernisse reagiert werden kann. In Anbetracht des Vorstehenden nimmt jeder Teilnehmer auf eigenes Risiko an der Veranstaltung teil.' },
86 { kind: 'text', value: 'Der Veranstalter (Last Soul GmbH) haftet nicht für einfache Fahrlässigkeit. Dies gilt sowohl für eigene Handlungen als auch für Handlungen seiner Vertreter oder Erfüllungsgehilfen. Das Vorstehende gilt nicht bei Verletzung von Leben, Körper oder Gesundheit - in diesen Fällen gelten die allgemeinen gesetzlichen Bestimmungen.' },
87 { kind: 'text', value: 'Mit meiner Anmeldung erklärt jeder Teilnehmer, dass er für die Teilnahme an dieser Veranstaltung ausreichend trainiert hat, körperlich fit ist und sein Gesundheitszustand ärztlich bestätigt wurde. Jeder Teilnehmer erklärt außerdem, dass seine Fähigkeiten es ihm ermöglichen, an einer anspruchsvollen und sehr extremen Ultra-Laufveranstaltung wie dem Last Soul Ultra teilzunehmen. Sollte er während der Veranstaltung feststellen, dass sich sein körperlicher Zustand verschlechtert, versichert er, dass der Veranstalter darüber informiert wird, damit die Last Soul GmbH geeignete Maßnahmen ergreifen kann.' },
88 { kind: 'text', value: 'Jeder Teilnehmer verpflichtet sich, die vom Veranstalter festgelegten Regeln und Vorschriften einzuhalten (die Regeln und Vorschriften werden vorab veröffentlicht und am Veranstaltungsort erneut im Athlete Briefing mitgeteilt). Jeder ist sich bewusst, dass man disqualifiziert wird, wenn die offizielle Teilnehmernummer in irgendeiner Weise verändert wird oder sich den Regeln widersetzt wird, insbesondere wenn man den Werbeaufdruck unsichtbar oder unkenntlich mache. Darüber hinaus ist es verboten, Tiere zum Veranstaltungsort mitzubringen.' },
89 { kind: 'text', value: 'Die RAD RACE GmbH und Last Soul GmbH engagiert sich in allen Bereichen ihrer Geschäftstätigkeit aktiv für den Umweltschutz. Der Veranstalter erwartet auch von den Teilnehmern und allen Beteiligten, dass sie die Umwelt respektieren. Die strikte Einhaltung aller umweltrechtlichen Vorschriften ist eine Grundvoraussetzung. Dies gilt insbesondere für die Vorschriften zur Abfallentsorgung, zum Boden- und Gewässerschutz sowie zur Vermeidung von Umweltverschmutzung. Ich bin mir bewusst, dass sich der Veranstalter das Recht vorbehält, Teilnehmer wegen Verstoßes gegen eine der oben genannten Verpflichtungen von der Veranstaltung auszuschließen. Die allgemeine Anmeldung sowie die Abbuchung von Kreditkarten werden vom Veranstalter (Last Soul GmbH) durchgeführt.' },
90 { kind: 'text', value: 'Mit der Anmeldung erklärt sich jeder Teilnehmer damit einverstanden, dass die von ihm im Zusammenhang mit seiner Teilnahme am Last Soul Ultra angefertigten Fotos, Filmaufnahmen und Interviews ohne Anspruch auf Vergütung meinerseits in Radio, Fernsehen, Internet, Werbung, Büchern, fotomechanischen Kopien, Filmen, Videokassetten, DVDs usw. verwendet, verbreitet und veröffentlicht werden dürfen und sein Name in der Ergebnisliste auf der Veranstalterwebsite und Partnerwebsite (im Rahmen der Medienberichterstattung über die Veranstaltung) veröffentlicht werden darf.' },
91 { kind: 'text', value: 'Vor Ort dürfen keine kommerziellen Aufnahmen gemacht und verbreitet werden. Zudem dürfen keine dritten Events mit dem Content des Last Soul Ultra beworben werden. Eine redaktionelle Berichterstattung in den sozialen Medien ist ausdrücklich erwünscht. Bei Fragen meldet euch gerne an mail@lastsoulultra.com' },
92 { kind: 'text', value: 'Die Teilnehmer versichern, dass ihr Geburtsdatum sowie alle anderen von ihm angegebenen Daten korrekt sind. Die Teilnahme an der Veranstaltung ist nur möglich, wenn die Teilnehmer zum Zeitpunkt der Anmeldung volljährig (18 Jahre) sind.' },
93 { kind: 'text', value: 'Der vorliegende Haftungsausschluss muss vor Beginn der Veranstaltung unterschrieben eingereicht werden. Wenn ein Teilnehmer für die Angabe eines falschen Geburtsdatums verantwortlich ist, behält sich der Veranstalter das Recht vor, eine Disqualifikation zu verhängen.' },
94 ],
95 },
96]
97
98export function legalBySlug(slug: string): LegalPage | undefined {
99 return legalPages.find(page => page.slug === slug)
100}
tests/seo.test.tsmodified+14-6
Changes to tests/seo.test.ts
@@ -1,8 +1,8 @@
11import { describe, expect, it } from 'bun:test'
22import { readFileSync, readdirSync } from 'node:fs'
33import { join } from 'node:path'
4import server from '../config/server'
45import { coachingPage } from '../resources/data/coaching'
5import { legalPages } from '../resources/data/legal'
66import { productionPages } from '../resources/data/production-pages'
77import { indexablePaths } from '../resources/data/site'
88import { publishedOrigin, robotsTxt, sitemapXml } from '../scripts/seo'
@@ -97,11 +97,19 @@ describe('seo artefacts', () => {
9797 expect(indexablePaths).toContain('/coaching/plans')
9898 })
9999
100 it('lists the canonical legal pages rather than the legacy redirects', () => {
101 for (const page of legalPages)
102 expect(indexablePaths).toContain(`/${page.slug}`)
103 for (const page of legalPages)
104 expect(indexablePaths).not.toContain(`/legal/${page.slug}`)
100 it('lists where a redirect lands, never the URL it leaves', () => {
101 // The redirect table is the list of URLs this site used to publish.
102 // Submitting one of those is submitting a 301, which costs a crawl and
103 // indexes nothing.
104 const rules = Object.entries(server.redirects ?? {})
105
106 expect(rules.length).toBeGreaterThan(0)
107 for (const [from, to] of rules) {
108 const target = typeof to === 'string' ? to : to.to
109 expect(indexablePaths).not.toContain(from)
110 if (target.startsWith('/'))
111 expect(indexablePaths).toContain(target)
112 }
105113 })
106114
107115 it('publishes absolute https URLs on a single origin', () => {