Dev #9

Merged
LordSchmackes merged 13 commits from dev into main 2026-05-23 21:30:54 +00:00
LordSchmackes commented 2026-05-23 13:54:46 +00:00 (Migrated from github.com)
No description provided.
github-actions[bot] (Migrated from github.com) reviewed 2026-05-23 21:30:28 +00:00
github-actions[bot] (Migrated from github.com) left a comment

gemini-code-review-action comments

gemini-code-review-action comments
@@ -0,0 +14,4 @@
- "8080:80"
environment:
DATABASE_URL: postgresql://flixcooks:flixcooks_prod@postgres:5432/flixcooks
FLIXCOOKS_ADMIN_KEY: ${FLIXCOOKS_ADMIN_KEY:-change-me-in-production}
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

The default value change-me-in-production is a good security practice for development environments. However, ensure that this variable is always overridden in production environments. Consider adding a validation check or a more prominent warning if this default value is detected in a production context, perhaps during the application's startup.

The default value `change-me-in-production` is a good security practice for development environments. However, ensure that this variable is *always* overridden in production environments. Consider adding a validation check or a more prominent warning if this default value is detected in a production context, perhaps during the application's startup.
@@ -0,0 +18,4 @@
RUN_DB_SEED: ${RUN_DB_SEED:-false}
depends_on:
postgres:
condition: service_healthy
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This is good practice for ensuring the database is ready before the application starts. However, the healthcheck in the postgres service has interval: 5s and timeout: 5s. If the database takes slightly longer to become fully ready than these parameters allow, the web service might still fail to start. Consider increasing the interval or timeout slightly, or ensuring the pg_isready command is robust enough to handle initial startup states.

This is good practice for ensuring the database is ready before the application starts. However, the `healthcheck` in the `postgres` service has `interval: 5s` and `timeout: 5s`. If the database takes slightly longer to become fully ready than these parameters allow, the `web` service might still fail to start. Consider increasing the `interval` or `timeout` slightly, or ensuring the `pg_isready` command is robust enough to handle initial startup states.
@@ -0,0 +1,28 @@
#!/bin/bash
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

It's good practice to include set -euo pipefail in shell scripts to make them more robust by exiting on unbound variables (-u), non-zero exit codes (-e), and pipeline failures (-o pipefail). This is already present, which is excellent.

It's good practice to include `set -euo pipefail` in shell scripts to make them more robust by exiting on unbound variables (`-u`), non-zero exit codes (`-e`), and pipeline failures (`-o pipefail`). This is already present, which is excellent.
@@ -0,0 +5,4 @@
echo "[flixcooks] Waiting for database..."
TRIES=0
MAX_TRIES="${DB_WAIT_MAX_TRIES:-30}"
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Making the maximum number of retries configurable via an environment variable is a good flexible design. The default of 30 attempts (with a 2-second sleep) provides a generous 60-second wait, which is generally sufficient. Ensure this timeout is documented or understood by users deploying the application.

Making the maximum number of retries configurable via an environment variable is a good flexible design. The default of 30 attempts (with a 2-second sleep) provides a generous 60-second wait, which is generally sufficient. Ensure this timeout is documented or understood by users deploying the application.
@@ -0,0 +6,4 @@
require __DIR__ . '/config.php';
try {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Wrapping the load_recipes() call in a try...catch block is essential for handling potential DatabaseUnavailableException errors gracefully, especially on pages that are not intended to show a maintenance page.

Wrapping the `load_recipes()` call in a `try...catch` block is essential for handling potential `DatabaseUnavailableException` errors gracefully, especially on pages that are not intended to show a maintenance page.
@@ -12,0 +22,4 @@
$stmt->execute([$table, $column]);
return (bool) $stmt->fetchColumn();
}
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This is a concise and efficient way to check for the existence of a column in information_schema.columns.

This is a concise and efficient way to check for the existence of a column in `information_schema.columns`.
@@ -12,1 +25,4 @@
function apply_recipe_schema(PDO $pdo): void {
$path = __DIR__ . '/scripts/schema.sql';
if (!file_exists($path)) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Loading the schema from an external SQL file (scripts/schema.sql) is a good practice for maintainability and readability. Throwing a RuntimeException if the file is missing is appropriate.

Loading the schema from an external SQL file (`scripts/schema.sql`) is a good practice for maintainability and readability. Throwing a `RuntimeException` if the file is missing is appropriate.
@@ -18,0 +37,4 @@
'description' => '',
'category' => '',
'difficulty' => '',
'tags' => [],
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This helper function provides a clean default structure for internationalized recipe data, ensuring consistency.

This helper function provides a clean default structure for internationalized recipe data, ensuring consistency.
@@ -18,0 +40,4 @@
'tags' => [],
'ingredients' => [],
'utensils' => [],
'steps' => [],
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

The default value for 'description' is an empty string. While functional, for internationalization, it's often beneficial to provide a placeholder string like null or a specific marker (e.g., __('default_description')) that can be explicitly translated or identified as missing. This can help in debugging or ensuring all fields are eventually populated.

The default value for 'description' is an empty string. While functional, for internationalization, it's often beneficial to provide a placeholder string like `null` or a specific marker (e.g., `__('default_description')`) that can be explicitly translated or identified as missing. This can help in debugging or ensuring all fields are eventually populated.
@@ -18,0 +41,4 @@
'ingredients' => [],
'utensils' => [],
'steps' => [],
'step_videos' => [],
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Similar to the 'description', an empty string for 'category' might be better represented by null or a translatable placeholder to distinguish between an intentionally empty category and a missing one.

Similar to the 'description', an empty string for 'category' might be better represented by `null` or a translatable placeholder to distinguish between an intentionally empty category and a missing one.
@@ -18,0 +42,4 @@
'utensils' => [],
'steps' => [],
'step_videos' => [],
'step_timers' => [],
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Similar to 'description' and 'category', an empty string for 'difficulty' could be improved by using null or a translatable placeholder. This helps differentiate between an unset difficulty and a deliberately empty one.

Similar to 'description' and 'category', an empty string for 'difficulty' could be improved by using `null` or a translatable placeholder. This helps differentiate between an unset difficulty and a deliberately empty one.
@@ -18,0 +47,4 @@
}
function recipe_base_from_row(array $row): array {
return [
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

It's good that step_videos is initialized as an empty array. However, the save_recipe function inserts video_url from the input directly. If the input step_videos array contains null or empty strings, they will be inserted as such. Consider adding a trim or filter for empty strings here if they are not intended to be stored.

It's good that `step_videos` is initialized as an empty array. However, the `save_recipe` function inserts `video_url` from the input directly. If the input `step_videos` array contains `null` or empty strings, they will be inserted as such. Consider adding a trim or filter for empty strings here if they are not intended to be stored.
@@ -18,0 +48,4 @@
function recipe_base_from_row(array $row): array {
return [
'slug' => $row['slug'],
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Similar to step_videos, step_timers is initialized as an empty array. The save_recipe function converts timer raw values to int or null. If the input array contains non-numeric strings, they will result in null. Ensure that any non-numeric or empty string values are handled consistently, perhaps by filtering them out before insertion.

Similar to `step_videos`, `step_timers` is initialized as an empty array. The `save_recipe` function converts timer raw values to `int` or `null`. If the input array contains non-numeric strings, they will result in `null`. Ensure that any non-numeric or empty string values are handled consistently, perhaps by filtering them out before insertion.
@@ -18,0 +52,4 @@
'hero' => $row['hero'] ?? '',
'prep_time' => (int) ($row['prep_time'] ?? 0),
'cook_time' => (int) ($row['cook_time'] ?? 0),
'total_time' => (int) ($row['total_time'] ?? 0),
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This function correctly extracts base recipe data from a database row, handling potential missing keys with default values. The explicit type casting ((int), (bool)) is good for data integrity.

This function correctly extracts base recipe data from a database row, handling potential missing keys with default values. The explicit type casting (`(int)`, `(bool)`) is good for data integrity.
@@ -18,0 +75,4 @@
}
if (is_string($value)) {
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : null;
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This function attempts to decode legacy JSONB values. It handles arrays, strings, and objects that can be JSON encoded. However, it might be beneficial to add explicit handling or error logging for unexpected data types passed to $value to prevent potential TypeError or other runtime errors if the input is not as expected.

This function attempts to decode legacy JSONB values. It handles arrays, strings, and objects that can be JSON encoded. However, it might be beneficial to add explicit handling or error logging for unexpected data types passed to `$value` to prevent potential `TypeError` or other runtime errors if the input is not as expected.
@@ -18,0 +91,4 @@
$legacy = [];
$stmt = $pdo->query('SELECT slug, data FROM recipes');
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This check correctly identifies if the legacy 'data' column exists, preventing unnecessary migration steps. This is a robust way to handle schema evolution.

This check correctly identifies if the legacy 'data' column exists, preventing unnecessary migration steps. This is a robust way to handle schema evolution.
@@ -18,0 +104,4 @@
$pdo->exec('DROP TABLE IF EXISTS recipe_tags CASCADE');
$pdo->exec('DROP TABLE IF EXISTS recipe_translations CASCADE');
$pdo->exec('DROP TABLE IF EXISTS recipes CASCADE');
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Dropping existing tables before applying the schema is a common migration strategy. However, if this script is ever run on a production database with existing data, this will result in data loss. Ensure this is only intended for development/testing environments or that a proper migration system is in place for production.

Dropping existing tables before applying the schema is a common migration strategy. However, if this script is ever run on a production database with existing data, this will result in data loss. Ensure this is only intended for development/testing environments or that a proper migration system is in place for production.
@@ -18,0 +118,4 @@
return;
}
if (db_table_has_column($pdo, 'recipes', 'data') && !db_table_has_column($pdo, 'recipes', 'calories')) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

Using a static variable $ready to ensure schema application only runs once per request is an efficient optimization. This prevents redundant database operations.

Using a static variable `$ready` to ensure schema application only runs once per request is an efficient optimization. This prevents redundant database operations.
@@ -18,0 +134,4 @@
$message = getenv('DATABASE_URL')
? 'Database connection failed. Check DATABASE_URL and that Postgres is running.'
: 'DATABASE_URL is not set in .env.';
throw new DatabaseUnavailableException($message);
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:28 +00:00

This function is crucial for database connectivity and schema management. The fallback logic for DATABASE_URL being unset is good. Throwing a DatabaseUnavailableException is a clear way to signal a critical error.

This function is crucial for database connectivity and schema management. The fallback logic for `DATABASE_URL` being unset is good. Throwing a `DatabaseUnavailableException` is a clear way to signal a critical error.
@@ -18,0 +151,4 @@
'SELECT * FROM recipes ORDER BY featured DESC, updated_at DESC, slug ASC'
);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$recipes[$row['slug']] = recipe_base_from_row($row);
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This function orchestrates the loading of all recipe data from the database, including translations, tags, ingredients, etc. It's well-structured, though the multiple SELECT statements could potentially be optimized if performance becomes an issue with a very large number of recipes or related data.

This function orchestrates the loading of all recipe data from the database, including translations, tags, ingredients, etc. It's well-structured, though the multiple `SELECT` statements could potentially be optimized if performance becomes an issue with a very large number of recipes or related data.
@@ -35,0 +258,4 @@
try {
$pdo->beginTransaction();
if (!empty($recipe['featured'])) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This function handles saving a recipe to the database, including its translations and related items. It uses transactions for atomicity, which is excellent. However, the repeated calls to DELETE for all related tables before inserting new data can be inefficient for updates. If a recipe is updated frequently, consider an UPSERT approach or more targeted updates for related data rather than full deletes and re-inserts.

This function handles saving a recipe to the database, including its translations and related items. It uses transactions for atomicity, which is excellent. However, the repeated calls to `DELETE` for all related tables before inserting new data can be inefficient for updates. If a recipe is updated frequently, consider an `UPSERT` approach or more targeted updates for related data rather than full deletes and re-inserts.
@@ -35,0 +270,4 @@
:slug, :hero, :prep_time, :cook_time, :total_time, :servings,
:featured, :coming_soon, :calories, :protein, :carbs, :fat, NOW()
)
ON CONFLICT (slug) DO UPDATE SET
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Clearing all other featured recipes when a new one is marked as featured is a good logic for ensuring only one recipe is featured at a time. This prevents ambiguity.

Clearing all other featured recipes when a new one is marked as featured is a good logic for ensuring only one recipe is featured at a time. This prevents ambiguity.
@@ -35,0 +310,4 @@
);
$tagStmt = $pdo->prepare(
'INSERT INTO recipe_tags (recipe_slug, lang, tag, sort_order) VALUES (?, ?, ?, ?)'
);
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

These delete statements are executed for every save, even if the data hasn't changed. This can be inefficient. If the goal is to update, consider only deleting/inserting what has changed, or using ON CONFLICT clauses where applicable in the INSERT statements for related tables if the database supports it.

These delete statements are executed for every save, even if the data hasn't changed. This can be inefficient. If the goal is to update, consider only deleting/inserting what has changed, or using `ON CONFLICT` clauses where applicable in the `INSERT` statements for related tables if the database supports it.
@@ -35,0 +334,4 @@
]);
$tagOrder = 0;
foreach (array_values($block['tags'] ?? []) as $tag) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Hardcoding languages en and de might become problematic if more languages are added. It would be more maintainable to fetch the list of supported languages from a configuration or a dedicated table if the application grows.

Hardcoding languages `en` and `de` might become problematic if more languages are added. It would be more maintainable to fetch the list of supported languages from a configuration or a dedicated table if the application grows.
@@ -35,0 +379,4 @@
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log('save_recipe failed: ' . $e->getMessage());
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

The ternary operator ($timerRaw !== '' && is_numeric($timerRaw)) ? (int) $timerRaw : null; correctly handles non-numeric or empty timer values. This is good input sanitization.

The ternary operator `($timerRaw !== '' && is_numeric($timerRaw)) ? (int) $timerRaw : null;` correctly handles non-numeric or empty timer values. This is good input sanitization.
@@ -2,2 +2,4 @@
require __DIR__ . '/helpers.php';
try {
$allRecipes = load_recipes();
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Loading all recipes on every page request might be inefficient if the recipe list is very large and rarely changes. Consider caching the recipe data if performance becomes an issue, especially for pages like index.php and login.php where the full list might not be immediately necessary for rendering.

Loading all recipes on every page request might be inefficient if the recipe list is very large and rarely changes. Consider caching the recipe data if performance becomes an issue, especially for pages like `index.php` and `login.php` where the full list might not be immediately necessary for rendering.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This JavaScript function filters recipes based on user goals. The logic for filtering appears sound. However, hardcoding the goal names ('weight_loss', 'muscle_gain', 'healthy') and the associated tag/category keywords could be made more configurable or data-driven, especially if more goals are to be added.

This JavaScript function filters recipes based on user goals. The logic for filtering appears sound. However, hardcoding the goal names (`'weight_loss'`, `'muscle_gain'`, `'healthy'`) and the associated tag/category keywords could be made more configurable or data-driven, especially if more goals are to be added.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This fallback ensures that if filtering results in an empty list, the full recipe bank is shown. This is a good user experience to prevent a blank carousel.

This fallback ensures that if filtering results in an empty list, the full recipe bank is shown. This is a good user experience to prevent a blank carousel.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

The original code had Firebase auth logic. This new implementation seems to rely on window.fcLocal for local storage of goals and favorites. The removal of the Firebase auth observer is a significant architectural change. Ensure that the authentication mechanism and data persistence strategy (local vs. server-side) are clearly defined and intended.

The original code had Firebase auth logic. This new implementation seems to rely on `window.fcLocal` for local storage of goals and favorites. The removal of the Firebase auth observer is a significant architectural change. Ensure that the authentication mechanism and data persistence strategy (local vs. server-side) are clearly defined and intended.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Accessing window.fcLocal without a check could lead to a TypeError if fcLocal is not defined. The check window.fcLocal ? ... : '' correctly handles this potential issue.

Accessing `window.fcLocal` without a check could lead to a `TypeError` if `fcLocal` is not defined. The check `window.fcLocal ? ... : ''` correctly handles this potential issue.
@@ -46,9 +52,9 @@ $copy = [
'your_goal' => 'Dein Ernährungsziel',
'saved_recipes' => 'Deine gespeicherten Favoriten',
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This function maps internal goal identifiers to user-friendly labels. It's a clean way to handle translations and display logic.

This function maps internal goal identifiers to user-friendly labels. It's a clean way to handle translations and display logic.
@@ -519,3 +449,3 @@
}
function handleLogin(e) {
function handleSaveGoal(e) {
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This check ensures that favorite rendering only proceeds if window.fcLocal is available, preventing potential errors. This is good defensive programming.

This check ensures that favorite rendering only proceeds if `window.fcLocal` is available, preventing potential errors. This is good defensive programming.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This check prevents errors if window.fcLocal isn't defined, ensuring the script doesn't crash. It's good defensive programming.

This check prevents errors if `window.fcLocal` isn't defined, ensuring the script doesn't crash. It's good defensive programming.
@@ -544,1 +455,4 @@
window.fcLocal.setGoal(goal);
document.getElementById('profileGoalVal').textContent = goalLabel(goal);
showAlert(langText.goal_saved);
}
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This function dynamically renders the user's favorite recipes. It correctly handles cases where window.fcLocal is not available or when there are no favorites.

This function dynamically renders the user's favorite recipes. It correctly handles cases where `window.fcLocal` is not available or when there are no favorites.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Iterating through favorite slugs and then finding the corresponding recipe in recipeBank is a straightforward approach. If recipeBank becomes very large, this linear search (.find()) could become a performance bottleneck. Consider using a map or object for faster lookups if performance is critical.

Iterating through favorite slugs and then finding the corresponding recipe in `recipeBank` is a straightforward approach. If `recipeBank` becomes very large, this linear search (`.find()`) could become a performance bottleneck. Consider using a map or object for faster lookups if performance is critical.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This DOMContentLoaded listener initializes the UI based on local storage. It correctly checks for window.fcLocal before accessing its methods. The logic for updating the goal select box and rendering favorites seems sound.

This `DOMContentLoaded` listener initializes the UI based on local storage. It correctly checks for `window.fcLocal` before accessing its methods. The logic for updating the goal select box and rendering favorites seems sound.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

This line assumes window.fcLocal is always defined. Similar to the index.php script, it would be safer to use const goal = window.fcLocal ? window.fcLocal.getGoal() : ''; to handle cases where fcLocal might not be initialized.

This line assumes `window.fcLocal` is always defined. Similar to the `index.php` script, it would be safer to use `const goal = window.fcLocal ? window.fcLocal.getGoal() : '';` to handle cases where `fcLocal` might not be initialized.
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

It's good to select the element before trying to set its value. The if (goal && select) check correctly handles cases where either the goal isn't set in local storage or the element doesn't exist on the page.

It's good to select the element before trying to set its value. The `if (goal && select)` check correctly handles cases where either the goal isn't set in local storage or the element doesn't exist on the page.
@@ -0,0 +4,4 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlixCooks Datenbank nicht verfügbar</title>
<link rel="stylesheet" href="/assets/style.css">
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Including a general stylesheet like /assets/style.css on a maintenance page is good for consistent branding and layout. Ensure this file is available and correctly linked even when the main application might be having issues.

Including a general stylesheet like `/assets/style.css` on a maintenance page is good for consistent branding and layout. Ensure this file is available and correctly linked even when the main application might be having issues.
@@ -0,0 +37,4 @@
<div class="db-error-card">
<h1>Datenbank nicht verfügbar</h1>
<p>FlixCooks benötigt eine laufende PostgreSQL-Verbindung. Ohne Datenbank werden keine Rezepte angezeigt.</p>
<?php if (!empty($dbError)): ?>
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Displaying the specific $dbError message within a <code> block is excellent for debugging and providing actionable information to the user about what went wrong.

Displaying the specific `$dbError` message within a `<code>` block is excellent for debugging and providing actionable information to the user about what went wrong.
@@ -0,0 +41,4 @@
<code><?php echo htmlspecialchars($dbError, ENT_QUOTES, 'UTF-8'); ?></code>
<?php endif; ?>
<p><strong>Lokal prüfen:</strong></p>
<ol style="text-align: left; margin: 0 auto; max-width: 22rem;">
github-actions[bot] (Migrated from github.com) commented 2026-05-23 21:30:29 +00:00

Providing explicit, step-by-step instructions for local debugging on a maintenance page is a very user-friendly and helpful practice. This significantly aids users in resolving common local setup issues.

Providing explicit, step-by-step instructions for local debugging on a maintenance page is a very user-friendly and helpful practice. This significantly aids users in resolving common local setup issues.
Sign in to join this conversation.