From 372134705757a167af015ff223fe90ee6fd7e697 Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Sat, 1 Aug 2026 12:33:32 +0200 Subject: [PATCH 1/3] Updated Seo Pages --- app/Seo/SchemaGraph.php | 4 ++-- config/health.php | 2 +- database/files/pages/about-us.index.yaml | 4 ++-- database/files/pages/ai.index.yaml | 4 ++-- database/files/pages/ai.llm.analytics.index.yaml | 4 ++-- database/files/pages/ai.llm.index.yaml | 4 ++-- database/files/pages/co-working.index.yaml | 4 ++-- database/files/pages/contact.index.yaml | 4 ++-- database/files/pages/jobs.index.yaml | 4 ++-- database/files/pages/legal.imprint.index.yaml | 4 ++-- database/files/pages/legal.privacy.index.yaml | 4 ++-- database/files/pages/legal.terms.index.yaml | 4 ++-- database/files/pages/media.index.yaml | 4 ++-- database/files/pages/network.index.yaml | 4 ++-- database/files/pages/network.request.index.yaml | 4 ++-- database/files/pages/news.index.yaml | 4 ++-- database/files/pages/open-source.index.yaml | 4 ++-- database/files/pages/products.index.yaml | 4 ++-- database/files/pages/services.index.yaml | 4 ++-- database/files/pages/start.index.yaml | 4 ++-- database/files/pages/technologies.index.yaml | 4 ++-- resources/views/layouts/_partials/_seo.blade.php | 7 +------ tests/Feature/Seo/MetaTagsTest.php | 4 ++-- tests/Feature/Seo/StructuredDataTest.php | 15 ++++++++------- 24 files changed, 52 insertions(+), 56 deletions(-) diff --git a/app/Seo/SchemaGraph.php b/app/Seo/SchemaGraph.php index 8533586..1014d40 100644 --- a/app/Seo/SchemaGraph.php +++ b/app/Seo/SchemaGraph.php @@ -229,8 +229,8 @@ private function breadcrumbList(string $url): ?array } /** - * Title of the page being rendered, stripped of the " – codebar Solutions AG" - * suffix that belongs in but not in a breadcrumb. + * Title of the page being rendered, reduced to the part before the first + * separator: a breadcrumb carries the label, not the full title. */ private function currentPageName(): ?string { diff --git a/config/health.php b/config/health.php index 13f30e8..3b23c68 100644 --- a/config/health.php +++ b/config/health.php @@ -26,7 +26,7 @@ /* * Notifications will only get sent if this option is set to `true`. */ - 'enabled' => true, + 'enabled' => (bool) env('HEALTH_NOTIFICATION_EMAIL'), 'notifications' => [ CheckFailedNotification::class => ['mail'], diff --git a/database/files/pages/about-us.index.yaml b/database/files/pages/about-us.index.yaml index 400110e..1c851eb 100644 --- a/database/files/pages/about-us.index.yaml +++ b/database/files/pages/about-us.index.yaml @@ -2,8 +2,8 @@ key: about-us.index robots: index,follow image: images/pages/about-us.index.svg title: - de_CH: 'Team – die Menschen hinter codebar Solutions AG' - en_CH: 'Team – the People Behind codebar Solutions AG' + de_CH: 'Team – die Menschen hinter codebar' + en_CH: 'Team – the People Behind codebar' description: de_CH: 'Klein aus Überzeugung: kurze Wege, direkte Ansprechpersonen – und Ausbildung als Teil unserer Kultur. Lerne die Menschen hinter codebar kennen.' en_CH: 'Small by conviction: short paths, direct contacts – and training as part of our culture. Meet the people behind codebar.' diff --git a/database/files/pages/ai.index.yaml b/database/files/pages/ai.index.yaml index ab6a657..7b52b58 100644 --- a/database/files/pages/ai.index.yaml +++ b/database/files/pages/ai.index.yaml @@ -2,8 +2,8 @@ key: ai.index robots: index,follow image: images/pages/ai.index.svg title: - de_CH: 'Lokale KI auf eigener Infrastruktur | codebar Solutions AG' - en_CH: 'Local AI on Our Own Infrastructure | codebar Solutions AG' + de_CH: 'Lokale KI auf eigener Infrastruktur' + en_CH: 'Local AI on Our Own Infrastructure' description: de_CH: 'Transparenz statt Buzzwords: Wir zeigen offen, wie wir KI in der eigenen Arbeit einsetzen – mit lokalen Open-Source-Modellen und echten Nutzungszahlen.' en_CH: 'Transparency over buzzwords: an open look at how we use AI in our own work – with local open-source models and real usage figures.' diff --git a/database/files/pages/ai.llm.analytics.index.yaml b/database/files/pages/ai.llm.analytics.index.yaml index 5ab7ac3..1f49779 100644 --- a/database/files/pages/ai.llm.analytics.index.yaml +++ b/database/files/pages/ai.llm.analytics.index.yaml @@ -2,8 +2,8 @@ key: ai.llm.analytics.index robots: index,follow image: images/pages/ai.llm.analytics.index.svg title: - de_CH: 'LLM-Nutzungsstatistik – codebar Solutions AG' - en_CH: 'LLM Usage Analytics – codebar Solutions AG' + de_CH: 'LLM-Nutzungsstatistik' + en_CH: 'LLM Usage Analytics' description: de_CH: 'Token-Verbrauch und Anfragen unserer lokal betriebenen Modelle – transparent aufgeschlüsselt pro Monat und Modell.' en_CH: 'Token consumption and requests of our locally run models – transparently broken down per month and model.' diff --git a/database/files/pages/ai.llm.index.yaml b/database/files/pages/ai.llm.index.yaml index 6974ddd..a0c02f5 100644 --- a/database/files/pages/ai.llm.index.yaml +++ b/database/files/pages/ai.llm.index.yaml @@ -2,8 +2,8 @@ key: ai.llm.index robots: index,follow image: images/pages/ai.llm.index.svg title: - de_CH: 'Unsere lokalen LLMs im Einsatz – codebar Solutions AG' - en_CH: 'Our Local LLMs in Action – codebar Solutions AG' + de_CH: 'Unsere lokalen LLMs im Einsatz' + en_CH: 'Our Local LLMs in Action' description: de_CH: 'Diese lokalen Open-Source-Modelle betreiben wir aktuell – auf eigener Hardware im hauseigenen Bürokeller. Modelle, Infrastruktur und Nutzung.' en_CH: 'The local open-source models we currently run – on our own hardware, in our own office basement. Models, infrastructure and usage.' diff --git a/database/files/pages/co-working.index.yaml b/database/files/pages/co-working.index.yaml index 56c9121..1915a01 100644 --- a/database/files/pages/co-working.index.yaml +++ b/database/files/pages/co-working.index.yaml @@ -2,8 +2,8 @@ key: co-working.index robots: index,follow image: images/pages/co-working.index.svg title: - de_CH: 'Co-Working bei codebar Solutions AG' - en_CH: 'Co-Working at codebar Solutions AG' + de_CH: 'Co-Working bei codebar' + en_CH: 'Co-Working at codebar' description: de_CH: 'Ein professioneller Arbeitsplatz in Oberwil — ruhig, gut ausgestattet und Teil eines Tech-Teams.' en_CH: 'A professional workspace in Oberwil — quiet, well-equipped, and embedded in a tech team.' diff --git a/database/files/pages/contact.index.yaml b/database/files/pages/contact.index.yaml index 233b930..4e13bbb 100644 --- a/database/files/pages/contact.index.yaml +++ b/database/files/pages/contact.index.yaml @@ -2,8 +2,8 @@ key: contact.index robots: index,follow image: images/pages/contact.index.svg title: - de_CH: "Kontakt – lass uns sprechen | codebar Solutions AG" - en_CH: "Contact – Let's Talk | codebar Solutions AG" + de_CH: "Kontakt – lass uns sprechen" + en_CH: "Contact – Let's Talk" description: de_CH: 'Erzähl uns von deiner Idee oder deinem Projekt – wir hören zu. Telefon, E-Mail und unsere zwei Standorte in der Region Basel.' en_CH: "Tell us about your idea or your project – we'll listen. Phone, email and our two locations in the Basel region." diff --git a/database/files/pages/jobs.index.yaml b/database/files/pages/jobs.index.yaml index 427a49c..887b2c7 100644 --- a/database/files/pages/jobs.index.yaml +++ b/database/files/pages/jobs.index.yaml @@ -2,8 +2,8 @@ key: jobs.index robots: index,follow image: images/pages/jobs.index.svg title: - de_CH: 'Stellen – arbeiten bei codebar Solutions AG' - en_CH: 'Jobs – Working at codebar Solutions AG' + de_CH: 'Stellen – arbeiten bei codebar' + en_CH: 'Jobs – Working at codebar' description: de_CH: 'Klein aus Überzeugung: Verantwortung ab Tag eins, offene Technologien und Praktika mitten im Projektalltag. So arbeitest du bei codebar.' en_CH: 'Small by conviction: responsibility from day one, open technologies and internships inside real projects. This is what working at codebar is like.' diff --git a/database/files/pages/legal.imprint.index.yaml b/database/files/pages/legal.imprint.index.yaml index c52970a..0170370 100644 --- a/database/files/pages/legal.imprint.index.yaml +++ b/database/files/pages/legal.imprint.index.yaml @@ -2,8 +2,8 @@ key: legal.imprint.index robots: index,follow image: images/pages/legal.imprint.index.svg title: - de_CH: 'Impressum – codebar Solutions AG' - en_CH: 'Imprint – codebar Solutions AG' + de_CH: 'Impressum' + en_CH: 'Imprint' description: de_CH: 'Rechtliche Angaben zur codebar Solutions AG: Rechtsform, UID-Nummer, Handelsregistereintrag, Zeichnungsberechtigte sowie Kontaktdaten und Haftungsausschluss.' en_CH: 'Legal details of codebar Solutions AG: legal form, UID number, commercial register entry, authorised representatives, contact details and disclaimer.' diff --git a/database/files/pages/legal.privacy.index.yaml b/database/files/pages/legal.privacy.index.yaml index dac45f9..4533186 100644 --- a/database/files/pages/legal.privacy.index.yaml +++ b/database/files/pages/legal.privacy.index.yaml @@ -2,8 +2,8 @@ key: legal.privacy.index robots: index,follow image: images/pages/legal.privacy.index.svg title: - de_CH: 'Datenschutz – codebar Solutions AG' - en_CH: 'Privacy – codebar Solutions AG' + de_CH: 'Datenschutz' + en_CH: 'Privacy' description: de_CH: 'Wie wir Personendaten auf codebar.ch bearbeiten: welche Daten anfallen, wofür wir sie nutzen, wie lange wir sie aufbewahren – und welche Rechte du hast.' en_CH: 'How we process personal data on codebar.ch: what we collect, why, how long we keep it – and the rights you have.' diff --git a/database/files/pages/legal.terms.index.yaml b/database/files/pages/legal.terms.index.yaml index b3ddd20..2a4e3b5 100644 --- a/database/files/pages/legal.terms.index.yaml +++ b/database/files/pages/legal.terms.index.yaml @@ -2,8 +2,8 @@ key: legal.terms.index robots: index,follow image: images/pages/legal.terms.index.svg title: - de_CH: 'AGB – codebar Solutions AG' - en_CH: 'Terms & Conditions – codebar Solutions AG' + de_CH: 'AGB' + en_CH: 'Terms & Conditions' description: de_CH: 'Die Allgemeinen Geschäftsbedingungen der codebar Solutions AG: Angebote, Projektabwicklung, Nutzungsrechte, Haftung und Support.' en_CH: 'The general terms and conditions of codebar Solutions AG: offers, project delivery, rights of use, liability and support.' diff --git a/database/files/pages/media.index.yaml b/database/files/pages/media.index.yaml index 7ffeb33..bca146c 100644 --- a/database/files/pages/media.index.yaml +++ b/database/files/pages/media.index.yaml @@ -2,8 +2,8 @@ key: media.index robots: index,follow image: images/pages/media.index.svg title: - de_CH: 'Medien & Markenassets – codebar Solutions AG' - en_CH: 'Media & Brand Assets – codebar Solutions AG' + de_CH: 'Medien & Markenassets' + en_CH: 'Media & Brand Assets' description: de_CH: 'Offizielle codebar-Logos für Presse und Partner – farbig, invertiert, schwarz-weiss, als PNG und SVG.' en_CH: 'Official codebar logos for press and partners – colour, inverted, black and white, as PNG and SVG.' diff --git a/database/files/pages/network.index.yaml b/database/files/pages/network.index.yaml index e95314c..cd37f62 100644 --- a/database/files/pages/network.index.yaml +++ b/database/files/pages/network.index.yaml @@ -2,8 +2,8 @@ key: network.index robots: index,follow image: images/pages/network.index.svg title: - de_CH: 'Netzwerk – Partner & Community | codebar Solutions AG' - en_CH: 'Network – Partners & Community | codebar Solutions AG' + de_CH: 'Netzwerk – Partner & Community' + en_CH: 'Network – Partners & Community' description: de_CH: 'Mit wem wir arbeiten: Projekt-, Software- und Infrastrukturpartner, unser Engagement in der Community – und die Labels, die dahinterstehen.' en_CH: 'Who we work with: project, software and infrastructure partners, our community engagement – and the labels behind it.' diff --git a/database/files/pages/network.request.index.yaml b/database/files/pages/network.request.index.yaml index 6e9c871..a64175e 100644 --- a/database/files/pages/network.request.index.yaml +++ b/database/files/pages/network.request.index.yaml @@ -2,8 +2,8 @@ key: network.request.index robots: noindex,nofollow image: images/pages/network.request.index.svg title: - de_CH: 'Netzwerk-Profil aktualisieren – codebar Solutions AG' - en_CH: 'Update network profile – codebar Solutions AG' + de_CH: 'Netzwerk-Profil aktualisieren' + en_CH: 'Update network profile' description: de_CH: 'Persönlichen Link anfordern, um das eigene Profil im codebar Netzwerk zu aktualisieren.' en_CH: 'Request a personal link to update your codebar network profile.' diff --git a/database/files/pages/news.index.yaml b/database/files/pages/news.index.yaml index 21ca761..0c262cd 100644 --- a/database/files/pages/news.index.yaml +++ b/database/files/pages/news.index.yaml @@ -2,8 +2,8 @@ key: news.index robots: index,follow image: images/pages/news.index.svg title: - de_CH: 'Neuigkeiten & Insights – codebar Solutions AG' - en_CH: 'News & Insights – codebar Solutions AG' + de_CH: 'Neuigkeiten & Insights' + en_CH: 'News & Insights' description: de_CH: 'Aktuelle News, Fachbeiträge und Trends rund um Softwareentwicklung, offene Technologien und digitale Innovation von codebar.' en_CH: 'Latest news and expert insights on software development, open technologies and digital innovation from codebar.' diff --git a/database/files/pages/open-source.index.yaml b/database/files/pages/open-source.index.yaml index d62579c..0fd6348 100644 --- a/database/files/pages/open-source.index.yaml +++ b/database/files/pages/open-source.index.yaml @@ -2,8 +2,8 @@ key: open-source.index robots: index,follow image: images/pages/open-source.index.svg title: - de_CH: 'Open-Source-Beiträge – codebar Solutions AG' - en_CH: 'Open Source Contributions – codebar Solutions AG' + de_CH: 'Open-Source-Beiträge' + en_CH: 'Open Source Contributions' description: de_CH: 'Unsere Beiträge an die Open-Source-Community – Packages, Tools und Libraries, entwickelt und gepflegt von codebar.' en_CH: 'Our contributions to the open source community – packages, tools and libraries developed and maintained by codebar.' diff --git a/database/files/pages/products.index.yaml b/database/files/pages/products.index.yaml index bd82d34..8c8989f 100644 --- a/database/files/pages/products.index.yaml +++ b/database/files/pages/products.index.yaml @@ -2,8 +2,8 @@ key: products.index robots: index,follow image: images/pages/products.index.svg title: - de_CH: 'Nutzerzentrierte Softwarelösungen – codebar Solutions AG' - en_CH: 'User-Centric Software Solutions – codebar Solutions AG' + de_CH: 'Nutzerzentrierte Softwarelösungen' + en_CH: 'User-Centric Software Solutions' description: de_CH: 'Nutzerzentrierte Softwarelösungen mit echtem Mehrwert – entwickelt mit offenen Technologien und Standards.' en_CH: 'Our software solutions are built around the needs of users, delivering real value through open technologies and standards.' diff --git a/database/files/pages/services.index.yaml b/database/files/pages/services.index.yaml index 0d03957..440ebef 100644 --- a/database/files/pages/services.index.yaml +++ b/database/files/pages/services.index.yaml @@ -2,8 +2,8 @@ key: services.index robots: index,follow image: images/pages/services.index.svg title: - de_CH: 'Konzeption, Software, DMS & ERP | codebar Solutions AG' - en_CH: 'Concept, Software, DMS & ERP | codebar Solutions AG' + de_CH: 'Konzeption, Software, DMS & ERP' + en_CH: 'Concept, Software, DMS & ERP' description: de_CH: 'Vier Bereiche, ein Weg: von der ersten Idee bis zum laufenden Betrieb – Konzeption, Softwareentwicklung, Dokumentenmanagement und ERP.' en_CH: 'Four areas, one path: from the first idea to day-to-day operations – concept design, software development, document management and ERP.' diff --git a/database/files/pages/start.index.yaml b/database/files/pages/start.index.yaml index 4f06c9e..f5a2038 100644 --- a/database/files/pages/start.index.yaml +++ b/database/files/pages/start.index.yaml @@ -2,8 +2,8 @@ key: start.index robots: index,follow image: images/pages/start.index.svg title: - de_CH: 'Innovative Ideen zum Leben erwecken – codebar Solutions AG' - en_CH: 'Bringing Innovative Ideas to Life – codebar Solutions AG' + de_CH: 'Innovative Ideen zum Leben erwecken' + en_CH: 'Bringing Innovative Ideas to Life' description: de_CH: 'Wir hören zu, verstehen dein Problem und begleiten dich von der ersten Idee bis zur Software im täglichen Einsatz. Ein kleines Team aus der Region Basel.' en_CH: 'We listen, understand your problem and stay with you from the first idea to software in daily use. A small team based in the Basel region.' diff --git a/database/files/pages/technologies.index.yaml b/database/files/pages/technologies.index.yaml index e4699bc..9643973 100644 --- a/database/files/pages/technologies.index.yaml +++ b/database/files/pages/technologies.index.yaml @@ -2,8 +2,8 @@ key: technologies.index robots: index,follow image: images/pages/technologies.index.svg title: - de_CH: 'Offene Technologien & Standards – codebar Solutions AG' - en_CH: 'Open Technologies & Standards – codebar Solutions AG' + de_CH: 'Offene Technologien & Standards' + en_CH: 'Open Technologies & Standards' description: de_CH: 'Die offenen Technologien und Standards, auf denen unsere Software basiert – bewährt, transparent und herstellerunabhängig.' en_CH: 'The open technologies and standards we build our software on – proven, transparent and vendor-independent.' diff --git a/resources/views/layouts/_partials/_seo.blade.php b/resources/views/layouts/_partials/_seo.blade.php index c6567de..f5499a7 100644 --- a/resources/views/layouts/_partials/_seo.blade.php +++ b/resources/views/layouts/_partials/_seo.blade.php @@ -41,13 +41,8 @@ }) ) ->sortKeys(); - - $brand = config()->string('app.name'); - $documentTitle = $page->isArticle() || str_contains($page->title, $brand) - ? $page->title - : $page->title.' – '.$brand; @endphp - <title>{{ $documentTitle }} + {{ $page->title }} diff --git a/tests/Feature/Seo/MetaTagsTest.php b/tests/Feature/Seo/MetaTagsTest.php index f43d3c6..a9b53c3 100644 --- a/tests/Feature/Seo/MetaTagsTest.php +++ b/tests/Feature/Seo/MetaTagsTest.php @@ -151,14 +151,14 @@ function titleOf(string $html): string expect(mb_strlen(titleOf($article)))->toBeLessThanOrEqual(60); })->group('seo'); -it('appends the company name to a page title that does not already carry it', function () { +it('renders a page title verbatim without appending the company name', function () { Page::where('key', 'start.index')->update([ 'title' => ['de_CH' => 'Ohne Marke im Titel', 'en_CH' => 'Without the brand'], ]); $html = (string) get(route('de-ch.start.index'))->assertOk()->getContent(); - expect(titleOf($html))->toBe('Ohne Marke im Titel – '.config()->string('app.name')); + expect(titleOf($html))->toBe('Ohne Marke im Titel'); })->group('seo'); it('serves a page image as PNG even when the page stores an SVG', function () { diff --git a/tests/Feature/Seo/StructuredDataTest.php b/tests/Feature/Seo/StructuredDataTest.php index b21bfff..17cc2e0 100644 --- a/tests/Feature/Seo/StructuredDataTest.php +++ b/tests/Feature/Seo/StructuredDataTest.php @@ -117,22 +117,23 @@ function breadcrumbItems(array $breadcrumb): Collection } })->group('seo', 'schema'); -it('carries the full company name in every indexable page title', function () { - // A title without the brand wastes the strongest signal a search result has. +it('keeps the legal company name out of every page title', function () { + // The brand belongs in og:site_name and the Organization node; repeating it in + // every title only eats the characters a search result actually shows. // Collected rather than asserted one by one so a failure names every offender. - $missing = []; + $offenders = []; - Page::all()->each(function (Page $page) use (&$missing): void { + Page::all()->each(function (Page $page) use (&$offenders): void { foreach (['de_CH', 'en_CH'] as $locale) { $title = $page->getTranslation('title', $locale); - if (is_string($title) && $title !== '' && ! str_contains($title, 'codebar Solutions AG')) { - $missing[] = "{$page->key} [{$locale}]: {$title}"; + if (is_string($title) && str_contains($title, 'codebar Solutions AG')) { + $offenders[] = "{$page->key} [{$locale}]: {$title}"; } } }); - expect($missing)->toBe([]); + expect($offenders)->toBe([]); })->group('seo'); it('links WebSite and WebPage back to the organization by id', function () { From b2615fefdf466158c132b559ea673f1db97032aa Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Sat, 1 Aug 2026 12:42:44 +0200 Subject: [PATCH 2/3] wip --- .env.example | 1 + app/Security/Presets/MyCspPreset.php | 4 ++++ config/csp.php | 2 +- database/seeders/NetworkUsersTableSeeder.php | 2 +- database/seeders/data/network_users.csv | 4 ++-- lang/de_CH/components.php | 1 + lang/en_CH/components.php | 1 + phpunit.xml.dist | 1 + resources/views/components/intro.blade.php | 3 ++- 9 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 12542ad..dfe77d0 100644 --- a/.env.example +++ b/.env.example @@ -23,6 +23,7 @@ DB_PASSWORD=postgres FPH_ENABLED=true CSP_ENABLED=true +CSP_NONCE_ENABLED=true SESSION_DRIVER=database SESSION_LIFETIME=120 diff --git a/app/Security/Presets/MyCspPreset.php b/app/Security/Presets/MyCspPreset.php index e3c48f6..d5f1830 100644 --- a/app/Security/Presets/MyCspPreset.php +++ b/app/Security/Presets/MyCspPreset.php @@ -35,6 +35,10 @@ public function configure(Policy $policy): void $policy->add(Directive::SCRIPT_ELEM, $scriptSources); + $policy->addNonce(Directive::SCRIPT); + + $policy->addNonce(Directive::SCRIPT_ELEM); + $styleSources = array_filter([ Keyword::SELF, $cdnHost ?: null, diff --git a/config/csp.php b/config/csp.php index 884e388..1e20dd4 100644 --- a/config/csp.php +++ b/config/csp.php @@ -63,5 +63,5 @@ * and cannot add inline nonces. * Note that this will make your CSP policy less secure. */ - 'nonce_enabled' => env('CSP_NONCE_ENABLED', false), + 'nonce_enabled' => env('CSP_NONCE_ENABLED', true), ]; diff --git a/database/seeders/NetworkUsersTableSeeder.php b/database/seeders/NetworkUsersTableSeeder.php index 9f088a3..55f6ed3 100644 --- a/database/seeders/NetworkUsersTableSeeder.php +++ b/database/seeders/NetworkUsersTableSeeder.php @@ -10,7 +10,7 @@ /** * Contact channels are real where known, otherwise placeholder (*@example.com). - * All rows seed as unpublished — a contact only appears on the network page once + * Rows seed unpublished by default — a contact only appears on the network page once * explicitly published. * * Sourced from network_users.csv — the current data export — not hand-authored, diff --git a/database/seeders/data/network_users.csv b/database/seeders/data/network_users.csv index 90317b3..2e48026 100644 --- a/database/seeders/data/network_users.csv +++ b/database/seeders/data/network_users.csv @@ -1,6 +1,6 @@ id;network_key;name;role;avatar_disk;avatar_path;avatar_url;email;public_email;linkedin;phone;published;sort;created_at;updated_at -1;wieland-business-solutions;Dario Wieland;;;;;dario.wieland@business-solutions.gmbh;dario.wieland@business-solutions.gmbh;https://www.linkedin.com/in/dario-wieland/;+41 81 560 21 20;false;10;2026-07-23 08:16:38;2026-07-25 10:02:48 -2;pst;Sarah Fässler;;;;;sarah.faessler@pstgmbh.ch;sarah.faessler@pstgmbh.ch;;;false;10;2026-07-23 08:16:38;2026-07-23 08:16:38 +1;wieland-business-solutions;Dario Wieland;;;;;dario.wieland@business-solutions.gmbh;dario.wieland@business-solutions.gmbh;https://www.linkedin.com/in/dario-wieland/;+41 81 560 21 20;true;10;2026-07-23 08:16:38;2026-07-25 10:02:48 +2;pst;Sarah Fässler;;;;;sarah.faessler@pstgmbh.ch;sarah.faessler@pstgmbh.ch;https://www.linkedin.com/in/sarah-f%C3%A4ss%C4%BAer-30492b152/;;true;10;2026-07-23 08:16:38;2026-07-23 08:16:38 3;docuware;Vincenzo Carbone;DocuWare Schweiz;;;/images/placeholders/avatar-sample.svg;vincenzo.carbone@docuware.com;vincenzo.carbone@docuware.com;;;false;10;2026-07-23 08:16:38;2026-07-23 08:16:38 4;odoo;Domenik Friedrich;;;;;domf@odoo.com;domf@odoo.com;;;false;10;2026-07-23 08:16:38;2026-07-23 08:16:38 5;baselhack;BaselHack;;;;;info@baselhack.ch;info@baselhack.ch;;;false;10;2026-07-23 08:16:38;2026-07-23 08:16:38 diff --git a/lang/de_CH/components.php b/lang/de_CH/components.php index 1c20728..75e1dd6 100644 --- a/lang/de_CH/components.php +++ b/lang/de_CH/components.php @@ -7,6 +7,7 @@ 'title' => 'Schön, bist du da', 'window' => 'Wir sind codebar', 'legend' => 'Was möchtest du über uns wissen?', + 'overview' => 'Übersicht', 'shortcuts' => 'Abschnitt wechseln: Zifferntasten 1 bis 3 oder die Pfeiltasten links und rechts.', 'prev_section' => 'Vorheriger Abschnitt', 'next_section' => 'Nächster Abschnitt', diff --git a/lang/en_CH/components.php b/lang/en_CH/components.php index 496fb3e..3fc3bfc 100644 --- a/lang/en_CH/components.php +++ b/lang/en_CH/components.php @@ -7,6 +7,7 @@ 'title' => 'Good to have you here', 'window' => 'We are codebar', 'legend' => 'What would you like to know about us?', + 'overview' => 'Overview', 'shortcuts' => 'Switch section: number keys 1 to 3, or the left and right arrow keys.', 'prev_section' => 'Previous section', 'next_section' => 'Next section', diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 15ee930..da52616 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -42,6 +42,7 @@ + diff --git a/resources/views/components/intro.blade.php b/resources/views/components/intro.blade.php index 9112995..4c985ce 100644 --- a/resources/views/components/intro.blade.php +++ b/resources/views/components/intro.blade.php @@ -26,7 +26,8 @@ - +
@foreach(array_slice($sections, 1) as $section) From 995a5a935dbb362e208fae01141f20e7061d144a Mon Sep 17 00:00:00 2001 From: "Sebastian BURGIN-FIX (ext)" Date: Sat, 1 Aug 2026 12:46:29 +0200 Subject: [PATCH 3/3] wip --- app/Security/Replacers/CspHeaderReplacer.php | 31 +++++++ config/responsecache.php | 2 + ...025_04_04_175530_create_products_table.php | 2 + ...025_04_04_175541_create_services_table.php | 2 + ...025_06_23_130054_create_contacts_table.php | 2 + ...06_24_063716_create_technologies_table.php | 2 + ...026_07_22_100000_create_networks_table.php | 4 +- ...7_22_100010_create_network_users_table.php | 2 +- .../2026_07_28_150010_create_news_table.php | 9 +- .../2026_08_01_090000_add_listing_indexes.php | 82 ------------------- tests/Feature/Seeders/NetworkSeedersTest.php | 10 ++- 11 files changed, 55 insertions(+), 93 deletions(-) create mode 100644 app/Security/Replacers/CspHeaderReplacer.php delete mode 100644 database/migrations/2026_08_01_090000_add_listing_indexes.php diff --git a/app/Security/Replacers/CspHeaderReplacer.php b/app/Security/Replacers/CspHeaderReplacer.php new file mode 100644 index 0000000..0c9e215 --- /dev/null +++ b/app/Security/Replacers/CspHeaderReplacer.php @@ -0,0 +1,31 @@ + */ + protected array $headers = [ + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + ]; + + public function prepareResponseToCache(Response $response): void + { + foreach ($this->headers as $header) { + $response->headers->remove($header); + } + } + + public function replaceInCachedResponse(Response $response): void + { + foreach ($this->headers as $header) { + $response->headers->remove($header); + } + } +} diff --git a/config/responsecache.php b/config/responsecache.php index 91dc412..f308935 100644 --- a/config/responsecache.php +++ b/config/responsecache.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Security\Replacers\CspHeaderReplacer; use Spatie\ResponseCache\CacheProfiles\CacheAllSuccessfulGetRequests; use Spatie\ResponseCache\Hasher\DefaultHasher; use Spatie\ResponseCache\Replacers\CsrfTokenReplacer; @@ -123,5 +124,6 @@ */ 'replacers' => [ CsrfTokenReplacer::class, + CspHeaderReplacer::class, ], ]; diff --git a/database/migrations/2025_04_04_175530_create_products_table.php b/database/migrations/2025_04_04_175530_create_products_table.php index 10d7af9..38d4aee 100644 --- a/database/migrations/2025_04_04_175530_create_products_table.php +++ b/database/migrations/2025_04_04_175530_create_products_table.php @@ -34,6 +34,8 @@ public function up(): void $table->string('url')->nullable(); $table->json('tags')->nullable(); $table->timestamps(); + + $table->index(['published', 'order']); }); } diff --git a/database/migrations/2025_04_04_175541_create_services_table.php b/database/migrations/2025_04_04_175541_create_services_table.php index e5108bb..99f0fca 100644 --- a/database/migrations/2025_04_04_175541_create_services_table.php +++ b/database/migrations/2025_04_04_175541_create_services_table.php @@ -26,6 +26,8 @@ public function up(): void $table->string('url')->nullable(); $table->json('tags')->nullable(); $table->timestamps(); + + $table->index(['published', 'order']); }); } diff --git a/database/migrations/2025_06_23_130054_create_contacts_table.php b/database/migrations/2025_06_23_130054_create_contacts_table.php index be75c85..71d4a43 100644 --- a/database/migrations/2025_06_23_130054_create_contacts_table.php +++ b/database/migrations/2025_06_23_130054_create_contacts_table.php @@ -23,6 +23,8 @@ public function up(): void $table->string('image'); $table->json('icons')->nullable(); $table->timestamps(); + + $table->index(['published', 'sort']); }); } diff --git a/database/migrations/2025_06_24_063716_create_technologies_table.php b/database/migrations/2025_06_24_063716_create_technologies_table.php index 22975ff..0882c34 100644 --- a/database/migrations/2025_06_24_063716_create_technologies_table.php +++ b/database/migrations/2025_06_24_063716_create_technologies_table.php @@ -26,6 +26,8 @@ public function up(): void $table->json('tags')->nullable(); $table->string('link')->nullable(); $table->timestamps(); + + $table->index(['published', 'order']); }); } diff --git a/database/migrations/2026_07_22_100000_create_networks_table.php b/database/migrations/2026_07_22_100000_create_networks_table.php index 6df0018..5090a58 100644 --- a/database/migrations/2026_07_22_100000_create_networks_table.php +++ b/database/migrations/2026_07_22_100000_create_networks_table.php @@ -27,10 +27,12 @@ public function up(): void $table->string('website')->nullable(); $table->unsignedSmallInteger('since_year')->nullable(); $table->unsignedSmallInteger('until_year')->nullable(); - $table->string('page_slug')->nullable(); + $table->string('page_slug')->nullable()->unique(); $table->boolean('published')->default(true); $table->unsignedInteger('sort')->default(0); $table->timestamps(); + + $table->index(['published', 'status', 'sort']); }); } diff --git a/database/migrations/2026_07_22_100010_create_network_users_table.php b/database/migrations/2026_07_22_100010_create_network_users_table.php index f90be9b..11cb7e6 100644 --- a/database/migrations/2026_07_22_100010_create_network_users_table.php +++ b/database/migrations/2026_07_22_100010_create_network_users_table.php @@ -21,7 +21,7 @@ public function up(): void $table->string('avatar_disk')->nullable(); $table->string('avatar_path')->nullable(); $table->string('avatar_url')->nullable(); - $table->string('email')->nullable()->index(); + $table->string('email')->nullable()->unique(); $table->string('public_email')->nullable(); $table->string('linkedin')->nullable(); $table->string('phone')->nullable(); diff --git a/database/migrations/2026_07_28_150010_create_news_table.php b/database/migrations/2026_07_28_150010_create_news_table.php index c207df0..abccb7f 100644 --- a/database/migrations/2026_07_28_150010_create_news_table.php +++ b/database/migrations/2026_07_28_150010_create_news_table.php @@ -13,15 +13,12 @@ public function up(): void { Schema::create('news', function (Blueprint $table) { $table->id(); - // Cross-language anchor: the de_CH and en_CH markdown files of one article share it. $table->string('key')->unique(); $table->json('slug'); $table->json('title'); $table->json('teaser'); $table->json('content')->nullable(); $table->string('hero_image')->nullable(); - // The square the index shows next to a list row. Its own file rather than a crop - // of the hero: the hero carries the title, and a square crop cuts the words. $table->string('thumb_image')->nullable(); $table->json('hero_caption')->nullable(); $table->json('hero_alt')->nullable(); @@ -36,12 +33,10 @@ public function up(): void $table->unsignedSmallInteger('reading_minutes')->nullable(); $table->json('tags')->nullable(); $table->timestamps(); + + $table->index(['published', 'published_at']); }); - // A JSON column cannot carry a plain unique constraint — index the extracted - // per-locale value instead, so each language keeps its own unique slug. - // PostgreSQL only: `->>` here takes a bare key, which is not valid MySQL. - // The whole application targets pgsql (see config/database.php). foreach (['de_CH', 'en_CH'] as $locale) { $index = 'news_slug_'.strtolower(str_replace('_', '', $locale)).'_unique'; DB::statement("CREATE UNIQUE INDEX {$index} ON news (( slug->>'{$locale}' ))"); diff --git a/database/migrations/2026_08_01_090000_add_listing_indexes.php b/database/migrations/2026_08_01_090000_add_listing_indexes.php deleted file mode 100644 index 874c012..0000000 --- a/database/migrations/2026_08_01_090000_add_listing_indexes.php +++ /dev/null @@ -1,82 +0,0 @@ -first(), so a duplicate would make - * the database pick the partner page arbitrarily. - */ -return new class extends Migration -{ - public function up(): void - { - Schema::table('news', function (Blueprint $table): void { - $table->index(['published', 'published_at']); - }); - - Schema::table('services', function (Blueprint $table): void { - $table->index(['published', 'order']); - }); - - Schema::table('products', function (Blueprint $table): void { - $table->index(['published', 'order']); - }); - - Schema::table('technologies', function (Blueprint $table): void { - $table->index(['published', 'order']); - }); - - Schema::table('contacts', function (Blueprint $table): void { - $table->index(['published', 'sort']); - }); - - Schema::table('networks', function (Blueprint $table): void { - $table->index(['published', 'status', 'sort']); - $table->unique('page_slug'); - }); - - Schema::table('network_users', function (Blueprint $table): void { - $table->unique('email'); - }); - } - - public function down(): void - { - Schema::table('news', function (Blueprint $table): void { - $table->dropIndex(['published', 'published_at']); - }); - - Schema::table('services', function (Blueprint $table): void { - $table->dropIndex(['published', 'order']); - }); - - Schema::table('products', function (Blueprint $table): void { - $table->dropIndex(['published', 'order']); - }); - - Schema::table('technologies', function (Blueprint $table): void { - $table->dropIndex(['published', 'order']); - }); - - Schema::table('contacts', function (Blueprint $table): void { - $table->dropIndex(['published', 'sort']); - }); - - Schema::table('networks', function (Blueprint $table): void { - $table->dropIndex(['published', 'status', 'sort']); - $table->dropUnique(['page_slug']); - }); - - Schema::table('network_users', function (Blueprint $table): void { - $table->dropUnique(['email']); - }); - } -}; diff --git a/tests/Feature/Seeders/NetworkSeedersTest.php b/tests/Feature/Seeders/NetworkSeedersTest.php index bc162c9..8b65930 100644 --- a/tests/Feature/Seeders/NetworkSeedersTest.php +++ b/tests/Feature/Seeders/NetworkSeedersTest.php @@ -51,7 +51,7 @@ ->and($baselhack->page_slug)->toBeNull(); })->group('network', 'seeders'); -it('seeds unpublished contact persons with real channels where known', function () { +it('seeds contact persons with real channels where known, published only where flagged', function () { seed(NetworkUsersTableSeeder::class); $vincenzo = NetworkUser::where('network_key', 'docuware')->firstOrFail(); @@ -62,13 +62,19 @@ $baselhack = NetworkUser::where('network_key', 'baselhack')->firstOrFail(); expect(NetworkUser::count())->toBe(6) - ->and(NetworkUser::where('published', true)->count())->toBe(0) + ->and($dario->published)->toBeTrue() + ->and($sarah->published)->toBeTrue() + ->and($vincenzo->published)->toBeFalse() + ->and($domenik->published)->toBeFalse() + ->and($patrick->published)->toBeFalse() + ->and($baselhack->published)->toBeFalse() ->and($vincenzo->name)->toBe('Vincenzo Carbone') ->and($vincenzo->role)->toBe('DocuWare Schweiz') ->and($vincenzo->email)->toBe('vincenzo.carbone@docuware.com') ->and($vincenzo->avatar_url)->toBe('/images/placeholders/avatar-sample.svg') ->and($dario->email)->toBe('dario.wieland@business-solutions.gmbh') ->and($sarah->email)->toBe('sarah.faessler@pstgmbh.ch') + ->and($sarah->linkedin)->not->toBeNull() ->and($domenik->name)->toBe('Domenik Friedrich') ->and($domenik->email)->toBe('domf@odoo.com') ->and($patrick->linkedin)->not->toBeNull()