Docker Compose mit drei Services (nginx auf Port 8000), Migrationsmechanik, Update- und Backup-Skript. Anwendung: Router mit CSRF-Prüfung, Anmeldung und Ersteinrichtung, zentraler Einstellungsdialog mit Reitern, ein- und ausschaltbares Debug-Log mit Maskierung von Zugangsdaten. Layout nach Vorbild des FinanzManagers: feste Navigation links, Werkzeugleiste oben, Tab-Leiste; Farbschema hell/dunkel/System. Tests: 51 PHP-Prüfungen und 7 Playwright-Tests, alle grün.
78 lines
2 KiB
PHP
Executable file
78 lines
2 KiB
PHP
Executable file
#!/usr/bin/env php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Spielt alle noch nicht angewandten Migrationen aus migrations/ ein.
|
||
*
|
||
* Läuft automatisch beim Containerstart (docker-entrypoint.sh). Ein zweiter
|
||
* Lauf ändert nichts: bereits eingespielte Dateien stehen in `migrationen`.
|
||
*/
|
||
|
||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||
|
||
use Finanzmanager\Config;
|
||
use Finanzmanager\Database;
|
||
|
||
Config::load();
|
||
|
||
// Beim Containerstart kann die Datenbank noch hochfahren – kurz warten.
|
||
$versuche = 0;
|
||
while (true) {
|
||
try {
|
||
Database::get();
|
||
break;
|
||
} catch (\PDOException $e) {
|
||
if (++$versuche >= 30) {
|
||
fwrite(STDERR, "Datenbank nicht erreichbar: {$e->getMessage()}\n");
|
||
exit(1);
|
||
}
|
||
sleep(1);
|
||
}
|
||
}
|
||
|
||
Database::execute(
|
||
'CREATE TABLE IF NOT EXISTS migrationen (
|
||
datei VARCHAR(255) NOT NULL,
|
||
angewandt_am DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (datei)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
|
||
);
|
||
|
||
$angewandt = array_column(
|
||
Database::fetchAll('SELECT datei FROM migrationen'),
|
||
'datei'
|
||
);
|
||
|
||
$dateien = glob(dirname(__DIR__) . '/migrations/*.sql') ?: [];
|
||
sort($dateien);
|
||
|
||
$neu = 0;
|
||
foreach ($dateien as $pfad) {
|
||
$name = basename($pfad);
|
||
if (in_array($name, $angewandt, true)) {
|
||
continue;
|
||
}
|
||
|
||
$sql = file_get_contents($pfad);
|
||
if ($sql === false || trim($sql) === '') {
|
||
fwrite(STDERR, "Übersprungen (leer oder nicht lesbar): {$name}\n");
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
// Mehrere Anweisungen je Datei: PDO::exec verarbeitet sie am Stück.
|
||
Database::get()->exec($sql);
|
||
Database::execute('INSERT INTO migrationen (datei) VALUES (?)', [$name]);
|
||
echo "Eingespielt: {$name}\n";
|
||
$neu++;
|
||
} catch (\PDOException $e) {
|
||
fwrite(STDERR, "Fehler in {$name}: {$e->getMessage()}\n");
|
||
exit(1);
|
||
}
|
||
}
|
||
|
||
echo $neu === 0
|
||
? "Keine neuen Migrationen.\n"
|
||
: "{$neu} Migration(en) eingespielt.\n";
|