Files
boursecu_server/app/Console/Commands/DeploySettingsConstCommand.php
T
mohmdsdgandCursor 924234bb19 fix(settings): PHP sync for runtime can_edit after deploy
Force can_edit=1 for Montzar/price runtime keys via settings:deploy
and settings:sync-runtime-can-edit, so tenant DB stays correct even
when an older deploy SQL is still on the server.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 21:07:12 +03:30

187 lines
5.8 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Services\SettingsRuntimeCanEditSync;
use Illuminate\Console\Command;
use Symfony\Component\Process\Process;
class DeploySettingsConstCommand extends Command
{
protected $signature = 'settings:deploy
{database?* : نام دیتابیس (اختیاری؛ پیش‌فرض DB_DATABASE)}
{--file= : مسیر SQL (پیش‌فرض database/sql/deploy_settings_const.sql)}
{--preserve : deploy_settings_const_preserve_value_sync_can_edit.sql}';
protected $description = 'اجرای deploy_settings_const.sql روی DB tenant با اتصال .env';
public function handle(): int
{
$path = $this->resolveSqlPath();
if ($path === null) {
return self::FAILURE;
}
$cfg = config('database.connections.'.config('database.default'));
if (($cfg['driver'] ?? '') !== 'mysql') {
$this->error('فقط mysql پشتیبانی می‌شود.');
return self::FAILURE;
}
$host = (string) ($cfg['host'] ?? '127.0.0.1');
$port = (string) ($cfg['port'] ?? '3306');
$user = (string) ($cfg['username'] ?? '');
$pass = (string) ($cfg['password'] ?? '');
$defaultDb = (string) ($cfg['database'] ?? '');
if ($user === '') {
$this->error('DB_USERNAME در .env خالی است.');
return self::FAILURE;
}
$databases = $this->argument('database');
if ($databases === [] || $databases === null) {
if ($defaultDb === '') {
$this->error('نام DB ندهید → DB_DATABASE را در .env بگذارید یا آرگومان بدهید.');
return self::FAILURE;
}
$databases = [$defaultDb];
}
$mysql = $this->findMysqlBinary();
if ($mysql === null) {
$this->error('mysql client پیدا نشد. نصب کنید یا اجرا کنید: bash database/sql/run_deploy_settings.sh');
return self::FAILURE;
}
$sql = file_get_contents($path);
if ($sql === false || trim($sql) === '') {
$this->error("فایل SQL خالی یا غیرقابل خواندن: {$path}");
return self::FAILURE;
}
foreach ($databases as $db) {
$db = trim((string) $db);
if ($db === '') {
continue;
}
$this->info("==> {$path} → {$db} @ {$host}:{$port}");
$process = new Process([
$mysql,
'-h', $host,
'-P', $port,
'-u', $user,
'--default-character-set=utf8mb4',
$db,
], null, ['MYSQL_PWD' => $pass]);
$process->setInput($sql);
$process->setTimeout(null);
$process->run(function ($type, $buffer) {
$line = trim($buffer);
if ($line === '') {
return;
}
if ($type === Process::ERR) {
$this->error($line);
} else {
$this->line($line);
}
});
if (! $process->isSuccessful()) {
$this->error("خطا روی دیتابیس: {$db}");
return self::FAILURE;
}
$this->syncRuntimeCanEdit($db);
$this->info(" done: {$db}");
}
$this->info('==> all done');
return self::SUCCESS;
}
private function resolveSqlPath(): ?string
{
if ($this->option('preserve')) {
$relative = 'database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql';
} else {
$relative = $this->option('file') ?: 'database/sql/deploy_settings_const.sql';
}
$path = str_starts_with($relative, '/')
? $relative
: base_path($relative);
if (! is_file($path)) {
$this->error("فایل SQL پیدا نشد: {$path}");
return null;
}
return $path;
}
private function syncRuntimeCanEdit(string $database): void
{
try {
$sync = app(SettingsRuntimeCanEditSync::class);
$sync->useDatabase($database);
$updated = $sync->sync();
$this->line(" runtime can_edit sync: {$updated} row(s) updated");
$status = $sync->statusMap();
if ($status !== []) {
$preview = collect($status)
->map(fn ($canEdit, $key) => "{$key}={$canEdit}")
->implode(', ');
$this->line(" runtime keys: {$preview}");
}
$violations = $sync->violations();
if ($violations !== []) {
$this->warn(' WARN: runtime keys still can_edit≠1: '.implode(', ', array_keys($violations)));
}
$fee = \App\Models\Setting::query()
->where('key', 'fee_deduct_from_gold')
->first(['key', 'can_edit']);
if ($fee) {
$this->line(" check fee_deduct_from_gold can_edit={$fee->can_edit}");
}
} catch (\Throwable $e) {
$this->warn(' runtime can_edit sync skipped: '.$e->getMessage());
}
}
private function findMysqlBinary(): ?string
{
$process = new Process(['command', '-v', 'mysql']);
$process->run();
if ($process->isSuccessful()) {
$bin = trim($process->getOutput());
if ($bin !== '') {
return $bin;
}
}
foreach (['/usr/bin/mysql', '/usr/local/bin/mysql'] as $candidate) {
if (is_executable($candidate)) {
return $candidate;
}
}
return null;
}
}