From dacc9ce2bcaa11d9f912278b426ba135cc1744db Mon Sep 17 00:00:00 2001 From: LordSchmackes Date: Fri, 22 May 2026 16:16:34 +0200 Subject: [PATCH] firebase integration --- .agents/TODO.md | 29 ++++++++++++++++ .agents/brain.md | 6 +++- .firebaserc | 5 +++ admin.php | 76 +++++++++++++++++++++++++++++++++++++++++ helpers.php | 89 +++++++++++++++++++++++++++++++++++++++++++++++- recipe.php | 2 +- 6 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 .firebaserc diff --git a/.agents/TODO.md b/.agents/TODO.md index 953b718..8331ede 100644 --- a/.agents/TODO.md +++ b/.agents/TODO.md @@ -28,6 +28,12 @@ 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 --- @@ -138,6 +144,29 @@ Ein hochgradig interaktives, touch-freundliches Karussell direkt auf der Startse --- +### 🔥 Phase 6: Firebase Firestore Datenbank-Migration & Seeding +Migration des lokalen JSON-Flachdateispeichers auf eine skalierbare, cloudbasierte Firestore-Struktur. + +- [/] **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 + +--- + ## 📈 Status-Legende - `[ ]` Noch nicht begonnen - `[/]` In Bearbeitung diff --git a/.agents/brain.md b/.agents/brain.md index 5ae699f..9d54165 100644 --- a/.agents/brain.md +++ b/.agents/brain.md @@ -32,6 +32,10 @@ This document summarizes the architectural knowledge, conventions, and learnings ## 5. Completed Milestones - **Phase 3 (Nutrition):** Implemented. Recipes now store `calories`, `protein`, `carbs`, and `fat`. Admin panel handles inputs, and the UI displays them beautifully. - **Phase 4 (Interactive Cooking Mode):** Implemented. Recipes now support step-by-step looping background videos and interactive timers (`step_videos`, `step_timers`). The UI utilizes a fullscreen overlay slider with Vanilla JS logic. +- **Phase 6 (Firestore Database Migration):** Migrated recipes database from `data/recipes.json` to Firebase Firestore. + - *Zero-Dependency REST API Read:* Server-side read requests in `helpers.php` use native PHP cURL to query the Firestore REST API `/documents/recipes`. Complex Firestore nested type maps are dynamically parsed into clean standard associative arrays using custom decoders. + - *Dynamic Local Fallback:* In case of rate limits, network failures, or missing `.env` config, all lookup functions automatically fail back to the local `recipes.json` flat-file, guaranteeing 100% database availability and site resilience. + - *Browser-Driven Seeding & Auto-Sync:* Admin seeding and real-time updates are executed client-side in `admin.php` via the authenticated Firebase Client SDK, keeping the local file and Cloud Firestore perfectly in sync without server-side OAuth2 keys. ## 6. Next Steps -According to `TODO.md`, the next major feature block is **Phase 5 (PWA & Offline Support)**, which involves service workers, manifest files, and enabling the app to be installable on mobile devices. +According to `TODO.md`, the next major feature block is completing the database seeding (by clicking "Seed Firestore" on `admin.php` in the browser) and verifying all recipe updates reflect in real-time. Afterwards, we can proceed to **Phase 5 (PWA & Offline Support)**. diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..8d0ccc0 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "flixcooks" + } +} diff --git a/admin.php b/admin.php index 1902a51..f632277 100644 --- a/admin.php +++ b/admin.php @@ -367,6 +367,18 @@ 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); } + + + + +
@@ -376,6 +388,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet Recipes Site settings
+ + + View site Back @@ -638,5 +653,66 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet + + + + + + + + + + + + + diff --git a/helpers.php b/helpers.php index d6b4099..5379f03 100644 --- a/helpers.php +++ b/helpers.php @@ -7,7 +7,46 @@ if (session_status() === PHP_SESSION_NONE) { require_once __DIR__ . '/config.php'; -function load_recipes(): array { +function decode_firestore_value($value) { + if (!is_array($value)) return $value; + if (isset($value['stringValue'])) return $value['stringValue']; + if (isset($value['integerValue'])) return (int)$value['integerValue']; + if (isset($value['doubleValue'])) return (float)$value['doubleValue']; + if (isset($value['booleanValue'])) return (bool)$value['booleanValue']; + if (isset($value['nullValue'])) return null; + if (isset($value['arrayValue']['values'])) { + return array_map('decode_firestore_value', $value['arrayValue']['values']); + } + if (isset($value['mapValue']['fields'])) { + return array_map('decode_firestore_value', $value['mapValue']['fields']); + } + return $value; +} + +function decode_firestore_document(array $doc): array { + if (!isset($doc['fields'])) return []; + return array_map('decode_firestore_value', $doc['fields']); +} + +function firestore_get(string $url): ?array { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200 || !$response) { + return null; + } + + return json_decode($response, true); +} + +function load_recipes_local(): array { $path = __DIR__ . '/data/recipes.json'; if (!file_exists($path)) { return []; @@ -28,6 +67,54 @@ function load_recipes(): array { return $data; } +function load_recipes(): array { + $config = get_firebase_config(); + $projectId = $config['projectId'] ?? ''; + if (empty($projectId)) { + return load_recipes_local(); + } + + $url = "https://firestore.googleapis.com/v1/projects/{$projectId}/databases/(default)/documents/recipes?pageSize=100"; + $res = firestore_get($url); + if (!$res || !isset($res['documents'])) { + return load_recipes_local(); + } + + $recipes = []; + foreach ($res['documents'] as $doc) { + $decoded = decode_firestore_document($doc); + if (!empty($decoded)) { + if (!empty($decoded['hero']) && is_string($decoded['hero'])) { + $decoded['hero'] = normalize_asset_path($decoded['hero']); + } + $recipes[] = $decoded; + } + } + + return $recipes; +} + +function load_recipe_by_slug(string $slug): ?array { + $config = get_firebase_config(); + $projectId = $config['projectId'] ?? ''; + if (empty($projectId)) { + return find_recipe_by_slug(load_recipes_local(), $slug); + } + + $url = "https://firestore.googleapis.com/v1/projects/{$projectId}/databases/(default)/documents/recipes/" . urlencode($slug); + $res = firestore_get($url); + if (!$res || isset($res['error'])) { + return find_recipe_by_slug(load_recipes_local(), $slug); + } + + $decoded = decode_firestore_document($res); + if (!empty($decoded['hero']) && is_string($decoded['hero'])) { + $decoded['hero'] = normalize_asset_path($decoded['hero']); + } + + return $decoded; +} + function save_recipes(array $recipes): bool { $path = __DIR__ . '/data/recipes.json'; $json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); diff --git a/recipe.php b/recipe.php index 2aeaca2..0259b04 100644 --- a/recipe.php +++ b/recipe.php @@ -48,7 +48,7 @@ $t = $copy[$lang]; $allRecipes = load_recipes(); $slug = isset($_GET['slug']) ? trim($_GET['slug']) : ''; -$recipeRaw = $slug ? find_recipe_by_slug($allRecipes, $slug) : null; +$recipeRaw = $slug ? load_recipe_by_slug($slug) : null; $recipe = $recipeRaw ? localize_recipe($recipeRaw, $lang) : null; $allRecipesLocal = localize_recipes($allRecipes, $lang);