postgres database implementation
This commit is contained in:
+108
-88
@@ -7,54 +7,72 @@ 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']);
|
||||
function init_db() {
|
||||
$pdo = get_db_connection();
|
||||
if (!$pdo) return;
|
||||
|
||||
try {
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS recipes (
|
||||
slug VARCHAR(255) PRIMARY KEY,
|
||||
data JSONB NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
|
||||
$stmt = $pdo->query("SELECT COUNT(*) FROM recipes");
|
||||
if ($stmt && $stmt->fetchColumn() == 0) {
|
||||
$path = __DIR__ . '/data/recipes.json';
|
||||
if (file_exists($path)) {
|
||||
$json = file_get_contents($path);
|
||||
$data = json_decode($json, true);
|
||||
if (is_array($data) && count($data) > 0) {
|
||||
$insert = $pdo->prepare("INSERT INTO recipes (slug, data) VALUES (:slug, :data)");
|
||||
foreach ($data as $recipe) {
|
||||
if (isset($recipe['slug'])) {
|
||||
$insert->execute([
|
||||
'slug' => $recipe['slug'],
|
||||
'data' => json_encode($recipe, JSON_UNESCAPED_UNICODE)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("DB Init Error: " . $e->getMessage());
|
||||
}
|
||||
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 [];
|
||||
}
|
||||
$json = file_get_contents($path);
|
||||
$data = json_decode($json, true);
|
||||
if (!is_array($data)) {
|
||||
return [];
|
||||
$pdo = get_db_connection();
|
||||
$data = [];
|
||||
|
||||
if (!$pdo) {
|
||||
$path = __DIR__ . '/data/recipes.json';
|
||||
if (file_exists($path)) {
|
||||
$json = file_get_contents($path);
|
||||
$data = json_decode($json, true) ?: [];
|
||||
}
|
||||
} else {
|
||||
static $initialized = false;
|
||||
if (!$initialized) {
|
||||
init_db();
|
||||
$initialized = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT data FROM recipes");
|
||||
if ($stmt) {
|
||||
while ($row = $stmt->fetch()) {
|
||||
$recipe = json_decode($row['data'], true);
|
||||
if (is_array($recipe)) {
|
||||
$data[] = $recipe;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("Failed to load recipes from DB: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data as &$recipe) {
|
||||
@@ -68,57 +86,59 @@ function load_recipes_local(): array {
|
||||
}
|
||||
|
||||
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;
|
||||
return load_recipes_local();
|
||||
}
|
||||
|
||||
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;
|
||||
return find_recipe_by_slug(load_recipes_local(), $slug);
|
||||
}
|
||||
|
||||
function save_recipes(array $recipes): bool {
|
||||
$pdo = get_db_connection();
|
||||
$path = __DIR__ . '/data/recipes.json';
|
||||
$json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
return (bool) file_put_contents($path, $json);
|
||||
|
||||
if (!$pdo) {
|
||||
$json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
return (bool) file_put_contents($path, $json);
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
$slugs = [];
|
||||
$insert = $pdo->prepare("INSERT INTO recipes (slug, data) VALUES (:slug, :data) ON CONFLICT (slug) DO UPDATE SET data = EXCLUDED.data, updated_at = CURRENT_TIMESTAMP");
|
||||
|
||||
foreach ($recipes as $recipe) {
|
||||
if (isset($recipe['slug'])) {
|
||||
$slugs[] = $recipe['slug'];
|
||||
$insert->execute([
|
||||
'slug' => $recipe['slug'],
|
||||
'data' => json_encode($recipe, JSON_UNESCAPED_UNICODE)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($slugs)) {
|
||||
$placeholders = implode(',', array_fill(0, count($slugs), '?'));
|
||||
$delete = $pdo->prepare("DELETE FROM recipes WHERE slug NOT IN ($placeholders)");
|
||||
$delete->execute($slugs);
|
||||
} else {
|
||||
$pdo->exec("DELETE FROM recipes");
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
// Also update local JSON as backup
|
||||
$json = json_encode($recipes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
file_put_contents($path, $json);
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log("Failed to save recipes to DB: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user