Proje dosyaları eklendi

This commit is contained in:
2026-08-29 15:53:53 +03:00
commit d3313400ca
440 changed files with 24827 additions and 0 deletions
@@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use App\Models\SerpCheck;
use App\Models\SerpOperationState;
use App\Serp\Operations\ActivationManager;
use App\Serp\Operations\CircuitBreaker;
use App\Serp\Operations\DataForSeoHealthService;
use App\Serp\Operations\Money;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Throwable;
class SerpProductionPreflightCommand extends Command
{
protected $signature = 'serp:production:preflight {--json}';
protected $description = 'Production SERP geçiş kapılarını dış HTTP olmadan doğrular';
public function handle(ActivationManager $activation, DataForSeoHealthService $health, CircuitBreaker $circuit): int
{
$gates = [];
$gate = function (string $name, bool $pass, mixed $value = null, bool $critical = true) use (&$gates) {
$gates[$name] = ['status' => $pass ? 'pass' : 'fail', 'critical' => $critical, 'value' => $value];
};
$configured = (string) config('serp.dataforseo.login') !== '' && (string) config('serp.dataforseo.password') !== '';
$gate('environment', app()->environment('production'), app()->environment());
$gate('debug', ! config('app.debug'), config('app.debug') ? 'enabled' : 'disabled');
$gate('provider', config('serp.provider') === 'dataforseo', config('serp.provider'));
$state = $activation->state();
$consistent = (! config('serp.enabled') && in_array($state->value, ['disabled', 'validation_only', 'canary'], true)) || (config('serp.enabled') && in_array($state->value, ['canary', 'enabled', 'emergency_stopped'], true));
$gate('activation_consistency', $consistent, ['enabled' => (bool) config('serp.enabled'), 'mode' => $state->value]);
$gate('credentials', $configured, $configured ? 'configured' : 'not_configured');
try {
$local = $health->validateConfig();
$gate('provider_security', true, ['host' => $local['host'], 'tls' => $local['tls']]);
} catch (Throwable) {
$gate('provider_security', false, 'invalid');
}$pending = $this->pendingMigrations();
$gate('migrations', $pending === 0, ['pending' => $pending]);
try {
$lock = Cache::lock('serp:preflight-lock', 5);
$acquired = $lock->get();
if ($acquired) {
$lock->release();
}$gate('cache_lock', $acquired, config('cache.default'));
} catch (Throwable) {
$gate('cache_lock', false, 'unavailable');
}$gate('queue', in_array(config('queue.default'), ['redis', 'database'], true), ['connection' => config('queue.default'), 'serp_queue' => config('serp.queue')]);
$console = file_get_contents(base_path('routes/console.php'));
$gate('scheduler', str_contains($console, 'serp:dispatch-due') && str_contains($console, 'serp:reconcile'), ['defined' => true]);
$stateRow = SerpOperationState::find(1);
$gate('kill_switch', ! $stateRow?->emergency_stopped_at, $stateRow?->emergency_stopped_at ? 'active' : 'clear');
$gate('circuit', ! $circuit->open(), $circuit->open() ? 'open' : 'closed');
foreach (['daily_budget_usd', 'monthly_budget_usd', 'low_balance_usd', 'critical_balance_usd'] as $key) {
try {
$gate($key, Money::micros((string) config('serp.'.$key)) > 0, (string) config('serp.'.$key));
} catch (Throwable) {
$gate($key, false, 'invalid');
}
}$gate('depth', config('serp.dataforseo.result_depth') === 10, config('serp.dataforseo.result_depth'));
$gate('provider_modes', config('serp.manual_mode') === 'live' && config('serp.automatic_mode') === 'standard', ['manual' => config('serp.manual_mode'), 'automatic' => config('serp.automatic_mode')]);
$summary = ['status' => collect($gates)->contains(fn ($g) => $g['critical'] && $g['status'] === 'fail') ? 'failed' : 'passed', 'remote' => 'not_requested', 'gates' => $gates, 'operations' => ['last_health_at' => $stateRow?->last_health_at, 'last_provider_success_at' => $stateRow?->last_provider_success_at, 'stuck' => SerpCheck::whereIn('status', ['pending', 'running'])->where('updated_at', '<', now()->subMinutes((int) config('serp.stuck_after_minutes')))->count(), 'pending' => SerpCheck::where('status', 'pending')->count(), 'running' => SerpCheck::where('status', 'running')->count()]];
$this->option('json') ? $this->line(json_encode($summary, JSON_THROW_ON_ERROR)) : $this->line('Preflight: '.$summary['status']);
return $summary['status'] === 'passed' ? self::SUCCESS : self::FAILURE;
}
private function pendingMigrations(): int
{
if (! Schema::hasTable('migrations')) {
return count(glob(database_path('migrations/*.php')));
}$ran = DB::table('migrations')->pluck('migration')->all();
return count(array_filter(glob(database_path('migrations/*.php')), fn ($f) => ! in_array(pathinfo($f, PATHINFO_FILENAME), $ran, true)));
}
}