diff --git a/.gitignore b/.gitignore index ff265ae..0fa92db 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ Thumbs.db # Local secrets deploy-config.php +config.php # Logs/temp *.log diff --git a/README.md b/README.md index f1e9c7e..b524550 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,27 @@ photo-gallery/ php -S 127.0.0.1:8080 ``` +## MySQL конфиг и миграции (этап перехода на БД) + +1. Создай локальный конфиг из шаблона: + +```bash +cp config.php.example config.php +``` + +2. Заполни доступы к MySQL в `config.php`. + +3. Прогони миграции: + +```bash +php scripts/migrate.php +``` + +Файлы: +- `lib/db.php` — подключение PDO +- `migrations/*.sql` — схема БД +- `scripts/migrate.php` — runner миграций + Открыть в браузере: - `http://127.0.0.1:8080` diff --git a/config.php.example b/config.php.example new file mode 100644 index 0000000..b2a487f --- /dev/null +++ b/config.php.example @@ -0,0 +1,11 @@ + [ + 'host' => '127.0.0.1', + 'port' => 3306, + 'name' => 'photo_gallery', + 'user' => 'gallery_user', + 'pass' => 'change_me', + 'charset' => 'utf8mb4', + ], +]; diff --git a/lib/db.php b/lib/db.php new file mode 100644 index 0000000..82cd3c5 --- /dev/null +++ b/lib/db.php @@ -0,0 +1,47 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ]); + + return $pdo; +} diff --git a/migrations/001_init.sql b/migrations/001_init.sql new file mode 100644 index 0000000..2792f89 --- /dev/null +++ b/migrations/001_init.sql @@ -0,0 +1,54 @@ +CREATE TABLE IF NOT EXISTS sections ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + sort_order INT NOT NULL DEFAULT 1000, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uq_sections_name (name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS photos ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + section_id BIGINT UNSIGNED NOT NULL, + code_name VARCHAR(191) NOT NULL, + description TEXT NULL, + sort_order INT NOT NULL DEFAULT 1000, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_photos_section FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE, + UNIQUE KEY uq_photos_code_name (code_name), + KEY idx_photos_section_sort (section_id, sort_order) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS photo_files ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + photo_id BIGINT UNSIGNED NOT NULL, + kind ENUM('before','after') NOT NULL, + file_path VARCHAR(500) NOT NULL, + mime_type VARCHAR(100) NOT NULL, + size_bytes INT UNSIGNED NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT fk_photo_files_photo FOREIGN KEY (photo_id) REFERENCES photos(id) ON DELETE CASCADE, + UNIQUE KEY uq_photo_files_kind (photo_id, kind) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS comment_users ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + display_name VARCHAR(191) NOT NULL, + token_hash CHAR(64) NOT NULL, + is_active TINYINT(1) NOT NULL DEFAULT 1, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uq_comment_users_token_hash (token_hash) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS photo_comments ( + id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + photo_id BIGINT UNSIGNED NOT NULL, + user_id BIGINT UNSIGNED NULL, + comment_text TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT fk_photo_comments_photo FOREIGN KEY (photo_id) REFERENCES photos(id) ON DELETE CASCADE, + CONSTRAINT fk_photo_comments_user FOREIGN KEY (user_id) REFERENCES comment_users(id) ON DELETE SET NULL, + KEY idx_photo_comments_photo_created (photo_id, created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/scripts/migrate.php b/scripts/migrate.php new file mode 100755 index 0000000..06f6182 --- /dev/null +++ b/scripts/migrate.php @@ -0,0 +1,48 @@ +#!/usr/bin/env php +getMessage() . PHP_EOL); + exit(1); +} + +$pdo->exec('CREATE TABLE IF NOT EXISTS migrations (name VARCHAR(191) PRIMARY KEY, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'); + +$files = glob(__DIR__ . '/../migrations/*.sql') ?: []; +sort($files, SORT_NATURAL); + +$check = $pdo->prepare('SELECT 1 FROM migrations WHERE name = :name'); +$mark = $pdo->prepare('INSERT INTO migrations(name) VALUES (:name)'); + +foreach ($files as $file) { + $name = basename($file); + $check->execute(['name' => $name]); + if ($check->fetchColumn()) { + echo "skip {$name}" . PHP_EOL; + continue; + } + + echo "apply {$name}" . PHP_EOL; + $sql = file_get_contents($file); + if ($sql === false) { + throw new RuntimeException("Cannot read {$file}"); + } + + $pdo->beginTransaction(); + try { + $pdo->exec($sql); + $mark->execute(['name' => $name]); + $pdo->commit(); + } catch (Throwable $e) { + $pdo->rollBack(); + throw $e; + } +} + +echo "done" . PHP_EOL;