diff --git a/README.md b/README.md index b167d33..b116f09 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,11 @@ Archivbestand erhalten. ## Hilfsskripte -- `scripts/check-prerequisites.ps1` prueft lokale Voraussetzungen. -- `scripts/prepare-saas-env.ps1` legt aus `.env.example` eine lokale `.env` an. +- `scripts/check-prerequisites.php` prueft lokale Voraussetzungen. +- `scripts/prepare-saas-env.php` legt aus `.env.example` eine lokale `.env` an. +- `scripts/install-saas.php` fuehrt den lokalen Setup-Grundlauf aus. +- `scripts/build-migration-bundle.php` baut die SQL-Migrationen zu einer Datei. +- `scripts/run-sql-migrations.php` fuehrt die SQL-Migrationen direkt per PDO SQL Server aus. ## Hinweise Zum Umbau diff --git a/docs/installationshandbuch.md b/docs/installationshandbuch.md index 93e43ac..f22d1a0 100644 --- a/docs/installationshandbuch.md +++ b/docs/installationshandbuch.md @@ -7,7 +7,7 @@ Worker ausgelegt. ## Voraussetzungen - PHP 8.2 oder neuer -- Composer +- Composer fuer den spaeteren Laravel-Bootstrap - SQL Server oder eine kompatible Datenbank fuer das Zielsystem - Webserver mit Document-Root auf `public/` des Zielprojekts - Cron-Zugang @@ -17,13 +17,41 @@ Worker ausgelegt. ## Installation lokal 1. Repository klonen. -2. In `saas-app/` wechseln. -3. Abhaengigkeiten mit Composer installieren. -4. `.env` aus `.env.example` ableiten. -5. Datenbankzugang, URL, Mail und Tenancy-Werte eintragen. -6. Migrations ausfuehren. +2. Mit `php scripts/check-prerequisites.php` die Voraussetzungen pruefen. +3. Mit `php scripts/prepare-saas-env.php` eine `.env` aus `.env.example` anlegen. +4. Datenbankzugang, URL, Mail und Tenancy-Werte in `saas-app/.env` eintragen. +5. Mit `php scripts/install-saas.php` das SQL-Bundle erzeugen. +6. Das SQL-Bundle manuell in SQL Server ausfuehren oder mit `php scripts/run-sql-migrations.php --server= --database= --username= --password=` direkt einspielen. 7. Einen ersten Mandanten anlegen. 8. Einen ersten Benutzer und eine Mitgliedszuordnung anlegen. +9. Spaeter Composer und das eigentliche Laravel-Bootstrap nachziehen. + +## Migrationen ausfuehren + +Die aktuellen Dateien unter `saas-app/database/migrations/` sind SQL-Skizzen in +PHP-Dateien, keine Laravel-Migrationsklassen. Deshalb gibt es aktuell bewusst +kein `php artisan migrate`. + +Stattdessen wird die SQL-Datei ueber Skripte erzeugt: + +- Installationsvorbereitung inklusive SQL-Bundle: + `php scripts/install-saas.php` +- SQL direkt gegen SQL Server ausfuehren: + `php scripts/run-sql-migrations.php --server=localhost --database=kaffeeliste_saas --username=sa --password=` + +Die erzeugte SQL-Datei liegt anschliessend unter: + +- `saas-app/database/migrations/generated/all-migrations.sql` + +## Installationsskripte + +Vorhandene Hilfsskripte: + +- `scripts/check-prerequisites.php` +- `scripts/prepare-saas-env.php` +- `scripts/install-saas.php` +- `scripts/build-migration-bundle.php` +- `scripts/run-sql-migrations.php` ## Wichtige Umgebungswerte @@ -42,7 +70,7 @@ eigene Umgebung angepasst werden: 1. Den Inhalt von `saas-app/` auf den Zielserver hochladen. 2. Das Document-Root auf `public/` zeigen lassen. -3. Schreibrechte fuer `storage/`, Cache und Queue-Tabellen sicherstellen. +3. Schreibrechte fuer spaetere Storage-, Cache- und Queue-Bereiche sicherstellen. 4. `.env` serverseitig hinterlegen. 5. Die Anwendung einmal per Browser aufrufen und die Grundseiten pruefen. diff --git a/saas-app/README.md b/saas-app/README.md index 4b4e183..3886871 100644 --- a/saas-app/README.md +++ b/saas-app/README.md @@ -19,15 +19,27 @@ Die komplette Installationsanleitung steht im Repo unter Kurzfassung: -1. `saas-app/` als Projektrahmen bereitstellen. -2. PHP 8.2+ und Composer verwenden. +1. `php ../scripts/check-prerequisites.php` +2. `php ../scripts/install-saas.php` 3. `.env` aus `.env.example` ableiten und anpassen. 4. Datenbank und Tenancy-Werte konfigurieren. -5. Migrations ausfuehren. +5. SQL-Migrationen ueber das erzeugte Bundle ausfuehren. 6. Einen ersten Mandanten und erste Benutzer anlegen. 7. Den Webserver auf `public/` ausrichten. 8. Cron-Jobs fuer Queue, Import, Export und Benachrichtigungen einrichten. +## Migrationen + +Aktuell gibt es in diesem Verzeichnis keine lauffaehige `artisan migrate`-Strecke. +Die Dateien unter `database/migrations/` liefern SQL und werden ueber folgende +Skripte verarbeitet: + +- `..\scripts\build-migration-bundle.php` +- `..\scripts\run-sql-migrations.php` + +Wenn `pdo_sqlsrv` lokal nicht verfuegbar ist, muss das erzeugte SQL-Bundle +manuell gegen SQL Server ausgefuehrt werden. + ## Migration Aus Dem Legacy-System Die fachliche Roadmap und der Uebergang aus dem alten Root-System sind in diff --git a/saas-app/database/migrations/generated/all-migrations.sql b/saas-app/database/migrations/generated/all-migrations.sql new file mode 100644 index 0000000..a4c29d1 --- /dev/null +++ b/saas-app/database/migrations/generated/all-migrations.sql @@ -0,0 +1,277 @@ +-- Generated migration bundle for Kaffeeliste SaaS +-- Generated at 2026-03-21T18:18:09+00:00 +SET XACT_ABORT ON; +BEGIN TRANSACTION; + +-- Migration: 2026_03_20_000001_create_tenants_table.php +CREATE TABLE tenants ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_key VARCHAR(120) NOT NULL, + name VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'active', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (tenant_key) +); + +-- Migration: 2026_03_20_000002_create_users_table.php +CREATE TABLE users ( + id CHAR(36) NOT NULL PRIMARY KEY, + email VARCHAR(255) NOT NULL, + password_hash VARCHAR(255) NULL, + display_name VARCHAR(255) NOT NULL, + is_platform_admin BIT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (email) +); + +-- Migration: 2026_03_20_000003_create_tenant_users_table.php +CREATE TABLE tenant_users ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + user_id CHAR(36) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'active', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (tenant_id, user_id), + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (user_id) REFERENCES users(id) +); + +-- Migration: 2026_03_20_000004_create_roles_table.php +CREATE TABLE roles ( + id CHAR(36) NOT NULL PRIMARY KEY, + role_key VARCHAR(100) NOT NULL, + name VARCHAR(255) NOT NULL, + scope VARCHAR(50) NOT NULL DEFAULT 'tenant', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (role_key, scope) +); + +-- Migration: 2026_03_20_000005_create_tenant_user_roles_table.php +CREATE TABLE tenant_user_roles ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_user_id CHAR(36) NOT NULL, + role_id CHAR(36) NOT NULL, + created_at DATETIME NOT NULL, + UNIQUE (tenant_user_id, role_id), + FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id), + FOREIGN KEY (role_id) REFERENCES roles(id) +); + +-- Migration: 2026_03_20_000006_create_members_table.php +CREATE TABLE members ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + tenant_user_id CHAR(36) NOT NULL, + display_name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'active', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (tenant_id, email), + UNIQUE (tenant_id, tenant_user_id), + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id) +); + +-- Migration: 2026_03_20_000006_create_password_reset_tokens_table.php +CREATE TABLE password_reset_tokens ( + email VARCHAR(255) NOT NULL, + token VARCHAR(255) NOT NULL, + created_at DATETIME NOT NULL, + PRIMARY KEY (email) +); + +-- Migration: 2026_03_20_000007_create_coffee_entries_table.php +CREATE TABLE coffee_entries ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + member_id CHAR(36) NOT NULL, + strokes INT NOT NULL, + unit_price DECIMAL(10,2) NOT NULL, + total_cost DECIMAL(10,2) NOT NULL, + booking_source VARCHAR(50) NOT NULL DEFAULT 'manual', + booked_at DATETIME NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (member_id) REFERENCES members(id) +); + +-- Migration: 2026_03_20_000007_create_tenant_identity_providers_table.php +CREATE TABLE tenant_identity_providers ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + provider_key VARCHAR(120) NOT NULL, + driver VARCHAR(50) NOT NULL DEFAULT 'oidc', + display_name VARCHAR(255) NOT NULL, + client_id VARCHAR(255) NOT NULL, + client_secret_encrypted VARCHAR(1024) NULL, + discovery_url VARCHAR(1024) NULL, + authorization_url VARCHAR(1024) NULL, + token_url VARCHAR(1024) NULL, + userinfo_url VARCHAR(1024) NULL, + redirect_uri VARCHAR(1024) NOT NULL, + scopes TEXT NULL, + is_enabled BIT NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (tenant_id, provider_key), + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000008_create_payment_entries_table.php +CREATE TABLE payment_entries ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + member_id CHAR(36) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + payment_method VARCHAR(50) NOT NULL DEFAULT 'manual', + booked_at DATETIME NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (member_id) REFERENCES members(id) +); + +-- Migration: 2026_03_20_000008_create_user_identities_table.php +CREATE TABLE user_identities ( + id CHAR(36) NOT NULL PRIMARY KEY, + user_id CHAR(36) NOT NULL, + tenant_id CHAR(36) NULL, + provider_key VARCHAR(120) NOT NULL, + external_subject VARCHAR(255) NOT NULL, + email VARCHAR(255) NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (provider_key, external_subject), + FOREIGN KEY (user_id) REFERENCES users(id), + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000009_create_ledger_entries_table.php +CREATE TABLE ledger_entries ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + member_id CHAR(36) NOT NULL, + entry_type VARCHAR(50) NOT NULL, + amount DECIMAL(10,2) NOT NULL, + reference_type VARCHAR(100) NOT NULL, + reference_id CHAR(36) NULL, + booked_at DATETIME NOT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id), + FOREIGN KEY (member_id) REFERENCES members(id) +); + +-- Migration: 2026_03_20_000010_create_announcements_table.php +CREATE TABLE announcements ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + title VARCHAR(255) NOT NULL, + message TEXT NOT NULL, + visible_until DATETIME NULL, + is_active BIT NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000011_create_faq_items_table.php +CREATE TABLE faq_items ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + question VARCHAR(255) NOT NULL, + answer TEXT NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + is_active BIT NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000012_create_surveys_table.php +CREATE TABLE surveys ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + title VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'draft', + starts_at DATETIME NULL, + ends_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000013_create_survey_questions_table.php +CREATE TABLE survey_questions ( + id CHAR(36) NOT NULL PRIMARY KEY, + survey_id CHAR(36) NOT NULL, + question TEXT NOT NULL, + question_type VARCHAR(50) NOT NULL, + is_required BIT NOT NULL DEFAULT 0, + sort_order INT NOT NULL DEFAULT 0, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (survey_id) REFERENCES surveys(id) +); + +-- Migration: 2026_03_20_000014_create_survey_answers_table.php +CREATE TABLE survey_answers ( + id CHAR(36) NOT NULL PRIMARY KEY, + survey_id CHAR(36) NOT NULL, + question_id CHAR(36) NOT NULL, + tenant_user_id CHAR(36) NOT NULL, + answer_text TEXT NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (survey_id) REFERENCES surveys(id), + FOREIGN KEY (question_id) REFERENCES survey_questions(id), + FOREIGN KEY (tenant_user_id) REFERENCES tenant_users(id) +); + +-- Migration: 2026_03_20_000015_create_import_jobs_table.php +CREATE TABLE import_jobs ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + import_type VARCHAR(100) NOT NULL, + source_path VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'pending', + scheduled_at DATETIME NULL, + processed_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000016_create_export_jobs_table.php +CREATE TABLE export_jobs ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + export_type VARCHAR(100) NOT NULL, + target_path VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'pending', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +-- Migration: 2026_03_20_000017_create_notification_logs_table.php +CREATE TABLE notification_logs ( + id CHAR(36) NOT NULL PRIMARY KEY, + tenant_id CHAR(36) NOT NULL, + channel VARCHAR(50) NOT NULL, + template_key VARCHAR(100) NOT NULL, + recipient VARCHAR(255) NOT NULL, + status VARCHAR(50) NOT NULL DEFAULT 'planned', + sent_at DATETIME NULL, + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + FOREIGN KEY (tenant_id) REFERENCES tenants(id) +); + +COMMIT TRANSACTION; diff --git a/saas-app/public/index.php b/saas-app/public/index.php index a58199e..ee4a36a 100644 --- a/saas-app/public/index.php +++ b/saas-app/public/index.php @@ -2,6 +2,8 @@ declare(strict_types=1); +$page = $_GET['page'] ?? 'home'; + $modules = [ [ 'title' => 'Dashboard', @@ -35,6 +37,34 @@ $modules = [ ], ]; +$installSteps = [ + '1. `php scripts/check-prerequisites.php` ausfuehren.', + '2. `php scripts/prepare-saas-env.php` fuer die lokale `.env` verwenden.', + '3. `saas-app\.env` mit echten DB-, Mail-, Tenancy- und OIDC-Werten fuellen.', + '4. `php scripts/install-saas.php` fuer die SQL-Bundle-Erzeugung verwenden.', + '5. Optional `php scripts/run-sql-migrations.php --server= --database= --username= --password=` fuer die direkte SQL-Ausfuehrung nutzen.', + '6. Ersten Tenant, ersten Benutzer und erste Member-Zuordnung anlegen.', +]; + +$migrationSteps = [ + 'Die Dateien unter `saas-app/database/migrations/*.php` sind keine Laravel-Migrationsklassen, sondern SQL-Skizzen.', + 'Das Bundle wird ueber `php scripts/build-migration-bundle.php` erzeugt.', + 'Empfohlener Weg: `php scripts/install-saas.php`.', + 'Direkte SQL-Server-Ausfuehrung: `php scripts/run-sql-migrations.php --server= --database= --username= --password=`.', + 'Ohne `pdo_sqlsrv` die erzeugte SQL-Datei manuell in SQL Server Management Studio oder einem kompatiblen Tool ausfuehren.', +]; + +function renderList(array $items): void +{ + echo '
    '; + + foreach ($items as $item) { + echo '
  • ' . htmlspecialchars($item, ENT_QUOTES) . '
  • '; + } + + echo '
'; +} + ?> @@ -48,7 +78,7 @@ $modules = [ --muted: #6a5649; --brand: #0f766e; --accent: #b45309; - --card: rgba(255, 252, 247, 0.88); + --card: rgba(255, 252, 247, 0.9); --line: rgba(36, 23, 15, 0.12); --shadow: 0 24px 60px rgba(61, 38, 24, 0.12); --radius: 28px; @@ -71,6 +101,7 @@ $modules = [ margin: 24px auto 40px; } + .nav, .hero, .card { border: 1px solid var(--line); @@ -79,6 +110,38 @@ $modules = [ box-shadow: var(--shadow); } + .nav { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; + justify-content: space-between; + padding: 18px 22px; + margin-bottom: 18px; + } + + .nav-links { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + + .nav a, + .button { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + padding: 11px 16px; + text-decoration: none; + font-weight: 700; + } + + .nav a { + color: var(--brand); + background: rgba(15, 118, 110, 0.08); + } + .hero { padding: 34px; background: @@ -98,11 +161,11 @@ $modules = [ h1, h2 { margin: 0 0 14px; - font-family: "Georgia", serif; + font-family: Georgia, serif; line-height: 1.04; } - h1 { font-size: clamp(2.2rem, 4vw, 4rem); } + h1 { font-size: clamp(2.1rem, 4vw, 3.8rem); } h2 { font-size: 1.35rem; } p { @@ -111,6 +174,14 @@ $modules = [ line-height: 1.65; } + code { + padding: 2px 6px; + border-radius: 8px; + background: rgba(15, 118, 110, 0.08); + color: var(--brand); + font-family: Consolas, monospace; + } + .actions, .meta { display: flex; @@ -119,17 +190,6 @@ $modules = [ margin-top: 22px; } - .button, - .pill { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 999px; - padding: 12px 18px; - text-decoration: none; - font-weight: 700; - } - .button { background: linear-gradient(135deg, var(--brand), #115e59); color: #fff; @@ -142,10 +202,14 @@ $modules = [ } .pill { + display: inline-flex; + align-items: center; + border-radius: 999px; padding: 9px 14px; background: rgba(15, 118, 110, 0.08); color: var(--brand); font-size: 0.9rem; + font-weight: 700; } .grid { @@ -155,6 +219,13 @@ $modules = [ grid-template-columns: repeat(3, minmax(0, 1fr)); } + .grid-2 { + display: grid; + gap: 18px; + margin-top: 22px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .card { padding: 22px; } @@ -175,6 +246,17 @@ $modules = [ border: 1px solid rgba(15, 118, 110, 0.16); } + .list { + margin: 18px 0 0; + padding-left: 18px; + color: var(--muted); + line-height: 1.7; + } + + .list li + li { + margin-top: 10px; + } + .footer { margin-top: 18px; text-align: center; @@ -183,52 +265,125 @@ $modules = [ } @media (max-width: 900px) { - .grid { + .grid, + .grid-2 { grid-template-columns: 1fr; } .hero { padding: 24px; } + + .nav { + align-items: flex-start; + flex-direction: column; + } }
-
-
Kaffeeliste SaaS Preview
-

Der Root ist jetzt SaaS-first aufgebaut.

-

- Diese Preview-Seite markiert den neuen Document-Root unter `saas-app/public/`. - Der Legacy-Bestand wurde nach `legacy-app/` verschoben, die Zielarchitektur - und die neu gestalteten Produktseiten liegen unter `saas-app/`. -

- -
- Mandantenfaehig - Webspace-tauglich - Legacy archiviert -
-
- Die eigentliche Runtime bleibt der naechste Schritt nach Composer-Bootstrap. - Layout, Module, Dokumentation und Hosting-Zielbild sind bereits auf die - SaaS-Zielstruktur umgestellt. + + + +
+
Installation
+

Die Installationsschritte laufen jetzt ueber interne Preview-Seiten.

+

+ Der Link zeigt nicht mehr auf docs/installationshandbuch.md, + sondern auf eine im Public-Bereich erreichbare Anleitung. Das ist noetig, + weil Dateien ausserhalb von saas-app/public/ im Browser nicht + direkt verlinkt werden sollten. +

+ + +
+ +
+
Migrationen
+

Die Migrationen werden ueber ein SQL-Bundle erzeugt und ausgefuehrt.

+

+ Aktuell gibt es hier keine lauffaehige Laravel-artisan migrate-Strecke. + Die Migrationsdateien liefern SQL und werden deshalb ueber Skripte zu + einer ausfuehrbaren Datei zusammengefuehrt. +

+ + +
+ Ergebnisdatei: saas-app/database/migrations/generated/all-migrations.sql +
+
+ +
+
Kaffeeliste SaaS Preview
+

Der Root ist jetzt SaaS-first aufgebaut.

+

+ Diese Preview-Seite markiert den neuen Document-Root unter + saas-app/public/. Der Legacy-Bestand wurde nach + legacy-app/ verschoben, die Zielarchitektur und die neu + gestalteten Produktseiten liegen unter saas-app/. +

+ +
+ Mandantenfaehig + Webspace-tauglich + Legacy archiviert +
+
+ Die eigentliche Runtime bleibt der naechste Schritt nach Composer-Bootstrap. + Layout, Module, Dokumentation und Hosting-Zielbild sind bereits auf die + SaaS-Zielstruktur umgestellt. +
+
+ +
+ +
+

+

+
+ +
+ + +
+
+

Verfuegbare Skripte

+
    +
  • scripts/check-prerequisites.php
  • +
  • scripts/prepare-saas-env.php
  • +
  • scripts/install-saas.php
  • +
  • scripts/build-migration-bundle.php
  • +
  • scripts/run-sql-migrations.php
  • +
+
+
+

Technischer Stand

+
    +
  • Composer ist fuer den finalen Bootstrap weiterhin erforderlich.
  • +
  • Die Migrationen sind aktuell SQL-basiert, nicht artisan-basiert.
  • +
  • Fuer automatische Ausfuehrung wird die PHP-Erweiterung pdo_sqlsrv benoetigt.
  • +
+
-
- -
-

-

-
- -
- - +
diff --git a/scripts/build-migration-bundle.php b/scripts/build-migration-bundle.php new file mode 100644 index 0000000..10e3185 --- /dev/null +++ b/scripts/build-migration-bundle.php @@ -0,0 +1,55 @@ + PDO::ERRMODE_EXCEPTION, + ]); + + $pdo->beginTransaction(); + + foreach ($files as $file) { + $sql = require $file; + + if (!is_string($sql) || trim($sql) === '') { + throw new RuntimeException('Ungueltige Migration: ' . basename($file)); + } + + scripts_stdout('Fuehre aus: ' . basename($file)); + $pdo->exec($sql); + } + + $pdo->commit(); + scripts_stdout('Migrationen wurden erfolgreich ausgefuehrt.'); +} catch (Throwable $exception) { + if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) { + $pdo->rollBack(); + } + + scripts_stderr('Migration fehlgeschlagen: ' . $exception->getMessage()); +} diff --git a/scripts/support.php b/scripts/support.php new file mode 100644 index 0000000..d74552a --- /dev/null +++ b/scripts/support.php @@ -0,0 +1,90 @@ +NUL' : ' 2>/dev/null'; + @exec($where . ' ' . escapeshellarg($command) . $redirect, $output, $exitCode); + + return $exitCode === 0; +} + +function scripts_parse_options(array $argv): array +{ + $options = []; + + foreach (array_slice($argv, 1) as $arg) { + if (!str_starts_with($arg, '--')) { + continue; + } + + $arg = substr($arg, 2); + + if (str_contains($arg, '=')) { + [$key, $value] = explode('=', $arg, 2); + $options[$key] = $value; + continue; + } + + $options[$arg] = true; + } + + return $options; +} + +function scripts_read_env_file(string $path): array +{ + if (!is_file($path)) { + return []; + } + + $values = []; + + foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) { + $trimmed = trim($line); + + if ($trimmed === '' || str_starts_with($trimmed, '#') || !str_contains($trimmed, '=')) { + continue; + } + + [$key, $value] = explode('=', $trimmed, 2); + $values[trim($key)] = trim($value, " \t\n\r\0\x0B\""); + } + + return $values; +} + +function scripts_stdout(string $message): void +{ + fwrite(STDOUT, $message . PHP_EOL); +} + +function scripts_stderr(string $message, int $exitCode = 1): never +{ + fwrite(STDERR, $message . PHP_EOL); + exit($exitCode); +}