- Implemented the full Fat Bottom Grille storefront and staff dashboard.
Highlights include configurable menus, specials, cart/checkout, taxes, customer accounts with email 2FA, newsletters, analytics, refunds, PDF/CSV exports, Square/Mailgun adapters, and optional MySQL migration. See README.md for setup and production configuration. Verified PHP/JavaScript syntax, responsive layouts, registration, 2FA, checkout, dashboard workflows, settings persistence, and PDF generation. Real Square, Mailgun, and MySQL connections require credentials/server access. Square implementation follows the official Payments API and Refunds API.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FatBottom\Services;
|
||||
|
||||
use FatBottom\Core\Config;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
final class MySqlMigrator
|
||||
{
|
||||
public function __construct(private readonly Config $config)
|
||||
{
|
||||
}
|
||||
|
||||
public function migrate(): array
|
||||
{
|
||||
$sqlitePath = BASE_PATH . '/storage/database/store.sqlite';
|
||||
if (!is_file($sqlitePath)) {
|
||||
throw new RuntimeException('The SQLite store database does not exist yet.');
|
||||
}
|
||||
if (!extension_loaded('pdo_mysql')) {
|
||||
throw new RuntimeException('The pdo_mysql PHP extension is required for migration.');
|
||||
}
|
||||
|
||||
$mysql = new PDO(
|
||||
sprintf(
|
||||
'mysql:host=%s;port=%d;charset=utf8mb4',
|
||||
$this->config->get('database.mysql_host'),
|
||||
(int) $this->config->get('database.mysql_port', 3306)
|
||||
),
|
||||
(string) $this->config->get('database.mysql_username'),
|
||||
(string) $this->config->get('database.mysql_password'),
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
$database = preg_replace('/[^a-zA-Z0-9_]/', '', (string) $this->config->get('database.mysql_database'));
|
||||
if ($database === '') {
|
||||
throw new RuntimeException('Enter a valid MySQL database name.');
|
||||
}
|
||||
$mysql->exec("CREATE DATABASE IF NOT EXISTS `{$database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
$mysql->exec("USE `{$database}`");
|
||||
|
||||
$sqlite = new PDO('sqlite:' . $sqlitePath, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
$tables = $sqlite->query(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
||||
)->fetchAll(PDO::FETCH_COLUMN);
|
||||
$rowCount = 0;
|
||||
|
||||
$mysql->exec('SET FOREIGN_KEY_CHECKS = 0');
|
||||
foreach ($tables as $table) {
|
||||
$columns = $sqlite->query("PRAGMA table_info(`{$table}`)")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$definitions = [];
|
||||
$primary = [];
|
||||
foreach ($columns as $column) {
|
||||
$type = strtoupper((string) $column['type']);
|
||||
$mysqlType = str_contains($type, 'INT') ? 'BIGINT'
|
||||
: (str_contains($type, 'REAL') || str_contains($type, 'FLOA') ? 'DOUBLE' : 'TEXT');
|
||||
if ((int) $column['pk'] === 1 && $column['name'] === 'id') {
|
||||
$definitions[] = '`id` BIGINT NOT NULL AUTO_INCREMENT';
|
||||
$primary[] = '`id`';
|
||||
} else {
|
||||
$definitions[] = sprintf(
|
||||
'`%s` %s %s',
|
||||
str_replace('`', '', (string) $column['name']),
|
||||
$mysqlType,
|
||||
(int) $column['notnull'] === 1 ? 'NOT NULL' : 'NULL'
|
||||
);
|
||||
if ((int) $column['pk'] === 1) {
|
||||
$primary[] = '`' . str_replace('`', '', (string) $column['name']) . '`';
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($primary !== []) {
|
||||
$definitions[] = 'PRIMARY KEY (' . implode(', ', $primary) . ')';
|
||||
}
|
||||
$mysql->exec("DROP TABLE IF EXISTS `{$table}`");
|
||||
$mysql->exec("CREATE TABLE `{$table}` (" . implode(', ', $definitions) . ') ENGINE=InnoDB');
|
||||
|
||||
$rows = $sqlite->query("SELECT * FROM `{$table}`")->fetchAll(PDO::FETCH_ASSOC);
|
||||
if ($rows !== []) {
|
||||
$names = array_keys($rows[0]);
|
||||
$placeholders = implode(', ', array_fill(0, count($names), '?'));
|
||||
$quotedNames = implode(', ', array_map(static fn (string $name): string => "`{$name}`", $names));
|
||||
$insert = $mysql->prepare("INSERT INTO `{$table}` ({$quotedNames}) VALUES ({$placeholders})");
|
||||
foreach ($rows as $row) {
|
||||
$insert->execute(array_values($row));
|
||||
$rowCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$mysql->exec('SET FOREIGN_KEY_CHECKS = 1');
|
||||
|
||||
return ['tables' => count($tables), 'rows' => $rowCount];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user