From a4cbd6a34d7a70f4491517643311cf39c1a9551d Mon Sep 17 00:00:00 2001 From: Lumerel Deploy Date: Sun, 5 Apr 2026 03:02:47 +0000 Subject: [PATCH] Deploy from Lumerel --- .docker/99-migrate.sh | 54 ++ .dockerignore | 3 + Dockerfile | 23 + admin.php | 672 ++++++++++++++++++ api.php | 76 ++ db.php | 13 + index.php | 510 +++++++++++++ migrate.php | 24 + .../001_create_training_items_table.php | 61 ++ 9 files changed, 1436 insertions(+) create mode 100644 .docker/99-migrate.sh create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 admin.php create mode 100644 api.php create mode 100644 db.php create mode 100644 index.php create mode 100644 migrate.php create mode 100644 migrations/001_create_training_items_table.php diff --git a/.docker/99-migrate.sh b/.docker/99-migrate.sh new file mode 100644 index 0000000..66b83d4 --- /dev/null +++ b/.docker/99-migrate.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# IMPORTANT: This script is sourced (not executed) by the webdevops entrypoint. +# Never use "exit" — it would kill the parent entrypoint and prevent supervisord from starting. +# Use "return" instead to leave this script without affecting the parent. + +echo ">>> Waiting for database connection..." +echo ">>> DB_HOST=${DB_HOST:-NOT SET}" +echo ">>> DB_PORT=${DB_PORT:-NOT SET}" +echo ">>> DB_DATABASE=${DB_DATABASE:-NOT SET}" +echo ">>> DB_USERNAME=${DB_USERNAME:-NOT SET}" +echo ">>> DB_PASSWORD is $([ -n "$DB_PASSWORD" ] && echo 'set' || echo 'NOT SET')" + +if [ -z "$DB_HOST" ] || [ -z "$DB_USERNAME" ]; then + echo ">>> ERROR: DB_HOST or DB_USERNAME not set. Skipping migrations." + return 0 2>/dev/null || true +fi + +_migrate_max_retries=30 +_migrate_count=0 +_migrate_done=false + +while [ $_migrate_count -lt $_migrate_max_retries ]; do + if php -r ' + try { + $host = getenv("DB_HOST"); + $port = getenv("DB_PORT") ?: "3306"; + $user = getenv("DB_USERNAME"); + $pass = getenv("DB_PASSWORD"); + new PDO("mysql:host=$host;port=$port", $user, $pass, [PDO::ATTR_TIMEOUT => 3]); + exit(0); + } catch (Throwable $e) { + fwrite(STDERR, "PDO error: " . $e->getMessage() . "\n"); + exit(1); + } + ' 2>&1; then + echo ">>> Database is reachable. Running migrations..." + if php /app/migrate.php; then + echo ">>> Migrations completed successfully." + else + echo ">>> WARNING: migrate.php exited with code $?" + fi + _migrate_done=true + break + fi + + _migrate_count=$((_migrate_count + 1)) + echo ">>> Waiting for database... attempt $_migrate_count/$_migrate_max_retries" + sleep 2 +done + +if [ "$_migrate_done" = false ]; then + echo ">>> WARNING: Could not connect to database after $_migrate_max_retries attempts. Skipping migrations." +fi \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..17896fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..96a47bf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM webdevops/php-nginx:8.3-alpine +ENV WEB_DOCUMENT_ROOT=/app +ARG CACHE_BUST=1775358167 +COPY . /app +RUN echo "index index.php index.html index.htm;" > /opt/docker/etc/nginx/vhost.common.d/01-index.conf \ + && echo "add_header Cache-Control 'no-cache, no-store, must-revalidate';" > /opt/docker/etc/nginx/vhost.common.d/02-no-cache.conf +RUN set -e; if [ -f /app/composer.json ]; then \ + echo ">>> composer.json found, installing dependencies..."; \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \ + cd /app && composer install --no-dev --no-interaction --optimize-autoloader; \ + ls -la /app/vendor/autoload.php; \ + else \ + echo ">>> No composer.json found, skipping composer install"; \ + fi +RUN set -e; if [ -f /app/package.json ]; then \ + echo ">>> package.json found, installing node dependencies..."; \ + apk add --no-cache nodejs npm; \ + cd /app && npm install --production; \ + else \ + echo ">>> No package.json found, skipping npm install"; \ + fi +COPY --chmod=755 .docker/99-migrate.sh /opt/docker/provision/entrypoint.d/99-migrate.sh +RUN chown -R application:application /app \ No newline at end of file diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..8ae64f2 --- /dev/null +++ b/admin.php @@ -0,0 +1,672 @@ +query("SELECT * FROM training_items ORDER BY sort_order ASC")->fetchAll() + : []; +?> + + + + + +Pawgress Admin ⚙️ + + + + + + +
+ +

Pawgress Admin

+

Manage your daily training checklist

+ + + +
+ + + +
+ +
+ + + +
+ +
+
+

Training Items

+

item • Drag to reorder

+
+ +
+ + +
+
🐕
+

No Training Items Yet

+

Click "Add Item" above to create your first training task.

+
+ +
+ $item): ?> +
+
+
+
+
+ +
+ +
+
+ + +
+
+ +
+ + +
+ + + + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/api.php b/api.php new file mode 100644 index 0000000..c2c9aba --- /dev/null +++ b/api.php @@ -0,0 +1,76 @@ + 'Unauthorized']); + exit; +} + +$body = json_decode(file_get_contents('php://input'), true); +$action = $body['action'] ?? ''; + +try { + switch ($action) { + + // ── CREATE ──────────────────────────────────────────────────── + case 'create': { + $title = trim($body['title'] ?? ''); + $desc = trim($body['description'] ?? ''); + if (!$title) { + echo json_encode(['error' => 'Title is required']); exit; + } + $maxOrder = $pdo->query(\"SELECT COALESCE(MAX(sort_order),0) FROM training_items\")->fetchColumn(); + $stmt = $pdo->prepare(\"INSERT INTO training_items (title, description, sort_order) VALUES (?, ?, ?)\"); + $stmt->execute([$title, $desc ?: null, (int)$maxOrder + 1]); + echo json_encode(['success' => true, 'id' => $pdo->lastInsertId()]); + break; + } + + // ── UPDATE ──────────────────────────────────────────────────── + case 'update': { + $id = (int)($body['id'] ?? 0); + $title = trim($body['title'] ?? ''); + $desc = trim($body['description'] ?? ''); + if (!$id || !$title) { + echo json_encode(['error' => 'ID and title are required']); exit; + } + $stmt = $pdo->prepare(\"UPDATE training_items SET title=?, description=?, updated_at=NOW() WHERE id=?\"); + $stmt->execute([$title, $desc ?: null, $id]); + echo json_encode(['success' => true]); + break; + } + + // ── DELETE ──────────────────────────────────────────────────── + case 'delete': { + $id = (int)($body['id'] ?? 0); + if (!$id) { echo json_encode(['error' => 'ID required']); exit; } + $pdo->prepare(\"DELETE FROM training_items WHERE id=?\")->execute([$id]); + echo json_encode(['success' => true]); + break; + } + + // ── REORDER ─────────────────────────────────────────────────── + case 'reorder': { + $ids = $body['ids'] ?? []; + if (!is_array($ids)) { echo json_encode(['error' => 'IDs must be an array']); exit; } + $stmt = $pdo->prepare(\"UPDATE training_items SET sort_order=? WHERE id=?\"); + foreach ($ids as $i => $id) { + $stmt->execute([$i + 1, (int)$id]); + } + echo json_encode(['success' => true]); + break; + } + + default: + echo json_encode(['error' => 'Unknown action']); + } + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Database error: ' . $e->getMessage()]); +} \ No newline at end of file diff --git a/db.php b/db.php new file mode 100644 index 0000000..bac204d --- /dev/null +++ b/db.php @@ -0,0 +1,13 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); +} catch (PDOException $e) { + http_response_code(500); + die(json_encode(['error' => 'Database connection failed.'])); +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..7608b7f --- /dev/null +++ b/index.php @@ -0,0 +1,510 @@ +query("SELECT * FROM training_items ORDER BY sort_order ASC")->fetchAll(); +$today = date('Y-m-d'); +?> + + + + + +Pawgress 🐾 Daily Training + + + + + + +
+ +

Pawgress

+

• Daily Training

+
+ +
+ + +
+
+ + + + + 🐶 +
+
+
Today's Progress
+
0 /
+
+
+
+
Let's get training! 💪
+
+
+ + +
+

🎉 Amazing Work!

+

You crushed every training task today. Your pup is so proud!

+
+ + + +
+
🐕
+

No Training Items Yet

+

Head to the admin panel to add your first training tasks.

+ Go to Admin → +
+ +
Training Checklist
+
+ +
+
+ + + +
+
+
+ +
+ +
+
+ +
+ + + + +
+ + + + + + \ No newline at end of file diff --git a/migrate.php b/migrate.php new file mode 100644 index 0000000..9043fb7 --- /dev/null +++ b/migrate.php @@ -0,0 +1,24 @@ +exec("CREATE TABLE IF NOT EXISTS migrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + migration VARCHAR(255) NOT NULL UNIQUE, + ran_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +)"); + +// Get already-run migrations +$ran = $pdo->query("SELECT migration FROM migrations")->fetchAll(PDO::FETCH_COLUMN); + +// Run pending migrations +$files = glob(__DIR__ . '/migrations/*.php'); +sort($files); + +foreach ($files as $file) { + $name = basename($file); + if (!in_array($name, $ran)) { + require $file; + $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)")->execute([$name]); + } +} diff --git a/migrations/001_create_training_items_table.php b/migrations/001_create_training_items_table.php new file mode 100644 index 0000000..64d0059 --- /dev/null +++ b/migrations/001_create_training_items_table.php @@ -0,0 +1,61 @@ +exec(" + CREATE TABLE IF NOT EXISTS training_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description TEXT NULL, + sort_order INT NOT NULL DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) +"); + +// Seed default items if table is empty +$count = $pdo->query("SELECT COUNT(*) FROM training_items")->fetchColumn(); +if ($count == 0) { + $items = [ + [ + 'Minute 0–3: Calm training', + "Sit down somewhere boring.\n\n +• Ignore her completely\n +• The second she settles even a little (lays down, sighs, pauses) → quietly say “yes” and give a treat\n +• No excitement, no talking\n\n +Goal: teach her that calm = reward", + 1 + ], + [ + 'Minute 3–6: Leash pressure game', + "Inside the house.\n\n +• Put leash on\n +• Apply gentle pressure (not a yank, just steady)\n +• The moment she moves toward you → “yes” + treat\n\n +Goal: pressure means move back, not fight it", + 2 + ], + [ + 'Minute 6–8: Micro walk practice', + "Inside or just outside your door.\n\n +• Take a few steps\n +• If leash tightens → stop immediately\n +• When she gives slack → move again\n\n +This is practice, not exercise", + 3 + ], + [ + 'Minute 8–10: Separation practice', + "Step away briefly.\n\n +• Step out of sight\n +• Count to 3\n +• Come back before she reacts\n +• Stay calm, no excitement when returning\n\n +Repeat 5–10 times\n\n +Goal: leaving and returning is no big deal", + 4 + ], + ]; + + $stmt = $pdo->prepare("INSERT INTO training_items (title, description, sort_order) VALUES (?, ?, ?)"); + foreach ($items as $item) { + $stmt->execute($item); + } +} \ No newline at end of file