From e44b16e523e9cd7268661fc2fd4b68d4b5158db6 Mon Sep 17 00:00:00 2001 From: Robert Wartenberg Date: Tue, 18 Aug 2026 17:24:12 +0200 Subject: [PATCH] =?UTF-8?q?feat:backup=20und=20restore=20der=20contao=20Da?= =?UTF-8?q?tenbank=20=C3=BCber=20sql=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- development-system/README.md | 20 ++++++- development-system/database/README.md | 35 ++++++++++++ .../database/contao-baseline.sql | 42 ++++++++++++++ .../scripts/Export-ContaoBaseline.ps1 | 47 ++++++++++++++++ .../scripts/Import-ContaoBaseline.ps1 | 55 +++++++++++++++++++ .../tests/ContaoBaseline.Tests.ps1 | 39 +++++++++++++ 6 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 development-system/database/README.md create mode 100644 development-system/database/contao-baseline.sql create mode 100644 development-system/scripts/Export-ContaoBaseline.ps1 create mode 100644 development-system/scripts/Import-ContaoBaseline.ps1 create mode 100644 development-system/tests/ContaoBaseline.Tests.ps1 diff --git a/development-system/README.md b/development-system/README.md index abc4820..4196c2d 100644 --- a/development-system/README.md +++ b/development-system/README.md @@ -17,10 +17,18 @@ docker compose exec app vendor/bin/contao-setup --no-interaction --env=dev docker compose exec app chown -R www-data:www-data var public docker compose exec app vendor/bin/contao-console contao:migrate --no-interaction -neuen admin account anlegen: +Neuen Admin-Account anlegen: + +```powershell docker compose exec app vendor/bin/contao-console contao:user:create --admin ``` +Danach den versionierten Seitenzustand importieren: + +```powershell +.\scripts\Import-ContaoBaseline.ps1 +``` + - Website/CMS: - Contao-Login: - Datenbankinspektor: (Server `db`, Benutzer `perspektivwerk`, Passwort `perspektivwerk`, Datenbank `perspektivwerk`) @@ -45,3 +53,13 @@ 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 Terminlogik liegt zunächst in `src/booking.ts`. Eine spätere Google-Calendar- oder Exchange-Anbindung sollte über eine Backend-API erfolgen, damit keine Secrets im Browser landen. + +## Versionierter Contao-Seitenzustand + +Die Contao-Seiten, Artikel und Layouts liegen in MariaDB und werden deshalb zusätzlich als bereinigter SQL-Seed versioniert. Details zu Export, Import und den bewusst ausgeschlossenen Daten stehen in [database/README.md](database/README.md). + +Die Seed-Integritätsprüfungen ausführen: + +```powershell +Invoke-Pester .\tests\ContaoBaseline.Tests.ps1 +``` diff --git a/development-system/database/README.md b/development-system/database/README.md new file mode 100644 index 0000000..07b063a --- /dev/null +++ b/development-system/database/README.md @@ -0,0 +1,35 @@ +# Versionierter Contao-Seitenzustand + +`contao-baseline.sql` ist der versionierte Ausgangszustand für Theme, Layout, Seiten, Artikel, Inhaltselemente und später auch Formulare. Er ist für eine frische Contao-Datenbank bestimmt und enthält keine Benutzer, Passworthashes, Sitzungen, Logs, Cache-Daten oder Zugangsdaten. + +## Nach einer redaktionellen Änderung exportieren + +```powershell +cd development-system +.\scripts\Export-ContaoBaseline.ps1 +git diff -- database/contao-baseline.sql +``` + +Bei einer vorhandenen Datei fragt das Skript vor dem Überschreiben nach. Für einen bewusst nicht-interaktiven Export, z. B. in einer Automatisierung, `-Force` ergänzen. Den Diff vor dem Commit prüfen. Die Exportdatei enthält keine `tl_user`-Daten. + +## In eine frische Datenbank importieren + +Nach `contao-setup` und `contao:migrate`: + +```powershell +cd development-system +.\scripts\Import-ContaoBaseline.ps1 +docker compose exec app vendor/bin/contao-console contao:user:create --admin +``` + +Der Import bricht ab, wenn die versionierten Contao-Tabellen bereits Inhalte enthalten. Das verhindert ein versehentliches Überschreiben. Nur für ein bewusstes Zurücksetzen auf den Git-Stand ist Folgendes vorgesehen: + +```powershell +.\scripts\Import-ContaoBaseline.ps1 -ReplaceExisting +``` + +`-ReplaceExisting` löscht ausschließlich Theme-, Layout-, Modul-, Seiten-, Artikel-, Inhaltselement- und Formulardaten. Benutzerkonten bleiben erhalten. + +## Medien + +Medien gehören als Dateien nach `development-system/files/` und werden in Git eingecheckt. Sobald Contao-Medien verwendet werden, muss der Seed zusätzlich die Metadaten aus `tl_files` enthalten. Das ist absichtlich noch nicht automatisiert, weil die aktuelle Datenbank nur Verweise auf nicht versionierte lokale Dateien enthält. diff --git a/development-system/database/contao-baseline.sql b/development-system/database/contao-baseline.sql new file mode 100644 index 0000000..0f75ccf --- /dev/null +++ b/development-system/database/contao-baseline.sql @@ -0,0 +1,42 @@ +-- Contao website baseline for local development. +-- +-- Contains only versionable website data. It deliberately excludes users, +-- credentials, sessions, logs, cache entries and file metadata. +-- Generated with scripts/Export-ContaoBaseline.ps1. + +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +INSERT INTO `tl_theme` (`id`, `tstamp`, `name`, `author`, `folders`, `screenshot`, `templates`) VALUES (1,1786708968,'Lokales PerspektivWerk-Theme','Lokale Entwicklung',NULL,NULL,''); + +INSERT INTO `tl_layout` (`type`, `combineScripts`, `minifyMarkup`, `lightboxSize`, `addJQuery`, `addMooTools`, `static`, `id`, `pid`, `tstamp`, `name`, `rows`, `headerHeight`, `footerHeight`, `cols`, `widthLeft`, `widthRight`, `sections`, `framework`, `external`, `modules`, `template`, `defaultImageDensities`, `viewport`, `titleTag`, `cssClass`, `onload`, `head`, `jquery`, `mootools`, `analytics`, `externalJs`, `scripts`, `script`, `width`, `align`, `newsfeeds`, `calendarfeeds`) VALUES ('default',1,1,'',0,0,0,2,1,1787041994,'PerspektivWerk Standardlayout','2rwh','','','1cl','','',NULL,'a:2:{i:0;s:10:\"layout.css\";i:1;s:14:\"responsive.css\";}',NULL,0x613A313A7B693A303B613A333A7B733A333A226D6F64223B693A303B733A333A22636F6C223B733A343A226D61696E223B733A363A22656E61626C65223B693A313B7D7D,'fe_page','','width=device-width,initial-scale=1.0,shrink-to-fit=no','','','','',NULL,NULL,NULL,NULL,NULL,NULL,'','center',NULL,NULL); + + +INSERT INTO `tl_page` (`requireItem`, `alwaysForward`, `redirectBack`, `target`, `fallback`, `disableLanguageRedirect`, `maintenanceMode`, `enableCanonical`, `useFolderUrl`, `useSSL`, `autoforward`, `protected`, `includeLayout`, `includeCache`, `alwaysLoadFromCache`, `includeChmod`, `hide`, `guests`, `published`, `enforceTwoFactor`, `enableCsp`, `csp`, `cspReportOnly`, `cspReportLog`, `imgSize`, `feedSource`, `feedFeatured`, `eventCalendars`, `feedRecurrenceLimit`, `id`, `pid`, `sorting`, `tstamp`, `title`, `type`, `alias`, `routePriority`, `pageTitle`, `language`, `robots`, `description`, `redirect`, `jumpTo`, `url`, `dns`, `staticFiles`, `staticPlugins`, `favicon`, `robotsTxt`, `mailerTransport`, `canonicalLink`, `canonicalKeepParams`, `adminEmail`, `dateFormat`, `timeFormat`, `datimFormat`, `validAliasCharacters`, `urlPrefix`, `urlSuffix`, `groups`, `layout`, `subpageLayout`, `cache`, `clientCache`, `cuser`, `cgroup`, `chmod`, `cssClass`, `sitemap`, `searchIndexer`, `accesskey`, `start`, `stop`, `twoFactorJumpTo`, `feedFormat`, `maxFeedItems`, `feedDescription`, `newsArchives`) VALUES (0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,'default-src \'self\'',0,0,'','source_teaser','all_items',NULL,25,5,0,128,1786715837,'Homepage','root','home',0,'Homepage','de','',NULL,'permanent',0,'','','','',NULL,NULL,'','','','','','','','','','',NULL,2,2,0,0,0,0,'a:9:{i:0;s:2:\"u1\";i:1;s:2:\"u2\";i:2;s:2:\"u3\";i:3;s:2:\"u4\";i:4;s:2:\"u5\";i:5;s:2:\"u6\";i:6;s:2:\"g4\";i:7;s:2:\"g5\";i:8;s:2:\"g6\";}','','','','','','',0,'rss',25,NULL,NULL); +INSERT INTO `tl_page` (`requireItem`, `alwaysForward`, `redirectBack`, `target`, `fallback`, `disableLanguageRedirect`, `maintenanceMode`, `enableCanonical`, `useFolderUrl`, `useSSL`, `autoforward`, `protected`, `includeLayout`, `includeCache`, `alwaysLoadFromCache`, `includeChmod`, `hide`, `guests`, `published`, `enforceTwoFactor`, `enableCsp`, `csp`, `cspReportOnly`, `cspReportLog`, `imgSize`, `feedSource`, `feedFeatured`, `eventCalendars`, `feedRecurrenceLimit`, `id`, `pid`, `sorting`, `tstamp`, `title`, `type`, `alias`, `routePriority`, `pageTitle`, `language`, `robots`, `description`, `redirect`, `jumpTo`, `url`, `dns`, `staticFiles`, `staticPlugins`, `favicon`, `robotsTxt`, `mailerTransport`, `canonicalLink`, `canonicalKeepParams`, `adminEmail`, `dateFormat`, `timeFormat`, `datimFormat`, `validAliasCharacters`, `urlPrefix`, `urlSuffix`, `groups`, `layout`, `subpageLayout`, `cache`, `clientCache`, `cuser`, `cgroup`, `chmod`, `cssClass`, `sitemap`, `searchIndexer`, `accesskey`, `start`, `stop`, `twoFactorJumpTo`, `feedFormat`, `maxFeedItems`, `feedDescription`, `newsArchives`) VALUES (0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,'default-src \'self\'',0,0,'','source_teaser','all_items',NULL,25,7,5,128,1787041994,'default','regular','default',0,'Perspektivwerk Default','','index,follow',NULL,'permanent',0,'','','','',NULL,NULL,'','','','','','','','','','',NULL,0,0,0,0,0,0,'a:9:{i:0;s:2:\"u1\";i:1;s:2:\"u2\";i:2;s:2:\"u3\";i:3;s:2:\"u4\";i:4;s:2:\"u5\";i:5;s:2:\"u6\";i:6;s:2:\"g4\";i:7;s:2:\"g5\";i:8;s:2:\"g6\";}','perspektivwerk-page','map_default','','','','',0,'rss',25,NULL,NULL); + +INSERT INTO `tl_article` (`showTeaser`, `protected`, `published`, `id`, `pid`, `sorting`, `tstamp`, `title`, `alias`, `author`, `inColumn`, `teaserCssID`, `teaser`, `printable`, `customTpl`, `groups`, `cssID`, `start`, `stop`) VALUES (0,0,1,3,7,128,1786716736,'default','default',3,'main','a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}',NULL,'','',NULL,'a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}','',''); +INSERT INTO `tl_article` (`showTeaser`, `protected`, `published`, `id`, `pid`, `sorting`, `tstamp`, `title`, `alias`, `author`, `inColumn`, `teaserCssID`, `teaser`, `printable`, `customTpl`, `groups`, `cssID`, `start`, `stop`) VALUES (0,0,1,5,7,256,1786716203,'Artikel2','Artikel2',3,'main','a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}',NULL,'','',NULL,'a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}','',''); + +INSERT INTO `tl_content` (`title`, `type`, `addImage`, `showPreview`, `inline`, `overwriteMeta`, `size`, `fullsize`, `thead`, `tfoot`, `tleft`, `sortable`, `closeSections`, `target`, `overwriteLink`, `useImage`, `useHomeDir`, `metaIgnore`, `splashImage`, `sliderContinuous`, `protected`, `jumpTo`, `redirectBack`, `autologin`, `pwResetPage`, `invisible`, `com_moderate`, `com_bbcode`, `com_disableCaptcha`, `com_requireLogin`, `jsonData`, `id`, `pid`, `ptable`, `sorting`, `tstamp`, `headline`, `sectionHeadline`, `text`, `singleSRC`, `alt`, `imageTitle`, `imageUrl`, `caption`, `floating`, `html`, `unfilteredHtml`, `listtype`, `listitems`, `tableitems`, `summary`, `sortIndex`, `sortOrder`, `mooHeadline`, `mooStyle`, `mooClasses`, `highlight`, `markdownSource`, `code`, `url`, `titleText`, `linkTitle`, `embed`, `rel`, `multiSRC`, `perRow`, `perPage`, `numberOfItems`, `sortBy`, `galleryTpl`, `customTpl`, `playerSRC`, `youtube`, `vimeo`, `posterSRC`, `playerSize`, `playerOptions`, `playerStart`, `playerStop`, `playerTitle`, `playerCaption`, `playerAspect`, `textTrackSRC`, `playerPreload`, `playerColor`, `youtubeOptions`, `vimeoOptions`, `sliderDelay`, `sliderSpeed`, `sliderStartSlide`, `data`, `cteAlias`, `articleAlias`, `article`, `form`, `module`, `groups`, `cssID`, `start`, `stop`, `com_order`, `com_perPage`, `com_template`) VALUES ('','headline',0,0,0,0,'',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,NULL,2,5,'tl_article',128,1786716133,'a:2:{s:4:\"unit\";s:2:\"h2\";s:5:\"value\";s:14:\"Perspektivwerk\";}','a:2:{s:5:\"value\";s:0:\"\";s:4:\"unit\";s:2:\"h2\";}',NULL,NULL,NULL,NULL,NULL,NULL,'above',NULL,NULL,'',NULL,NULL,'',0,'ascending','','','','','sourceText',NULL,NULL,'','','','',NULL,4,0,0,'','','',NULL,'','',NULL,'',NULL,'','','','','',NULL,'','',NULL,NULL,0,300,0,NULL,0,0,0,0,0,NULL,'a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}','','','ascending',0,''); +INSERT INTO `tl_content` (`title`, `type`, `addImage`, `showPreview`, `inline`, `overwriteMeta`, `size`, `fullsize`, `thead`, `tfoot`, `tleft`, `sortable`, `closeSections`, `target`, `overwriteLink`, `useImage`, `useHomeDir`, `metaIgnore`, `splashImage`, `sliderContinuous`, `protected`, `jumpTo`, `redirectBack`, `autologin`, `pwResetPage`, `invisible`, `com_moderate`, `com_bbcode`, `com_disableCaptcha`, `com_requireLogin`, `jsonData`, `id`, `pid`, `ptable`, `sorting`, `tstamp`, `headline`, `sectionHeadline`, `text`, `singleSRC`, `alt`, `imageTitle`, `imageUrl`, `caption`, `floating`, `html`, `unfilteredHtml`, `listtype`, `listitems`, `tableitems`, `summary`, `sortIndex`, `sortOrder`, `mooHeadline`, `mooStyle`, `mooClasses`, `highlight`, `markdownSource`, `code`, `url`, `titleText`, `linkTitle`, `embed`, `rel`, `multiSRC`, `perRow`, `perPage`, `numberOfItems`, `sortBy`, `galleryTpl`, `customTpl`, `playerSRC`, `youtube`, `vimeo`, `posterSRC`, `playerSize`, `playerOptions`, `playerStart`, `playerStop`, `playerTitle`, `playerCaption`, `playerAspect`, `textTrackSRC`, `playerPreload`, `playerColor`, `youtubeOptions`, `vimeoOptions`, `sliderDelay`, `sliderSpeed`, `sliderStartSlide`, `data`, `cteAlias`, `articleAlias`, `article`, `form`, `module`, `groups`, `cssID`, `start`, `stop`, `com_order`, `com_perPage`, `com_template`) VALUES ('','headline',0,0,0,0,'',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,NULL,3,3,'tl_article',128,1786716801,'a:2:{s:4:\"unit\";s:2:\"h2\";s:5:\"value\";s:83:\"PerspektivWerk ÔÇô Br├╝cke zwischen Diagnostik, Begleitung und betrieblicher Praxis\";}','a:2:{s:5:\"value\";s:0:\"\";s:4:\"unit\";s:2:\"h2\";}',NULL,NULL,NULL,NULL,NULL,NULL,'above',NULL,NULL,'',NULL,NULL,'',0,'ascending','','','','','sourceText',NULL,NULL,'','','','',NULL,4,0,0,'','','',NULL,'','',NULL,'',NULL,'','','','','',NULL,'','',NULL,NULL,0,300,0,NULL,0,0,0,0,0,NULL,'a:2:{i:0;s:0:\"\";i:1;s:0:\"\";}','','','ascending',0,''); + + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + diff --git a/development-system/scripts/Export-ContaoBaseline.ps1 b/development-system/scripts/Export-ContaoBaseline.ps1 new file mode 100644 index 0000000..ff2f9bb --- /dev/null +++ b/development-system/scripts/Export-ContaoBaseline.ps1 @@ -0,0 +1,47 @@ +[CmdletBinding()] +param( + [string]$ComposeFile = (Join-Path $PSScriptRoot '..\docker-compose.yml'), + [string]$OutputPath = (Join-Path $PSScriptRoot '..\database\contao-baseline.sql'), + [switch]$Force +) + +$ErrorActionPreference = 'Stop' +$tables = @('tl_theme', 'tl_layout', 'tl_module', 'tl_page', 'tl_article', 'tl_content', 'tl_form', 'tl_form_field') + +if ((Test-Path -LiteralPath $OutputPath) -and -not $Force) { + $confirmation = Read-Host "Die Datei '$OutputPath' existiert bereits. Überschreiben? [j/N]" + if ($confirmation.Trim().ToLowerInvariant() -notin @('j', 'ja', 'y', 'yes')) { + Write-Host 'Export nicht überschrieben.' + return + } +} + +$composeFile = (Resolve-Path -LiteralPath $ComposeFile).Path +$outputDirectory = Split-Path -Parent $OutputPath +New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + +# Credentials bleiben im Compose-Service und werden nicht in den Export geschrieben. +$command = 'exec mariadb-dump -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" --no-tablespaces --no-create-info --skip-comments --skip-triggers --skip-add-locks --skip-disable-keys --complete-insert --skip-extended-insert --order-by-primary --hex-blob "$MARIADB_DATABASE" tl_theme tl_layout tl_module tl_page tl_article tl_content tl_form tl_form_field' +$dump = & docker compose -f $composeFile exec -T db sh -lc $command + +if ($LASTEXITCODE -ne 0) { + throw 'Der Contao-Baseline-Export ist fehlgeschlagen.' +} + +$header = @( + '-- Contao website baseline for local development.', + '--', + '-- Contains only versionable website data. It deliberately excludes users,', + '-- credentials, sessions, logs, cache entries and file metadata.', + '-- Generated with scripts/Export-ContaoBaseline.ps1.', + '' +) + +$utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllText( + (Resolve-Path -LiteralPath $outputDirectory).Path + [System.IO.Path]::DirectorySeparatorChar + (Split-Path -Leaf $OutputPath), + (($header + $dump) -join [Environment]::NewLine) + [Environment]::NewLine, + $utf8WithoutBom +) + +Write-Host "Contao-Baseline exportiert nach $OutputPath" diff --git a/development-system/scripts/Import-ContaoBaseline.ps1 b/development-system/scripts/Import-ContaoBaseline.ps1 new file mode 100644 index 0000000..48a5aac --- /dev/null +++ b/development-system/scripts/Import-ContaoBaseline.ps1 @@ -0,0 +1,55 @@ +[CmdletBinding()] +param( + [string]$ComposeFile = (Join-Path $PSScriptRoot '..\docker-compose.yml'), + [string]$SeedPath = (Join-Path $PSScriptRoot '..\database\contao-baseline.sql'), + [switch]$ReplaceExisting +) + +$ErrorActionPreference = 'Stop' +$tables = @('tl_theme', 'tl_layout', 'tl_module', 'tl_page', 'tl_article', 'tl_content', 'tl_form', 'tl_form_field') + +if (-not (Test-Path -LiteralPath $SeedPath)) { + throw "Der Baseline-Seed '$SeedPath' wurde nicht gefunden." +} + +$composeFile = (Resolve-Path -LiteralPath $ComposeFile).Path +$countQuery = ($tables | ForEach-Object { "SELECT COUNT(*) FROM $_" }) -join ' UNION ALL ' +$checkCommand = "mariadb -N -u`"`$MARIADB_USER`" -p`"`$MARIADB_PASSWORD`" `"`$MARIADB_DATABASE`" -e `"$countQuery`"" +$existingRows = (& docker compose -f $composeFile exec -T db sh -lc $checkCommand | ForEach-Object { [int]$_ } | Measure-Object -Sum).Sum + +if ($existingRows -gt 0 -and -not $ReplaceExisting) { + throw 'Die versionierten Contao-Tabellen enthalten bereits Daten. Der Import wurde nicht ausgeführt. Nutze -ReplaceExisting nur, wenn du die aktuelle Seitenstruktur bewusst durch den Git-Stand ersetzen möchtest.' +} + +$seed = Get-Content -LiteralPath $SeedPath -Raw +if ($ReplaceExisting) { + $deleteStatements = @( + 'SET FOREIGN_KEY_CHECKS=0;', + 'DELETE FROM tl_content;', + 'DELETE FROM tl_article;', + 'DELETE FROM tl_form_field;', + 'DELETE FROM tl_form;', + 'DELETE FROM tl_module;', + 'DELETE FROM tl_page;', + 'DELETE FROM tl_layout;', + 'DELETE FROM tl_theme;', + 'SET FOREIGN_KEY_CHECKS=1;', + '' + ) -join [Environment]::NewLine + $seed = $deleteStatements + $seed +} + +$utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) +[Console]::OutputEncoding = $utf8WithoutBom +$seed | & docker compose -f $composeFile exec -T db sh -lc 'exec mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE"' + +if ($LASTEXITCODE -ne 0) { + throw 'Der Contao-Baseline-Import ist fehlgeschlagen.' +} + +& docker compose -f $composeFile exec -T app sh -lc 'runuser -u www-data -- vendor/bin/contao-console cache:clear --env=dev' +if ($LASTEXITCODE -ne 0) { + throw 'Der Import war erfolgreich, aber der Contao-Cache konnte nicht geleert werden.' +} + +Write-Host 'Contao-Baseline importiert und Cache geleert.' diff --git a/development-system/tests/ContaoBaseline.Tests.ps1 b/development-system/tests/ContaoBaseline.Tests.ps1 new file mode 100644 index 0000000..142081c --- /dev/null +++ b/development-system/tests/ContaoBaseline.Tests.ps1 @@ -0,0 +1,39 @@ +$projectRoot = Split-Path -Parent $PSScriptRoot +$seedPath = Join-Path $projectRoot 'database\contao-baseline.sql' +$exportScriptPath = Join-Path $projectRoot 'scripts\Export-ContaoBaseline.ps1' +$importScriptPath = Join-Path $projectRoot 'scripts\Import-ContaoBaseline.ps1' + +Describe 'Contao baseline' { + $seed = Get-Content -LiteralPath $seedPath -Raw + + It 'contains the page-specific PerspektivWerk background rule' { + $seed | Should Match 'body\.perspektivwerk-page \{ background-color: #159A34; \}' + } + + It 'contains the versioned Contao website tables' { + foreach ($table in @('tl_theme', 'tl_layout', 'tl_page', 'tl_article', 'tl_content')) { + $seed | Should Match "INSERT INTO ``$table``" + } + } + + It 'does not contain user, runtime or unversioned file metadata' { + $seed | Should Not Match 'INSERT INTO `tl_(user|session|log|lock|files)' + } + + It 'uses the same approved table list in export and import scripts' { + $export = Get-Content -LiteralPath $exportScriptPath -Raw + $import = Get-Content -LiteralPath $importScriptPath -Raw + + foreach ($table in @('tl_theme', 'tl_layout', 'tl_module', 'tl_page', 'tl_article', 'tl_content', 'tl_form', 'tl_form_field')) { + $export | Should Match $table + $import | Should Match $table + } + } + + It 'asks before overwriting an existing baseline export' { + $export = Get-Content -LiteralPath $exportScriptPath -Raw + + $export | Should Match 'Read-Host' + $export | Should Match 'überschreiben' + } +}