Revert "firebase integration"

This reverts commit dacc9ce2bc.
This commit is contained in:
2026-05-22 19:15:59 +02:00
parent dacc9ce2bc
commit 4c9a149325
6 changed files with 3 additions and 204 deletions
-29
View File
@@ -28,12 +28,6 @@ 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
---
@@ -144,29 +138,6 @@ 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
+1 -5
View File
@@ -32,10 +32,6 @@ 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 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)**.
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.
-5
View File
@@ -1,5 +0,0 @@
{
"projects": {
"default": "flixcooks"
}
}
-76
View File
@@ -367,18 +367,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); }
</style>
<!-- Firebase v10 compat SDKs -->
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-auth-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore-compat.js"></script>
<script>
(function() {
var config = <?php echo json_encode(get_firebase_config()); ?>;
firebase.initializeApp(config);
window.auth = firebase.auth();
window.db = firebase.firestore();
})();
</script>
</head>
<body>
<div class="admin-header">
@@ -388,9 +376,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet
<a href="/admin.php" class="<?php echo $view === 'recipes' ? 'active' : ''; ?>">Recipes</a>
<a href="/admin.php?view=settings" class="<?php echo $view === 'settings' ? 'active' : ''; ?>">Site settings</a>
</div>
<?php if ($view === 'recipes' && !$editing): ?>
<button id="btn-seed-firestore" class="button" style="background: var(--accent-gold, #c5a880); color: #000; font-weight: 700; border: none; padding: 10px 14px; border-radius: 10px; cursor: pointer;">Seed Firestore</button>
<?php endif; ?>
<a class="button" href="/index.php" style="padding:10px 14px;">View site</a>
<?php if ($editing): ?>
<a class="button" href="/admin.php" style="padding:10px 14px;">Back</a>
@@ -653,66 +638,5 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delet
<button type="submit" class="button" style="width: fit-content;"><?php echo $editing ? 'Save changes' : 'Save recipe'; ?></button>
</form>
<?php endif; ?>
<script>
// Firestore Seeder Logic
document.addEventListener('DOMContentLoaded', () => {
const btnSeed = document.getElementById('btn-seed-firestore');
if (btnSeed) {
btnSeed.addEventListener('click', async () => {
if (!confirm('Bist du sicher, dass du alle Rezepte nach Firestore importieren möchtest? Bestehende Dokumente mit identischem Slug werden überschrieben.')) return;
btnSeed.disabled = true;
btnSeed.innerText = 'Seeding...';
try {
const recipes = <?php echo json_encode($allRecipes); ?>;
let count = 0;
for (const recipe of recipes) {
await window.db.collection('recipes').doc(recipe.slug).set(recipe);
console.log('Seeded to Firestore:', recipe.slug);
count++;
}
alert(`Erfolgreich ${count} Rezepte nach Firestore migriert!`);
} catch (err) {
console.error('Seeding Fehler:', err);
alert('Fehler beim Seeding: ' + err.message);
} finally {
btnSeed.disabled = false;
btnSeed.innerText = 'Seed Firestore';
}
});
}
});
</script>
<!-- Auto-Sync after PHP Save -->
<?php if (isset($record) && str_contains($message ?? '', 'Saved')): ?>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const recipe = <?php echo json_encode($record); ?>;
try {
await window.db.collection('recipes').doc(recipe.slug).set(recipe);
console.log('Successfully synced saved recipe to Firestore:', recipe.slug);
} catch (err) {
console.error('Error syncing saved recipe to Firestore:', err);
}
});
</script>
<?php endif; ?>
<!-- Auto-Sync after PHP Delete -->
<?php if (isset($slugDel) && ($message ?? '') === 'Recipe deleted.'): ?>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const slugDel = <?php echo json_encode($slugDel); ?>;
try {
await window.db.collection('recipes').doc(slugDel).delete();
console.log('Successfully deleted recipe from Firestore:', slugDel);
} catch (err) {
console.error('Error deleting recipe from Firestore:', err);
}
});
</script>
<?php endif; ?>
</body>
</html>
+1 -88
View File
@@ -7,46 +7,7 @@ if (session_status() === PHP_SESSION_NONE) {
require_once __DIR__ . '/config.php';
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 {
function load_recipes(): array {
$path = __DIR__ . '/data/recipes.json';
if (!file_exists($path)) {
return [];
@@ -67,54 +28,6 @@ function load_recipes_local(): 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);
+1 -1
View File
@@ -48,7 +48,7 @@ $t = $copy[$lang];
$allRecipes = load_recipes();
$slug = isset($_GET['slug']) ? trim($_GET['slug']) : '';
$recipeRaw = $slug ? load_recipe_by_slug($slug) : null;
$recipeRaw = $slug ? find_recipe_by_slug($allRecipes, $slug) : null;
$recipe = $recipeRaw ? localize_recipe($recipeRaw, $lang) : null;
$allRecipesLocal = localize_recipes($allRecipes, $lang);