*/ protected $options = [ '--dry-run' => 'Show which rows would be purged without deleting cache or DB.', ]; public function run(array $params) { $dryRun = CLI::getOption('dry-run') !== null; $db = Database::connect(); if (! $db->tableExists('rate_limit_blocks')) { CLI::write('Table rate_limit_blocks does not exist. Nothing to do.', 'yellow'); return; } /** @var RateLimiterConfig $rl */ $rl = config('RateLimiter'); $limiter = new RateLimiterService(); $rows = $db->table('rate_limit_blocks') ->where('status', 'active') ->get() ->getResultArray(); $count = 0; foreach ($rows as $row) { $duration = $this->blockSecondsForRow($row, $rl); if ($duration <= 0) { continue; } $blockedAt = strtotime((string) $row['blocked_at']); if ($blockedAt === false) { CLI::write('Skipping id ' . $row['id'] . ': invalid blocked_at.', 'red'); continue; } if (time() < $blockedAt + $duration) { continue; } $id = (int) $row['id']; $cacheId = (string) $row['cache_identifier']; $blockType = (string) $row['block_type']; CLI::write( ($dryRun ? '[dry-run] Would reconcile ' : 'Reconciling ') . "{$blockType} id={$id} level={$row['block_level']} display=" . $row['display_identifier'], 'cyan' ); if (! $dryRun) { if ($blockType === 'ip') { $limiter->purgeIpBlockCaches($cacheId); } elseif ($blockType === 'user') { $limiter->purgeUserBlockCaches($cacheId); } $db->table('rate_limit_blocks')->delete(['id' => $id], 1); } $count++; } CLI::write( $dryRun ? "Dry run complete. {$count} row(s) would be purged and deleted." : "Done. Reconciled {$count} expired row(s).", 'yellow' ); } /** * @param array $row */ protected function blockSecondsForRow(array $row, RateLimiterConfig $cfg): int { $blockCfg = ($row['block_type'] ?? '') === 'ip' ? $cfg->ipBlock : $cfg->userBlock; $level = (string) ($row['block_level'] ?? ''); return match ($level) { 'soft' => (int) $blockCfg['soft_duration'], 'medium' => (int) $blockCfg['medium_duration'], 'hard' => (int) $blockCfg['hard_duration'], default => 0, }; } }