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>
187 lines
5.8 KiB
PHP
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;
|
|
}
|
|
}
|