diff --git a/.agents/TODO.md b/.agents/TODO.md index 8331ede..2e7c01c 100644 --- a/.agents/TODO.md +++ b/.agents/TODO.md @@ -12,10 +12,10 @@ Dieses Dokument enthält den aktuellen Entwicklungsstand und detaillierte Aufgab - Fullscreen Overlay-Navigation (`LiquidOverlayMenu` & `SlideUpTextHover`) - Geschmeidiges Scrollverhalten (`LenisSmoothScroll` & `IndexSectionIndicator`) - Layout-Raster (`FloemaLayoutGrid`) & Premium-Buttons -- [x] **Phase 2: Authentifizierung & Firebase Integration** - - Firebase-Projekt Setup & Anbindung - - Newsletter-System (Firebase Firestore) - - Benutzerprofile (Registrierung, Login, Favoriten) +- [x] **Phase 2: Profil & Personalisierung (lokal)** + - Ernährungsziel & Favoriten im Browser (`fc-local.js`) + - Newsletter per Mailto + - Profilseite `login.php` - [x] **Phase 3: Nährwerte & Rezepterweiterung** - Datenmodell & Admin.php Erweiterung - Visuelles Nährwert-Widget (Floema-Präzision) @@ -28,12 +28,10 @@ Dieses Dokument enthält den aktuellen Entwicklungsstand und detaillierte Aufgab - Touch-Swipe-Karussell direkt auf index.php (unter Landing-Page) - Filteralgorithmus nach Benutzer-Ernährungszielen - Mikro-Animationen & Quick-Save Funktion -- [/] **Phase 6: Firebase Firestore Datenbank-Migration & Seeding** - - Firebase CLI-Login & Projekt-Initialisierung - - .env-Konfiguration mit Firebase App Credentials - - Client-seitiger "Seed Firestore"-Button in admin.php - - Firestore REST API Integration in helpers.php - - Echtzeit-Zwei-Wege-Sync bei Rezeptänderungen in admin.php +- [x] **Phase 6: PostgreSQL Datenbank** + - `DATABASE_URL` in `.env`, Docker Compose für lokale Dev-DB + - `init_db()` / `save_recipes()` in `helpers.php` + - Fallback auf `data/recipes.json` --- @@ -66,26 +64,17 @@ Das visuelle Fundament der Website. Die Etablierung des Premium-Designs stellt s --- -### 🔒 Phase 2: Authentifizierung & Firebase Integration -Das Fundament für Personalisierung und Newsletter-Abos über eine sichere Firebase-Anbindung (Auth & Firestore). +### 🔒 Phase 2: Profil & Personalisierung (lokal) +Favoriten, Ernährungsziel und Newsletter ohne Cloud-Backend. -- [x] **Firebase Setup & Initialisierung** - - [x] Firebase SDK Client-Side Einbindung in `partials/head.php` or separaten Helper - - [x] Firebase Config in umweltabhängigen Settings/Variablen auslagern - - [x] Firestore-Datenbank initialisieren (`subscribers` und `users` Collections) -- [x] **Premium Login- & Registrierungsseite** - - [x] Erstellung der Seite `login.php` (und optional `signup.php`) im neuen Design-System-Stil - - [x] Firebase Authentication (Email/Passwort Login & Registrierung) implementieren - - [x] Responsive UI mit sanften Error- & Success-Meldungen und Eingabefeldern im Glasmorphismus-Look - - [x] Client-seitige und Server-seitige Session-Synchronisation (z.B. Firebase Session Tokens an PHP-Session via Ajax senden) +- [x] **Profilseite `login.php`** + - [x] Ernährungsziel wählen und in `localStorage` speichern (`fc-local.js`) + - [x] Gespeicherte Favoriten anzeigen - [x] **Favoriten-Funktion (Rezept-Bookmarks)** - - [x] Firestore-Collection `user_favorites` anlegen (Format: `userId` -> Liste von `recipeSlugs`) - - [x] "Rezept speichern" Button (Herz-Icon) auf den Rezeptkarten und Rezept-Details mit dynamischem Login-Check - - [x] Mikro-Animationen für das Herz-Icon (Scale up/down, Fülleffekte mit `var(--ease-overshoot)`) -- [x] **Firebase-gestütztes Newsletter-Abo** - - [x] Newsletter-Eingabefeld im Footer (`partials/footer.php`) implementieren - - [x] AJAX-Submit-Handler: E-Mail-Adresse prüfen und in Firestore Collection `subscribers` speichern - - [x] Elegantes Feedback-UI (Erfolgsmeldung ohne Page-Reload) + - [x] Herz-Icon auf Karten, Speicherung in `localStorage` + - [x] Mikro-Animationen für das Herz-Icon +- [x] **Newsletter** + - [x] Footer-Formular mit `mailto:`-Weiterleitung --- @@ -112,7 +101,7 @@ Jedes Rezept erhält präzise Makronährstoffe (Kalorien, Proteine, Kohlenhydrat Ein immersiver Kochmodus, der Anwendern Schritt-für-Schritt durch die Zubereitung führt – inklusive Videoanleitungen und integrierten Timern. - [x] **Video-Verknüpfung im Admin-Panel** - - [x] Rezeptstruktur in `data/recipes.json` erweitern, damit jeder Arbeitsschritt (`steps`) eine optionale `video_url` (MP4 aus Firebase Storage oder extern) besitzen kann + - [x] Rezeptstruktur in `data/recipes.json` erweitern, damit jeder Arbeitsschritt (`steps`) eine optionale `video_url` (externe MP4-URL) besitzen kann - [x] `admin.php` erweitern, um Video-URLs pro Einzelschritt einzugeben - [x] **Fullscreen Cooking-Mode Overlay** - [x] Trigger-Button "Kochmodus starten" (`CapitoliumRevealButton` Stil) auf `recipe.php` einbauen @@ -134,36 +123,25 @@ Ein hochgradig interaktives, touch-freundliches Karussell direkt auf der Startse - [x] CSS-Klammern (`clamp()`) für responsive Skalierung der Karten - [x] Flüssiges Einblenden per Scroll-Reveal (`reveal-target`) mit Anbindung an `LenisSmoothScroll` - [x] **Personalisiertes Goal-Matching** - - [x] Falls der Benutzer eingeloggt ist, sein Ernährungsziel aus den Firebase-Profildaten abrufen + - [x] Ernährungsziel aus `localStorage` (`fc-local.js`) für Karussell-Filter - [x] Rezepte dynamisch filtern (z.B. High-Protein Rezepte bei Ziel "Muskelaufbau", Low-Carb Rezepte bei Ziel "Abnehmen") - [x] Personalisierte "Für dich empfohlen"-Badge auf den Karten anzeigen - [x] **Mikro-Interaktionen & Quick-Save** - [x] Touch-Gesten-Unterstützung (Wischen zum Blättern mit sanftem Trägheits-Feedback) - - [x] Quick-Save Button direkt auf den Karussell-Karten (Herzeffekt mit direktem Firebase Firestore Sync) + - [x] Quick-Save Button direkt auf den Karussell-Karten (Herzeffekt, `localStorage`) - [x] Hover-Reveal-Effekte für Rezeptdetails (Zutaten-Vorschau oder Kochzeit-Highlight schiebt sich weich hoch) --- -### 🔥 Phase 6: Firebase Firestore Datenbank-Migration & Seeding -Migration des lokalen JSON-Flachdateispeichers auf eine skalierbare, cloudbasierte Firestore-Struktur. +### 🔥 Phase 6: PostgreSQL Datenbank +Rezepte in Postgres (JSONB), optional lokal per Docker. -- [/] **Firebase-Projekt & CLI-Setup** - - [x] Firebase-Verzeichnis-Umgebung initialisieren und anbinden - - [/] Firebase CLI Login durchführen und aktiven Account verknüpfen - - [ ] Firebase Project & Web App ermitteln oder neu anlegen - - [ ] Lokale `.env`-Konfiguration mit Firebase SDK Parametern befüllen -- [ ] **Client-seitiges Daten-Seeding** - - [ ] "Seed Firestore"-Aktion im Admin-Bereich (`admin.php`) einbauen - - [ ] Batch-Dokumentenupload der `recipes.json`-Einträge in die Firestore Collection `recipes` über JS SDK - - [ ] Firestore Security Rules für den öffentlichen Lesezugriff anpassen -- [ ] **Firestore Server-seitiger Abruf (PHP REST API)** - - [ ] Firestore REST API Parser-Helfer in `helpers.php` implementieren (Transformation von Firestore Key-Maps in Standard-Arrays) - - [ ] `load_recipes()` auf REST API GET `/recipes` umstellen - - [ ] Neue Funktion `load_recipe_by_slug($slug)` für gezielten Einzelabruf implementieren und in `recipe.php` integrieren -- [ ] **Zwei-Wege-Echtzeit-Synchronisierung (Admin)** - - [ ] `save_recipes()` so anpassen, dass lokales JSON als Fallback-Backup erhalten bleibt - - [ ] Client-seitigen Trigger nach erfolgreichem PHP-Speichern in `admin.php` ausführen, um Änderungen sofort in Firestore zu spiegeln - - [ ] Funktionstest: Hinzufügen, Editieren und Löschen von Rezepten über die Admin-Konsole verifizieren +- [x] **`DATABASE_URL` & Docker Compose** + - [x] `docker-compose.dev.yml` für lokale Postgres-Instanz + - [x] `init_db()` legt Tabelle `recipes` an und seedet aus `recipes.json` +- [x] **PHP-Datenzugriff** + - [x] `load_recipes()` / `save_recipes()` mit JSON-Fallback + - [x] `load_recipe_by_slug()` in `recipe.php` --- diff --git a/.agents/brain.md b/.agents/brain.md index a31ba54..9011b1e 100644 --- a/.agents/brain.md +++ b/.agents/brain.md @@ -4,11 +4,11 @@ This document summarizes architectural knowledge, conventions, and learnings for ## 1. Project Architecture & Stack - **Backend:** Vanilla PHP. No heavy frameworks. -- **Database:** PostgreSQL (optional) via `DATABASE_URL` in `.env`, with automatic seed from and backup to `data/recipes.json`. All data access lives in `helpers.php`; connection logic in `config.php`. +- **Database:** PostgreSQL required (`DATABASE_URL` in `.env`). Normalized tables in `scripts/schema.sql`; `load_recipes()` / `save_recipe()` in `helpers.php`. No runtime JSON recipe file. One-time import: `php scripts/db-seed.php` from `data/recipes.json`. - **Site settings (legal pages):** Flat-file `data/site.json` via `load_site_settings()` / `save_site_settings()` — not in Postgres. - **Admin Panel (`admin.php`):** Lightweight CMS. Textareas use one line per array element (`ingredients`, `steps`, `step_videos`, `step_timers`). -- **Frontend:** Server-rendered PHP (`index.php`, `recipe.php`, …), Vanilla JS/CSS. Firebase compat SDKs in `partials/head.php` for auth, Firestore (newsletter/bookmarks where used). -- **Config:** `config.php` loads `.env`, exposes `get_firebase_config()`, `get_db_connection()`. Never commit `.env` (see `.gitignore`). +- **Frontend:** Server-rendered PHP (`index.php`, `recipe.php`, …), Vanilla JS/CSS. Profile/favorites via `assets/fc-local.js`. +- **Config:** `config.php` loads `.env`, `get_db_connection()`. Never commit `.env` (see `.gitignore`). ## 2. Design & Aesthetics - **CSS:** Custom properties (`var(--ease-out-expo)`, `var(--surface-1)`), glassmorphism, `FloemaLayoutGrid`. @@ -26,29 +26,27 @@ This document summarizes architectural knowledge, conventions, and learnings for ## 5. PostgreSQL — Schema & Data Flow -### Table `recipes` (only app table today) -Created by `init_db()` in `helpers.php` if missing: +### Relational schema (`scripts/schema.sql`) +Created by `ensure_recipe_schema()` on first DB use. Legacy JSONB `recipes.data` is migrated once automatically. -| Column | Type | Role | -|-------------|-------------|------| -| `slug` | `VARCHAR(255)` PRIMARY KEY | Stable recipe ID (URLs: `recipe.php?slug=…`) | -| `data` | `JSONB` NOT NULL | **Entire recipe document** (title, i18n, ingredients, steps, nutrition, …) | -| `created_at`| `TIMESTAMP` | Auto on insert | -| `updated_at`| `TIMESTAMP` | Set on `ON CONFLICT` update in `save_recipes()` | +| Table | Role | +|-------|------| +| `recipes` | slug, hero, times, servings, nutrition columns, featured, coming_soon | +| `recipe_translations` | title, description, category, difficulty (en/de) | +| `recipe_tags`, `recipe_ingredients`, `recipe_utensils`, `recipe_steps` | Ordered lists per language | -**Design choice:** Document-in-a-row (JSONB), not normalized columns. PHP already works with JSON arrays; avoids schema migrations for every new recipe field. PostgreSQL can still query inside JSON (`data->'i18n'->'en'->>'title'`). +PHP still exposes the same nested arrays (`i18n`, `nutrition`, …) via `hydrate_recipes_from_db()`. ### Runtime flow -1. `get_db_connection()` in `config.php` parses `DATABASE_URL` → PDO `pgsql:` DSN. -2. If no URL or connection fails → `load_recipes_local()` reads `data/recipes.json` only. -3. If connected → `init_db()` once per request (static flag): `CREATE TABLE IF NOT EXISTS`, then if `COUNT(*) = 0` → seed all rows from `data/recipes.json`. -4. `load_recipes()` / `load_recipe_by_slug()` decode `data` JSONB to PHP arrays. -5. `save_recipes()` (admin): upsert all recipes in Postgres **and** write `data/recipes.json` as backup. +1. `DATABASE_URL` required → `require_database()` or HTTP 503 (`maintenance/db-unavailable.php`). +2. `load_recipes()` → SQL → PHP arrays for templates. +3. Admin: `save_recipe()`, `delete_recipe()`, `clear_featured_recipes()`. +4. One-time import: `php scripts/db-seed.php` from `data/recipes.json` (not read at runtime). ### `config.php` functions -- `load_env()` — parses `.env` into `getenv()` / `$_ENV` / `$_SERVER`. -- `get_firebase_config()` — returns Firebase web config array from `FIREBASE_*` env vars. **Required** by `partials/head.php` and `admin.php`. Was accidentally removed in postgres commit `e7f35d7`; restored (undefined function caused HTTP 500). -- `get_db_connection()` — returns `PDO` or `null`; logs failures, does not throw. +- `load_env()` — parses `.env`. +- `get_db_connection()` — PDO or `null`. +- `DatabaseUnavailableException` — thrown when DB is required but missing. --- @@ -79,7 +77,7 @@ docker exec -it flixcooks-postgres-dev psql -U flixcooks -d flixcooks_dev ``` ### PHP requirements (WSL/Linux) -- Extension **`php-pgsql`** (or `php8.5-pgsql`) required; without it: log `could not find driver`, fallback to JSON. +- Extension **`php-pgsql`** (or `php8.5-pgsql`) required; without it the site cannot connect. - Install interactively: `sudo apt install php8.5-pgsql` (sudo in non-interactive agent shells may timeout). - Verify: `php -m | grep pgsql` → expect `pdo_pgsql`, `pgsql`. @@ -92,14 +90,12 @@ php scripts/db-check.php # after .env + pgsql OK ### `.env` rules for agents - Copy from `.env.example`; never commit `.env`. - **Local:** use `127.0.0.1` Docker URL above. -- **Railway production:** `postgres.railway.internal` only works **inside** Railway network — not from local WSL. For local access to hosted DB use Railway **public** proxy URL from dashboard, or prefer Docker for dev. -- **Firebase:** `FIREBASE_API_KEY`, `FIREBASE_AUTH_DOMAIN`, `FIREBASE_PROJECT_ID`, `FIREBASE_STORAGE_BUCKET`, `FIREBASE_MESSAGING_SENDER_ID`, `FIREBASE_APP_ID` — empty values break client `firebase.initializeApp()` in browser. -- Omit `DATABASE_URL` entirely to force JSON-only mode (UI work without Postgres). +- **`DATABASE_URL` is mandatory** for recipe pages; omitting it shows the DB unavailable page. ### Environment separation (important) - One database per environment (local Docker / staging / production). - Never point a dev branch `.env` at production Postgres. -- Export/import between envs: `pg_dump` / `psql` when needed; document URLs in platform secrets (Railway variables), not in repo. +- Export/import between envs: `pg_dump` / `psql` when needed; document URLs in hosting secrets, not in repo. --- @@ -107,21 +103,18 @@ php scripts/db-check.php # after .env + pgsql OK | Symptom | Cause | Fix | |--------|--------|-----| -| HTTP 500, `Call to undefined function get_firebase_config()` | Function missing from `config.php` | Ensure `get_firebase_config()` exists in `config.php` | +| HTTP 503, database unavailable | No `DATABASE_URL` or Postgres down | Fix `.env`, start Docker, `php scripts/db-check.php` | | Log: `could not find driver` | `php-pgsql` not installed | `sudo apt install php8.5-pgsql` | -| Log: connection failed, host `postgres.railway.internal` | Internal Railway hostname from local machine | Use Docker local URL or Railway public URL | -| Site loads, no recipes from DB | `DATABASE_URL` unset or DB empty and seed file missing | Set URL, ensure `data/recipes.json` exists, hit site or run `db-check.php` | -| Recipes work without Docker | Expected fallback | `load_recipes_local()` uses JSON when `get_db_connection()` is null | +| DB OK but 0 recipes | Empty tables | `php scripts/db-seed.php` | --- ## 8. Completed Milestones - **Phase 3 (Nutrition):** `calories`, `protein`, `carbs`, `fat` on recipes; admin + UI. - **Phase 4 (Cooking Mode):** `step_videos`, `step_timers`; fullscreen overlay. -- **Phase 6 (Postgres):** `recipes` JSONB table; seed from JSON; admin dual-write. +- **Phase 6 (Postgres):** Normalized SQL tables; `db-seed.php`; no runtime JSON recipes. - **Local dev Postgres:** `docker-compose.dev.yml`, README section, `scripts/db-check.php`, `.env.example` with `DATABASE_URL`. -- **Bugfix (May 2026):** Restored `get_firebase_config()` after postgres migration regression. -- **Local profile data:** Favorites/goals via `assets/fc-local.js` (`localStorage`). Newsletter may use `mailto:` or Firestore depending on page. +- **Local profile data:** Favorites/goals via `assets/fc-local.js` (`localStorage`). Newsletter via `mailto:`. ## 9. Next Steps See `.agents/TODO.md` (e.g. PWA & offline support). README `Local Postgres (Docker)` and `Postgres in diesem Projekt` sections mirror setup for humans. diff --git a/.env.example b/.env.example index b1d9ce7..744a47b 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # FlixCooks – lokale Entwicklung (.env wird nicht committed) # Kopieren: cp .env.example .env -# --- Postgres (docker-compose.dev.yml) --- -# Nur setzen, wenn du die lokale DB testen willst. Ohne DATABASE_URL → Fallback auf data/recipes.json +# --- Postgres (Pflicht für die Website) --- +# docker compose -f docker-compose.dev.yml up -d DATABASE_URL="postgresql://flixcooks:flixcooks_dev@127.0.0.1:5432/flixcooks_dev" diff --git a/.firebaserc b/.firebaserc deleted file mode 100644 index 8d0ccc0..0000000 --- a/.firebaserc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "projects": { - "default": "flixcooks" - } -} diff --git a/README.md b/README.md index 6b39884..291170f 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,11 @@ The project is architected to remain extremely lightweight and fast, intentional - `login.php`: Local profile page for dietary goals and saved favorites (browser storage). - **Support & Layouts**: - `partials/`: Contains modular templates (`head.php`, `header.php`, `footer.php`) to maintain a clean DRY structure. - - `helpers.php`: Core PHP utilities containing data formatting helpers and the data access layer for the flat-file database. + - `helpers.php`: Core PHP utilities and the PostgreSQL data access layer for recipes. - `config.php`: Environment-independent configuration loader which reads runtime secrets from `.env`. - `assets/fc-local.js`: Browser-side storage for favorites and dietary goals. - - `data/`: Houses `recipes.json`, our flat-file recipe database. + - `data/recipes.json`: Optional seed file only (`php scripts/db-seed.php`), not used at runtime. + - `scripts/schema.sql`: Relational table definitions for recipes. --- @@ -36,7 +37,7 @@ FlixCooks uses a modern, carefully curated vanilla tech-stack focused on lightni ### ⚙️ Backend & Data - **Engine**: Vanilla PHP. -- **Database**: PostgreSQL (production/staging) with automatic seed from `data/recipes.json`. Without `DATABASE_URL`, the app falls back to the JSON file. +- **Database**: PostgreSQL only. `DATABASE_URL` is required; without a working DB connection the site returns HTTP 503. - **Environment**: Custom `.env` variable parser integrated into PHP bootstrap. --- @@ -56,7 +57,8 @@ Make sure you have the following installed on your local machine: ```bash cp .env.example .env ``` -2. Open `.env` and optionally set **local Postgres** (see [Local Postgres (Docker)](#local-postgres-docker) below). +2. Set **`DATABASE_URL`** in `.env` (required). See [Local Postgres (Docker)](#local-postgres-docker) below. +3. Seed recipes once: `php scripts/db-seed.php` (imports `data/recipes.json` into SQL tables). ### 2. Local Postgres (Docker) @@ -99,14 +101,17 @@ SELECT slug, data->>'title' AS title FROM recipes, jsonb_to_record(data) AS x(ti \q -- beenden ``` -**DB komplett leeren und neu seeden** (lädt wieder aus `data/recipes.json` beim nächsten Seitenaufruf): +**DB komplett leeren und neu seeden:** ```bash docker compose -f docker-compose.dev.yml down -v docker compose -f docker-compose.dev.yml up -d +php scripts/db-seed.php ``` -Details zum Schema und Ablauf: Abschnitt unten in dieser README und `helpers.php` → `init_db()`. +**Verbindung prüfen:** `php scripts/db-check.php` + +Details zum Schema: Abschnitt unten und `scripts/schema.sql`. ### 3. Start the Development Server @@ -130,27 +135,29 @@ If you prefer running a full local stack: ## 🗄️ Postgres in diesem Projekt (Kurzüberblick) -FlixCooks nutzt **eine Tabelle** – kein klassisches „eine Spalte pro Rezeptfeld“-Schema: +Rezepte liegen in **normalisierten SQL-Tabellen** (kein JSONB-Blob, kein Laufzeit-Fallback auf Dateien): -| Spalte | Typ | Bedeutung | -|-------------|------------|-----------| -| `slug` | `VARCHAR` | Eindeutige ID des Rezepts (URL: `/recipe.php?slug=...`) | -| `data` | `JSONB` | **Gesamtes** Rezept als JSON (Titel, Zutaten, Schritte, i18n, …) | -| `created_at`| `TIMESTAMP`| Erstellzeit | -| `updated_at`| `TIMESTAMP`| Letzte Änderung (wird beim Update gesetzt) | +| Tabelle | Inhalt | +|---------|--------| +| `recipes` | Slug, Zeiten, Hero-URL, Nährwerte, `featured`, `coming_soon` | +| `recipe_translations` | Titel, Beschreibung, Kategorie, Schwierigkeit (EN/DE) | +| `recipe_tags` | Tags pro Sprache | +| `recipe_ingredients` | Zutatenzeilen | +| `recipe_utensils` | Werkzeugzeilen | +| `recipe_steps` | Schritte inkl. Video-URL und Timer | -**Warum JSONB?** Das Rezept ist in PHP/JSON ohnehin ein Objekt. Statt 20+ SQL-Spalten zu pflegen, speichert ihr ein Dokument pro Zeile. PostgreSQL kann in `JSONB` trotzdem indexieren und abfragen (`data->>'title'`), wenn ihr später filtern wollt. +Schema: `scripts/schema.sql`. PHP baut daraus dieselben Arrays wie früher (`i18n.en`, `nutrition`, …), damit Templates unverändert bleiben. -**Ablauf beim ersten Aufruf mit leerer DB:** +**Ablauf:** -1. `config.php` liest `DATABASE_URL` → PDO-Verbindung. -2. `init_db()` in `helpers.php` erstellt `recipes`, falls nicht vorhanden. -3. Ist die Tabelle leer → Import aus `data/recipes.json`. -4. `load_recipes()` liest alle Zeilen, dekodiert `data` zurück zu PHP-Arrays. +1. `DATABASE_URL` in `.env` → Verbindung über `config.php`. +2. Beim ersten Request: Tabellen anlegen (`ensure_recipe_schema()`). Alte JSONB-Tabelle wird einmalig migriert. +3. `load_recipes()` liest per SQL; ohne DB → HTTP 503 (`maintenance/db-unavailable.php`). +4. Admin: `save_recipe()` / `delete_recipe()` – direkt in die Tabellen. -**Admin speichern:** `save_recipes()` schreibt nach Postgres **und** aktualisiert `data/recipes.json` als Backup. +**Einmalig Daten laden:** `php scripts/db-seed.php` (aus `data/recipes.json`). -Für **Staging/Production** setzt du `DATABASE_URL` in der jeweiligen Hosting-Umgebung – nie Production-Daten in der lokalen Dev-DB mischen. +Für **Staging/Production** nur `DATABASE_URL` in der Hosting-Umgebung setzen – nie Production-Daten in der lokalen Dev-DB mischen. --- diff --git a/admin.php b/admin.php index f632277..36ae186 100644 --- a/admin.php +++ b/admin.php @@ -49,7 +49,11 @@ if (!$authed) { exit; } -$allRecipes = load_recipes(); +try { + $allRecipes = load_recipes(); +} catch (DatabaseUnavailableException $e) { + handle_database_unavailable($e); +} $siteSettings = load_site_settings(); $message = null; $errors = []; @@ -165,12 +169,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') { $slugDel = trim($_POST['slug'] ?? ''); - $before = count($allRecipes); - $allRecipes = array_values(array_filter($allRecipes, fn($r) => ($r['slug'] ?? '') !== $slugDel)); - if ($before !== count($allRecipes) && save_recipes($allRecipes)) { + if ($slugDel !== '' && delete_recipe($slugDel)) { $message = 'Recipe deleted.'; + $allRecipes = load_recipes(); } else { - $message = 'Could not delete. Check permissions.'; + $message = 'Could not delete recipe.'; } $editing = null; $editIndex = null; @@ -259,13 +262,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet } } - if ($featured) { - foreach ($allRecipes as &$r) { - $r['featured'] = false; - } - unset($r); - } - $record = [ 'slug' => $slug, 'hero' => $hero, @@ -309,16 +305,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet ], ]; - if ($slugOriginal && $editIndex !== null) { - $allRecipes[$editIndex] = $record; - } else { - $allRecipes[] = $record; + if ($featured) { + clear_featured_recipes(); + } + if ($slugOriginal && $slugOriginal !== $slug) { + delete_recipe($slugOriginal); } - if (save_recipes($allRecipes)) { + if (save_recipe($record)) { $message = $slugOriginal ? 'Saved changes.' : 'Saved! New recipe added.'; + $allRecipes = load_recipes(); } else { - $message = 'Could not save the file. Check permissions on data/recipes.json.'; + $message = 'Could not save to database.'; } } } @@ -367,18 +365,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet .list-item { padding: 12px 0; border-bottom: 1px solid rgba(255,255,255,0.08); display: flex; justify-content: space-between; align-items: center; } .pill { background: rgba(255,255,255,0.06); color: var(--ink); } - - - - -