seedproject-web/api/commands/MigrateCommand.php
Carlos Arias 1fdfc3174b feat(api): Foundation — installer, migrations, two-tier auth, health round-trip
Builds the /api Foundation into the base (per api/.memory/foundation-plan.md):
- app/Services/Installer.php  DB test + dump.sql import + crypto keys + /api config + lock
- app/Services/Migrator.php   versioned db/migrations/*.sql runner (+ migrations table)
- commands/InstallCommand.php (app:install), commands/MigrateCommand.php (db:migrate)
- app/Controllers/{JsonController,PublicController,ApiController}  envelope + two-tier auth
- public/controllers/{health,admin}.php  GET /api/health (public), /api/admin/ping (bearer)
- db/migrations/001_*.sql  smoke migration
- install/controllers/index.php  web wizard now delegates to Installer (path bugs fixed, lock)
- console + composer.json  register commands / add commands to classmap
- app/src/pages/api-health-test.astro  browser round-trip proof page

Verified (no DB): composer install OK; php console lists app:install + db:migrate;
PSR-4 classes autoload; CLI fails gracefully (validation, bad DB, missing config) with
no artifacts left; app builds 14 pages. Live DB + HTTP round-trip pending a served instance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
2026-07-04 23:23:51 +00:00

42 lines
1.7 KiB
PHP

<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use App\Services\Migrator;
class MigrateCommand extends Command
{
protected function configure()
{
$this->setName('db:migrate')
->setDescription('Apply pending SQL migrations from db/migrations/')
->addOption('status', null, InputOption::VALUE_NONE, 'Show applied/pending without applying');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$base = dirname(__DIR__); // api/
if (!is_file($base . '/config.php')) {
$output->writeln('<error>config.php missing — run app:install first.</error>');
return Command::FAILURE;
}
require_once $base . '/config.php'; // defines DB_* constants
$pdo = new PDO(
'mysql:host=' . DB_HOST . ';dbname=' . DB_NAME . ';charset=utf8mb4',
DB_USER, DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$migrator = new Migrator($pdo, $base . '/db/migrations');
$migrator->ensureTable();
if ($input->getOption('status')) {
$output->writeln('Applied: ' . (implode(', ', $migrator->applied()) ?: '(none)'));
$output->writeln('Pending: ' . (implode(', ', array_map('basename', $migrator->pending())) ?: '(none)'));
return Command::SUCCESS;
}
$done = $migrator->migrate();
$output->writeln($done ? 'Applied: ' . implode(', ', $done) : 'Nothing to migrate.');
return Command::SUCCESS;
}
}