reorder folders for easy 1:1 upload to boogie server

This commit is contained in:
Robert Wartenberg committed 2026-08-31 11:40:54 +02:00
1 parent de5895f558
commit 6c16e16354
20 files changed
+170 -51

No files matched your search

+1
View File
@@ -5,6 +5,7 @@
/public/share /public/share
/public/system /public/system
/public/vendor /public/vendor
/public/booking-config.php
/system /system
/bin/console /bin/console
/node_modules/ /node_modules/
+45 -22
View File
@@ -58,7 +58,11 @@ http://localhost:8080/perspektivwerk/
Die genaue Contao-, PHP- und Template-Version der öffentlichen Seite ist nicht verlässlich aus dem ausgelieferten HTML ableitbar. Das Setup bildet daher die wahrscheinlichste Komponentenfamilie nach; es ist kein Dump der Produktivinstallation. IONOS/Hosting, Matomo-Konfiguration, Inhalte, Templates und Erweiterungen müssen bei Bedarf separat ergänzt werden. Die genaue Contao-, PHP- und Template-Version der öffentlichen Seite ist nicht verlässlich aus dem ausgelieferten HTML ableitbar. Das Setup bildet daher die wahrscheinlichste Komponentenfamilie nach; es ist kein Dump der Produktivinstallation. IONOS/Hosting, Matomo-Konfiguration, Inhalte, Templates und Erweiterungen müssen bei Bedarf separat ergänzt werden.
Die Terminlogik liegt in `public/booking/booking.js` als browsernatives ES-Modul. Für die statische Perspektivwerk-Vorschau liegen identische Kopien unter `public/perspektivwerk/js/`. Der vollständige, statisch auslieferbare Perspektivwerk-DocumentRoot liegt in
`public/perspektivwerk/`. Er enthält die Buchungs-API unter `booking/` und die
Verwaltung unter `booking-admin/`. Die Vorschau verwendet relative API-URLs und
funktioniert dadurch lokal unter `/perspektivwerk/` sowie auf dem Server direkt
im DocumentRoot.
Für einen einfachen Upload der vollständigen statischen Mockup-Seite auf den Für einen einfachen Upload der vollständigen statischen Mockup-Seite auf den
Testserver steht unter Windows dieses Skript bereit: Testserver steht unter Windows dieses Skript bereit:
@@ -67,17 +71,27 @@ Testserver steht unter Windows dieses Skript bereit:
scripts\Deploy-Perspektivwerk.bat scripts\Deploy-Perspektivwerk.bat
``` ```
Es überträgt `public\perspektivwerk\*` rekursiv nach Es überträgt den vollständigen Ordner `public\perspektivwerk` und dessen
`boogie:/html/perspektivwerk/`. Voraussetzung ist ein konfigurierter benachbarte Konfigurationsdatei `public\booking-config.php` nach
OpenSSH-Zugang mit dem Host-Alias `boogie`. `boogie:html/perspektivwerk/`. Damit entsteht der Server-DocumentRoot unter
`html/perspektivwerk/perspektivwerk/`; die Konfigurationsdatei liegt eine Ebene
darüber und damit außerhalb des DocumentRoots. Anschließend setzt es per SSH
lesbare Dateien und durchsuchbare Verzeichnisse.
Vor dem ersten Upload `public\booking-config.php.example` nach
`public\booking-config.php` kopieren und dort die Produktiv-`database_url`
eintragen. Die echte Konfigurationsdatei ist von Git ausgeschlossen; sie wird
bei fehlender oder leerer PHP-Umgebungsvariable `DATABASE_URL` verwendet. Die
mit übertragene `.htaccess` verweigert den HTTP-Zugriff auf diese Datei.
Voraussetzung ist ein konfigurierter OpenSSH-Zugang mit dem Host-Alias
`boogie`.
## Datenbankgestützte Buchungsdemo ## Datenbankgestützte Buchungsdemo
Die Buchungsdemo lädt ihre Termine aus MariaDB und speichert eine Anmeldung Die Buchungsdemo lädt ihre Termine aus MariaDB und speichert eine Anmeldung
über `public/booking/api.php`. Die API wird unabhängig von Contao direkt durch über `public/perspektivwerk/booking/api.php`. Die API wird unabhängig von
Apache ausgeliefert, verwendet aber dieselbe `DATABASE_URL` wie Contao. Dadurch Contao direkt durch Apache ausgeliefert. In der statischen Vorschau liegt sie
funktioniert sie aus der Vorschau `/perspektivwerk/` und aus dem Contao- relativ unter `/perspektivwerk/booking/api.php`.
HTML-Element gleichermaßen.
Vor dem ersten Aufruf müssen die externen Migrationen eingespielt werden: Vor dem ersten Aufruf müssen die externen Migrationen eingespielt werden:
@@ -156,7 +170,7 @@ nach 30 Minuten ab.
## Eigenständiges PHP-Backend ## Eigenständiges PHP-Backend
Die Testverwaltung läuft unabhängig von Contao unter Die Testverwaltung läuft unabhängig von Contao unter
<http://localhost:8080/booking-admin/>. Sie bietet für PW und PS: <http://localhost:8080/perspektivwerk/booking-admin/>. Sie bietet für PW und PS:
- Termin anlegen, bearbeiten sowie aktivieren/deaktivieren, - Termin anlegen, bearbeiten sowie aktivieren/deaktivieren,
- Übersicht und Entfernen von Teilnahmen, - Übersicht und Entfernen von Teilnahmen,
@@ -215,34 +229,43 @@ Contao maskiert nicht freigegebene HTML-Tags auch im Inhaltselement **HTML**. Vo
Danach den Contao-Cache über **Systemwartung** leeren und das Inhaltselement erneut speichern. Keine globale Freigabe mit `*` verwenden. Wer ein HTML-Inhaltselement bearbeiten darf, kann mit dem freigegebenen `script`-Tag JavaScript auf der Website ausführen; diese Bearbeitungsrechte müssen daher auf vertrauenswürdige Backend-Nutzer beschränkt bleiben. Danach den Contao-Cache über **Systemwartung** leeren und das Inhaltselement erneut speichern. Keine globale Freigabe mit `*` verwenden. Wer ein HTML-Inhaltselement bearbeiten darf, kann mit dem freigegebenen `script`-Tag JavaScript auf der Website ausführen; diese Bearbeitungsrechte müssen daher auf vertrauenswürdige Backend-Nutzer beschränkt bleiben.
Die Termin- und Kapazitätsdaten kommen aus `/booking/api.php`; Datenbankzugänge Die Termin- und Kapazitätsdaten kommen in der statischen Vorschau aus
oder SQL gehören niemals in das HTML oder das Browser-JavaScript. `/perspektivwerk/booking/api.php`; Datenbankzugänge oder SQL gehören niemals in
das HTML oder das Browser-JavaScript.
`public/perspektivwerk/js/booking.js` und die zugehörige CSS-Datei sind die für `public/perspektivwerk/js/booking.js` und die zugehörige CSS-Datei sind die für
den statischen Upload bestimmte Kopie. Ein Test stellt sicher, dass beide den statischen Upload bestimmte Kopie. Ein Test stellt sicher, dass beide
Asset-Paare gleich bleiben. Nach Änderungen können sie lokal synchronisiert Asset-Paare gleich bleiben. Nach Änderungen können sie lokal synchronisiert
werden mit: werden mit:
```powershell ```powershell
Copy-Item public\booking\booking.js public\perspektivwerk\js\booking.js -Force Copy-Item public\perspektivwerk\booking\booking.js public\perspektivwerk\js\booking.js -Force
Copy-Item public\booking\booking.css public\perspektivwerk\js\booking.css -Force Copy-Item public\perspektivwerk\booking\booking.css public\perspektivwerk\js\booking.css -Force
``` ```
Der vorhandene SCP-Upload überträgt weiterhin nur die statische Der vorhandene SCP-Upload überträgt den vollständigen statischen
Perspektivwerk-Seite. Für einen externen Einsatz der Buchungsdemo müssen Perspektivwerk-DocumentRoot einschließlich Buchungsdemo. Vor dem externen
zusätzlich `/booking/api.php`, die Migrationen und die sichere Einsatz müssen die Migrationen eingespielt und `booking-config.php` mit einer
`DATABASE_URL`-Konfiguration auf dem Zielserver bereitgestellt werden. sicheren Datenbank-URL befüllt sein.
## Statische Perspektivwerk-Seite übertragen ## Statische Perspektivwerk-Seite übertragen
Das Skript [scripts/Deploy-Perspektivwerk.ps1](scripts/Deploy-Perspektivwerk.ps1) überträgt ausschließlich den Inhalt von `public/perspektivwerk/` per SCP und korrigiert anschließend die Lesbarkeit auf dem Server per SSH. Dieser Ordner enthält auch `js/booking.js` und `js/booking.css`. Es lädt weder Contao noch die Datenbank oder andere Projektdateien hoch. Das Skript [scripts/Deploy-Perspektivwerk.bat](scripts/Deploy-Perspektivwerk.bat) überträgt den Ordner `public/perspektivwerk/` als vollständigen DocumentRoot per SCP und korrigiert anschließend die Lesbarkeit auf dem Server per SSH. Es lädt weder Contao noch Datenbank-Migrationen hoch.
```powershell ```bat
.\scripts\Deploy-Perspektivwerk.ps1 scripts\Deploy-Perspektivwerk.bat
``` ```
Der Hostname `boogie` wird aus der lokalen SSH-Konfiguration verwendet. Der vollständige Inhalt wird per SCP-Aufruf nach `/home/boogie/html/perspektivwerk/` übertragen. Anschließend führt ein separater SSH-Aufruf `chmod -R a+rX /home/boogie/html/perspektivwerk` aus. Die Passphrase des SSH-Schlüssels wird daher in der Regel zweimal abgefragt. Der Hostname `boogie` wird aus der lokalen SSH-Konfiguration verwendet. Der
Ordner landet unter `/home/boogie/html/perspektivwerk/perspektivwerk/`; die
Konfiguration liegt daneben unter `/home/boogie/html/perspektivwerk/booking-config.php`.
Anschließend führt ein separater SSH-Aufruf `chmod -R a+rX
/home/boogie/html/perspektivwerk` aus. Die Passphrase des SSH-Schlüssels wird
daher in der Regel zweimal abgefragt.
Vor dem Upload gibt das Skript den vollständigen Zielpfad aus. Damit lässt sich direkt prüfen, ob `booking.js` unter `/home/boogie/html/perspektivwerk/js/booking.js` landet. Das große `X` in `a+rX` setzt das Ausführungsrecht nur auf Verzeichnissen und auf bereits ausführbaren Dateien; normale HTML-, CSS- und JavaScript-Dateien erhalten kein unnötiges Ausführungsbit. Vor dem Upload muss die ignorierte `booking-config.php` befüllt sein. Das große
`X` in `a+rX` setzt das Ausführungsrecht nur auf Verzeichnissen und auf bereits
ausführbaren Dateien; normale HTML-, CSS- und JavaScript-Dateien erhalten kein
unnötiges Ausführungsbit.
## Versionierter Contao-Seitenzustand ## Versionierter Contao-Seitenzustand
+1 -1
View File
@@ -9,7 +9,7 @@ services:
APP_ENV: dev APP_ENV: dev
CONTAO_ENVIRONMENT: dev CONTAO_ENVIRONMENT: dev
DATABASE_URL: mysql://perspektivwerk:perspektivwerk@db:3306/perspektivwerk?serverVersion=mariadb-10.11.0 DATABASE_URL: mysql://perspektivwerk:perspektivwerk@db:3306/perspektivwerk?serverVersion=mariadb-10.11.0
BOOKING_BASE_URL: http://localhost:8080 BOOKING_BASE_URL: http://localhost:8080/perspektivwerk
BOOKING_MAIL_TRANSPORT: database BOOKING_MAIL_TRANSPORT: database
command: command:
- sh - sh
+13 -3
View File
@@ -1,3 +1,13 @@
<Files "booking-config.php">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</Files>
<IfModule mod_rewrite.c> <IfModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
@@ -44,9 +54,9 @@
# directory request to Contao's front controller. # directory request to Contao's front controller.
RewriteRule ^perspektivwerk/?$ perspektivwerk/index.html [L] RewriteRule ^perspektivwerk/?$ perspektivwerk/index.html [L]
# The independent booking administration is a plain PHP application, not a # The standalone booking administration lives below the static mockup and
# Contao route. Serve its directory URL directly as well. # must not be handled by Contao's front controller.
RewriteRule ^booking-admin/?$ booking-admin/index.php [L] RewriteRule ^perspektivwerk/booking-admin/?$ perspektivwerk/booking-admin/index.php [L]
# If the requested filename exists, simply serve it. # If the requested filename exists, simply serve it.
# We only want to let Apache serve files and not directories. # We only want to let Apache serve files and not directories.
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
// Copy this file to booking-config.php, then insert the production database URL.
// booking-config.php is intentionally excluded from Git and denied by public/.htaccess.
return [
'database_url' => 'mysql://USER:PASSWORD@database-host:3306/booking_database',
];
@@ -2,9 +2,25 @@
declare(strict_types=1); declare(strict_types=1);
function bookingDatabaseUrl(?string $environmentUrl = null, ?string $configurationFile = null): string
{
$url = $environmentUrl ?? getenv('DATABASE_URL');
if (!is_string($url) || trim($url) === '') {
$configurationFile ??= dirname(__DIR__, 2).'/booking-config.php';
if (is_file($configurationFile)) {
$configuration = require $configurationFile;
$url = is_array($configuration) ? ($configuration['database_url'] ?? '') : '';
}
}
if (!is_string($url) || trim($url) === '') {
throw new RuntimeException('DATABASE_URL is not configured and the booking configuration file does not contain a database URL.');
}
return trim($url);
}
function bookingDatabase(): PDO function bookingDatabase(): PDO
{ {
$url = getenv('DATABASE_URL'); $url = bookingDatabaseUrl();
$parts = is_string($url) ? parse_url($url) : false; $parts = is_string($url) ? parse_url($url) : false;
if (!is_array($parts) || ($parts['scheme'] ?? '') !== 'mysql' || !isset($parts['host'], $parts['path'], $parts['user'])) { if (!is_array($parts) || ($parts['scheme'] ?? '') !== 'mysql' || !isset($parts['host'], $parts['path'], $parts['user'])) {
throw new RuntimeException('DATABASE_URL is not configured correctly.'); throw new RuntimeException('DATABASE_URL is not configured correctly.');
@@ -153,7 +153,7 @@ function openBookingDialog(container, event, state) {
function renderComponent(container) { function renderComponent(container) {
const project = container.dataset.project === 'produktionsschule' ? 'produktionsschule' : 'perspektivwerk'; const project = container.dataset.project === 'produktionsschule' ? 'produktionsschule' : 'perspektivwerk';
const now = new Date(); const now = new Date();
const state = { apiUrl: container.dataset.apiUrl || '/booking/api.php', csrfToken: '', events: [], currentMonth: new Date(now.getFullYear(), now.getMonth(), 1), selectedDate: '' }; const state = { apiUrl: container.dataset.apiUrl || 'booking/api.php', csrfToken: '', events: [], currentMonth: new Date(now.getFullYear(), now.getMonth(), 1), selectedDate: '' };
const fetchEvents = async () => { const fetchEvents = async () => {
const from = formatDateKey(state.currentMonth); const from = formatDateKey(state.currentMonth);
const to = formatDateKey(new Date(state.currentMonth.getFullYear(), state.currentMonth.getMonth() + 1, 1)); const to = formatDateKey(new Date(state.currentMonth.getFullYear(), state.currentMonth.getMonth() + 1, 1));
@@ -13,7 +13,7 @@ function page(string $title, string $message, bool $success = false): never
$safeTitle = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $safeTitle = htmlspecialchars($title, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$safeMessage = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); $safeMessage = htmlspecialchars($message, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$class = $success ? 'success' : 'error'; $class = $success ? 'success' : 'error';
echo "<!doctype html><html lang=\"de\"><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>$safeTitle</title><style>body{font:18px/1.5 Arial,sans-serif;margin:0;background:#f3f6f3;color:#202020}.box{max-width:42rem;margin:12vh auto;padding:2rem;background:#fff;border-top:6px solid #08752a}.success{color:#08752a}.error{color:#8a1515}a{color:#08752a}</style><main class=\"box\"><h1 class=\"$class\">$safeTitle</h1><p>$safeMessage</p><p><a href=\"/perspektivwerk/\">Zurück zur Terminbuchung</a></p></main></html>"; echo "<!doctype html><html lang=\"de\"><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>$safeTitle</title><style>body{font:18px/1.5 Arial,sans-serif;margin:0;background:#f3f6f3;color:#202020}.box{max-width:42rem;margin:12vh auto;padding:2rem;background:#fff;border-top:6px solid #08752a}.success{color:#08752a}.error{color:#8a1515}a{color:#08752a}</style><main class=\"box\"><h1 class=\"$class\">$safeTitle</h1><p>$safeMessage</p><p><a href=\"../\">Zurück zur Terminbuchung</a></p></main></html>";
exit; exit;
} }
@@ -21,10 +21,10 @@
<section class="team-section"><div class="team-copy"><img src="media/wir-sind-fuer-sie-da.svg" alt="Wir sind für Sie da"><p>Unser Team, bestehend aus sozialpädagogischen, pädagogischen und psychologischen Mitarbeiterinnen und Mitarbeitern, <strong>unterstützt Ihren Prozess der beruflichen Orientierung.</strong> Wir arbeiten eng mit den Bearbeiterinnen und Beratern der Agentur für Arbeit, dem Jobcenter und weiteren Beratungsstellen zusammen.</p><p>Die Teilnahme am <strong>PerspektivWerk ist kostenlos.</strong> Sie bestimmen, an welchen Angeboten Sie teilnehmen.</p></div><div class="team-image"><img src="media/perspektivwerk-psych.jpg" alt="Beratungsgespräch im PerspektivWerk"></div></section> <section class="team-section"><div class="team-copy"><img src="media/wir-sind-fuer-sie-da.svg" alt="Wir sind für Sie da"><p>Unser Team, bestehend aus sozialpädagogischen, pädagogischen und psychologischen Mitarbeiterinnen und Mitarbeitern, <strong>unterstützt Ihren Prozess der beruflichen Orientierung.</strong> Wir arbeiten eng mit den Bearbeiterinnen und Beratern der Agentur für Arbeit, dem Jobcenter und weiteren Beratungsstellen zusammen.</p><p>Die Teilnahme am <strong>PerspektivWerk ist kostenlos.</strong> Sie bestimmen, an welchen Angeboten Sie teilnehmen.</p></div><div class="team-image"><img src="media/perspektivwerk-psych.jpg" alt="Beratungsgespräch im PerspektivWerk"></div></section>
<section class="content page-width" id="projekte"><div class="content-grid"><article><h2>Wer kann zu uns kommen?</h2><p>Menschen zwischen 15 und 65 Jahren, die sich beruflich neu orientieren möchten, beim Einstieg Unterstützung suchen oder ihre Möglichkeiten gemeinsam klären wollen.</p></article><article><h2>1. Orientierung &amp; Ersteinschätzung</h2><p>Sozialpädagogische und psychologische Module helfen dabei, persönliche Situation, Motivation, Belastbarkeit und Stärken einzuschätzen.</p></article><article><h2>2. Praktische Erprobung &amp; Belastungstest</h2><p>Arbeitsproben und praktische Erprobungen machen fachliche, soziale und arbeitsbezogene Kompetenzen sichtbar.</p></article><article><h2>3. Auswertung &amp; Perspektiventwicklung</h2><p>Wir führen die Ergebnisse zusammen und entwickeln konkrete, realistische nächste Schritte.</p></article></div></section> <section class="content page-width" id="projekte"><div class="content-grid"><article><h2>Wer kann zu uns kommen?</h2><p>Menschen zwischen 15 und 65 Jahren, die sich beruflich neu orientieren möchten, beim Einstieg Unterstützung suchen oder ihre Möglichkeiten gemeinsam klären wollen.</p></article><article><h2>1. Orientierung &amp; Ersteinschätzung</h2><p>Sozialpädagogische und psychologische Module helfen dabei, persönliche Situation, Motivation, Belastbarkeit und Stärken einzuschätzen.</p></article><article><h2>2. Praktische Erprobung &amp; Belastungstest</h2><p>Arbeitsproben und praktische Erprobungen machen fachliche, soziale und arbeitsbezogene Kompetenzen sichtbar.</p></article><article><h2>3. Auswertung &amp; Perspektiventwicklung</h2><p>Wir führen die Ergebnisse zusammen und entwickeln konkrete, realistische nächste Schritte.</p></article></div></section>
<section class="page-width" aria-label="Terminbuchung PerspektivWerk"> <section class="page-width" aria-label="Terminbuchung PerspektivWerk">
<div data-sufw-booking data-project="perspektivwerk" data-api-url="/booking/api.php"></div> <div data-sufw-booking data-project="perspektivwerk" data-api-url="booking/api.php"></div>
</section> </section>
<section class="page-width" aria-label="Terminbuchung Produktionsschule"> <section class="page-width" aria-label="Terminbuchung Produktionsschule">
<div data-sufw-booking data-project="produktionsschule" data-api-url="/booking/api.php"></div> <div data-sufw-booking data-project="produktionsschule" data-api-url="booking/api.php"></div>
</section> </section>
<section class="contact page-width" id="kontakt"><h2>Sie haben Fragen zu dieser Maßnahme?</h2><p>Haben Sie Interesse an unserem Angebot, melden Sie sich bei uns. Wir beraten Sie gern vorab zu den weiteren Schritten.</p><p><strong>Kontakt:</strong> <a href="tel:+4917660328929">(0176) 60328929</a> · <a href="mailto:perspektivwerk@sufw.de">perspektivwerk@sufw.de</a> · Dresdner Straße 16, 01326 Dresden</p></section> <section class="contact page-width" id="kontakt"><h2>Sie haben Fragen zu dieser Maßnahme?</h2><p>Haben Sie Interesse an unserem Angebot, melden Sie sich bei uns. Wir beraten Sie gern vorab zu den weiteren Schritten.</p><p><strong>Kontakt:</strong> <a href="tel:+4917660328929">(0176) 60328929</a> · <a href="mailto:perspektivwerk@sufw.de">perspektivwerk@sufw.de</a> · Dresdner Straße 16, 01326 Dresden</p></section>
</main> </main>
@@ -153,7 +153,7 @@ function openBookingDialog(container, event, state) {
function renderComponent(container) { function renderComponent(container) {
const project = container.dataset.project === 'produktionsschule' ? 'produktionsschule' : 'perspektivwerk'; const project = container.dataset.project === 'produktionsschule' ? 'produktionsschule' : 'perspektivwerk';
const now = new Date(); const now = new Date();
const state = { apiUrl: container.dataset.apiUrl || '/booking/api.php', csrfToken: '', events: [], currentMonth: new Date(now.getFullYear(), now.getMonth(), 1), selectedDate: '' }; const state = { apiUrl: container.dataset.apiUrl || 'booking/api.php', csrfToken: '', events: [], currentMonth: new Date(now.getFullYear(), now.getMonth(), 1), selectedDate: '' };
const fetchEvents = async () => { const fetchEvents = async () => {
const from = formatDateKey(state.currentMonth); const from = formatDateKey(state.currentMonth);
const to = formatDateKey(new Date(state.currentMonth.getFullYear(), state.currentMonth.getMonth() + 1, 1)); const to = formatDateKey(new Date(state.currentMonth.getFullYear(), state.currentMonth.getMonth() + 1, 1));
@@ -1,10 +1,11 @@
@echo off @echo off
setlocal setlocal
set "SOURCE=%~dp0..\public\perspektivwerk\*" set "SOURCE=%~dp0..\public\perspektivwerk"
set "TARGET=boogie:html/perspektivwerk/" set "TARGET=boogie:html/perspektivwerk/"
set "CONFIG=%~dp0..\public\booking-config.php"
echo Lade das PerspektivWerk-Mockup nach %TARGET% hoch ... echo Lade den Perspektivwerk-DocumentRoot und die Konfiguration nach %TARGET% hoch ...
scp -r "%SOURCE%" "%TARGET%" scp -r "%SOURCE%" "%TARGET%"
if errorlevel 1 ( if errorlevel 1 (
@@ -12,4 +13,12 @@ if errorlevel 1 (
exit /b 1 exit /b 1
) )
echo Setze Leseberechtigungen auf %TARGET% ...
ssh boogie "chmod -R a+rX html/perspektivwerk/"
if errorlevel 1 (
echo Berechtigungen konnten nicht gesetzt werden.
exit /b 1
)
echo Upload abgeschlossen. echo Upload abgeschlossen.
@@ -0,0 +1,16 @@
$projectRoot = Split-Path -Parent $PSScriptRoot
$deployBatchPath = Join-Path $projectRoot 'scripts\Deploy-Perspektivwerk.bat'
$htaccessPath = Join-Path $projectRoot 'public\.htaccess'
Describe 'Perspektivwerk deployment' {
It 'deploys the complete Perspektivwerk document root and its sibling configuration file' {
$script = Get-Content -LiteralPath $deployBatchPath -Raw
$htaccess = Get-Content -LiteralPath $htaccessPath -Raw
$script | Should Match 'public\\booking-config\.php'
$script | Should Match 'public\\perspektivwerk"'
$script | Should Match 'boogie:html/perspektivwerk/'
$htaccess | Should Match '<Files "booking-config\.php">'
$htaccess | Should Match 'Require all denied'
}
}
@@ -2,8 +2,8 @@ $projectRoot = Split-Path -Parent $PSScriptRoot
$mockupPath = Join-Path $projectRoot 'public\perspektivwerk\index.html' $mockupPath = Join-Path $projectRoot 'public\perspektivwerk\index.html'
$bookingScriptPath = Join-Path $projectRoot 'public\perspektivwerk\js\booking.js' $bookingScriptPath = Join-Path $projectRoot 'public\perspektivwerk\js\booking.js'
$bookingStylesPath = Join-Path $projectRoot 'public\perspektivwerk\js\booking.css' $bookingStylesPath = Join-Path $projectRoot 'public\perspektivwerk\js\booking.css'
$contaoBookingScriptPath = Join-Path $projectRoot 'public\booking\booking.js' $deploymentBookingScriptPath = Join-Path $projectRoot 'public\perspektivwerk\booking\booking.js'
$contaoBookingStylesPath = Join-Path $projectRoot 'public\booking\booking.css' $deploymentBookingStylesPath = Join-Path $projectRoot 'public\perspektivwerk\booking\booking.css'
$htaccessPath = Join-Path $projectRoot 'public\.htaccess' $htaccessPath = Join-Path $projectRoot 'public\.htaccess'
Describe 'Perspektivwerk static route' { Describe 'Perspektivwerk static route' {
@@ -19,26 +19,28 @@ Describe 'Perspektivwerk static route' {
$mockup | Should Match 'href="js/booking\.css"' $mockup | Should Match 'href="js/booking\.css"'
$mockup | Should Match 'data-sufw-booking data-project="perspektivwerk"' $mockup | Should Match 'data-sufw-booking data-project="perspektivwerk"'
$mockup | Should Match 'data-sufw-booking data-project="produktionsschule"' $mockup | Should Match 'data-sufw-booking data-project="produktionsschule"'
$mockup | Should Match 'data-api-url="booking/api\.php"'
$mockup | Should Not Match 'data-api-url="/booking/api\.php"'
$mockup.IndexOf('data-project="perspektivwerk"') | Should BeLessThan $mockup.IndexOf('data-project="produktionsschule"') $mockup.IndexOf('data-project="perspektivwerk"') | Should BeLessThan $mockup.IndexOf('data-project="produktionsschule"')
$mockup | Should Match 'src="js/booking\.js"' $mockup | Should Match 'src="js/booking\.js"'
} }
It 'keeps the Contao and static-preview asset copies identical' { It 'keeps the deployed API assets and static-preview copies identical' {
(Test-Path -LiteralPath $contaoBookingScriptPath) | Should Be $true (Test-Path -LiteralPath $deploymentBookingScriptPath) | Should Be $true
(Test-Path -LiteralPath $contaoBookingStylesPath) | Should Be $true (Test-Path -LiteralPath $deploymentBookingStylesPath) | Should Be $true
(Get-FileHash -LiteralPath $bookingScriptPath).Hash | Should Be (Get-FileHash -LiteralPath $contaoBookingScriptPath).Hash (Get-FileHash -LiteralPath $bookingScriptPath).Hash | Should Be (Get-FileHash -LiteralPath $deploymentBookingScriptPath).Hash
(Get-FileHash -LiteralPath $bookingStylesPath).Hash | Should Be (Get-FileHash -LiteralPath $contaoBookingStylesPath).Hash (Get-FileHash -LiteralPath $bookingStylesPath).Hash | Should Be (Get-FileHash -LiteralPath $deploymentBookingStylesPath).Hash
} }
It 'keeps the enabled submit button visibly rendered' { It 'keeps the enabled submit button visibly rendered' {
$script = Get-Content -LiteralPath $contaoBookingScriptPath -Raw $script = Get-Content -LiteralPath $deploymentBookingScriptPath -Raw
$script | Should Match "submitButton\.style\.visibility = 'visible'" $script | Should Match "submitButton\.style\.visibility = 'visible'"
$script | Should Match "actions\.style\.position = 'sticky'" $script | Should Match "actions\.style\.position = 'sticky'"
} }
It 'defines colours for the booking form action buttons' { It 'defines colours for the booking form action buttons' {
$styles = Get-Content -LiteralPath $contaoBookingStylesPath -Raw $styles = Get-Content -LiteralPath $deploymentBookingStylesPath -Raw
$styles | Should Match '\.sufw-booking__form-actions button\s*\{[^}]*background-color:\s*#08752a' $styles | Should Match '\.sufw-booking__form-actions button\s*\{[^}]*background-color:\s*#08752a'
$styles | Should Match '\.sufw-booking__form-actions button\s*\{[^}]*color:\s*#fff' $styles | Should Match '\.sufw-booking__form-actions button\s*\{[^}]*color:\s*#fff'
@@ -47,9 +49,12 @@ Describe 'Perspektivwerk static route' {
It 'routes /perspektivwerk to the static entry point before the Contao fallback' { It 'routes /perspektivwerk to the static entry point before the Contao fallback' {
$htaccess = Get-Content -LiteralPath $htaccessPath -Raw $htaccess = Get-Content -LiteralPath $htaccessPath -Raw
$staticRouteIndex = $htaccess.IndexOf('RewriteRule ^perspektivwerk/?$ perspektivwerk/index.html [L]') $staticRouteIndex = $htaccess.IndexOf('RewriteRule ^perspektivwerk/?$ perspektivwerk/index.html [L]')
$adminRouteIndex = $htaccess.IndexOf('RewriteRule ^perspektivwerk/booking-admin/?$ perspektivwerk/booking-admin/index.php [L]')
$fallbackIndex = $htaccess.IndexOf('RewriteRule ^ %{ENV:BASE}/index.php [L]') $fallbackIndex = $htaccess.IndexOf('RewriteRule ^ %{ENV:BASE}/index.php [L]')
$staticRouteIndex | Should BeGreaterThan -1 $staticRouteIndex | Should BeGreaterThan -1
$staticRouteIndex | Should BeLessThan $fallbackIndex $staticRouteIndex | Should BeLessThan $fallbackIndex
$adminRouteIndex | Should BeGreaterThan -1
$adminRouteIndex | Should BeLessThan $fallbackIndex
} }
} }
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
require __DIR__.'/../public/perspektivwerk/booking/backend.php';
function expectSame(string $expected, string $actual, string $message): void
{
if ($expected !== $actual) {
throw new RuntimeException($message."\nExpected: $expected\nActual: $actual");
}
}
$configFile = tempnam(sys_get_temp_dir(), 'sufw-booking-config-');
if ($configFile === false) throw new RuntimeException('Temporary configuration file could not be created.');
try {
file_put_contents($configFile, "<?php\nreturn ['database_url' => 'mysql://fallback:secret@database:3306/booking'];\n");
expectSame(
'mysql://fallback:secret@database:3306/booking',
bookingDatabaseUrl('', $configFile),
'The configuration file must be used when DATABASE_URL is empty.'
);
expectSame(
'mysql://environment:secret@database:3306/booking',
bookingDatabaseUrl('mysql://environment:secret@database:3306/booking', $configFile),
'DATABASE_URL must take precedence over the configuration file.'
);
} finally {
unlink($configFile);
}
echo "booking backend configuration tests passed\n";
@@ -13,9 +13,9 @@ test('liefert eine externe, idempotente Anfangsmigration für Termine und Anmeld
test('hält die statische Vorschaukopie der Buchungsassets identisch', async () => { test('hält die statische Vorschaukopie der Buchungsassets identisch', async () => {
const [componentScript, previewScript, componentStyle, previewStyle] = await Promise.all([ const [componentScript, previewScript, componentStyle, previewStyle] = await Promise.all([
readFile(new URL('../public/booking/booking.js', import.meta.url), 'utf8'), readFile(new URL('../public/perspektivwerk/booking/booking.js', import.meta.url), 'utf8'),
readFile(new URL('../public/perspektivwerk/js/booking.js', import.meta.url), 'utf8'), readFile(new URL('../public/perspektivwerk/js/booking.js', import.meta.url), 'utf8'),
readFile(new URL('../public/booking/booking.css', import.meta.url), 'utf8'), readFile(new URL('../public/perspektivwerk/booking/booking.css', import.meta.url), 'utf8'),
readFile(new URL('../public/perspektivwerk/js/booking.css', import.meta.url), 'utf8'), readFile(new URL('../public/perspektivwerk/js/booking.css', import.meta.url), 'utf8'),
]); ]);
@@ -61,12 +61,8 @@ test('liefert zusätzliche lokale Demo-Termine für Juli und August', async () =
}); });
test('liefert die unabhängige Verwaltung unter booking-admin mit getrennten Bereichen', async () => { test('liefert die unabhängige Verwaltung unter booking-admin mit getrennten Bereichen', async () => {
const [routes, admin] = await Promise.all([ const admin = await readFile(new URL('../public/perspektivwerk/booking-admin/index.php', import.meta.url), 'utf8');
readFile(new URL('../public/.htaccess', import.meta.url), 'utf8'),
readFile(new URL('../public/booking-admin/index.php', import.meta.url), 'utf8'),
]);
assert.match(routes, /\^booking-admin\/\?\$ booking-admin\/index\.php/);
assert.match(admin, /view === 'registrations'/); assert.match(admin, /view === 'registrations'/);
assert.match(admin, /view === 'export'/); assert.match(admin, /view === 'export'/);
assert.doesNotMatch(admin, /getenv\(/); assert.doesNotMatch(admin, /getenv\(/);
+1 -1
View File
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { bookingConfirmationMessage, canSubmitBookingForm, formatDateKey, formatEventDuration, getSlotsForDate, requiresIntroductionDetails, validateBookingForm } from '../public/booking/booking.js'; import { bookingConfirmationMessage, canSubmitBookingForm, formatDateKey, formatEventDuration, getSlotsForDate, requiresIntroductionDetails, validateBookingForm } from '../public/perspektivwerk/booking/booking.js';
const events = [ const events = [
{ id: 'one', project: 'perspektivwerk', startsAt: '2026-09-14 08:00:00', endsAt: '2026-09-18 16:00:00', status: 'available' }, { id: 'one', project: 'perspektivwerk', startsAt: '2026-09-14 08:00:00', endsAt: '2026-09-18 16:00:00', status: 'available' },