<?php
 namespace App\Helpers; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Redis; class CompletedJobLogger { public const REDIS_KEY = 'completed_jobs_list'; private const CACHE_KEY = 'completed_jobs_list'; private const MAX_JOBS = 100; private const TTL_SECONDS = 604800; private static function useRedis(): bool { return config('cache.default') === 'redis' || config('queue.default') === 'redis'; } public static function log(string $jobClass, array $data = []): void { $jobEntry = [ 'job_id' => uniqid('completed_', true), 'job_class' => $jobClass, 'short_class' => class_basename($jobClass), 'job_type' => self::detectJobType($jobClass), 'queue' => 'completed', 'details' => $data, 'attempts' => 1, 'created_at' => now()->format('d.m.Y H:i:s'), 'created_at_timestamp' => now()->timestamp, 'completed_at_human' => now()->diffForHumans(), ]; try { if (self::useRedis()) { Redis::lpush(self::REDIS_KEY, json_encode($jobEntry)); Redis::ltrim(self::REDIS_KEY, 0, self::MAX_JOBS - 1); if (Redis::ttl(self::REDIS_KEY) === -1) { Redis::expire(self::REDIS_KEY, self::TTL_SECONDS); } return; } self::cacheMutate(function (array $list) use ($jobEntry): array { array_unshift($list, $jobEntry); return array_slice($list, 0, self::MAX_JOBS); }); } catch (\Throwable $e) { Log::warning('Failed to log completed job', ['job_class' => $jobClass, 'error' => $e->getMessage()]); } } public static function getAll(): array { try { if (self::useRedis()) { return array_map(fn ($j) => json_decode($j, true), Redis::lrange(self::REDIS_KEY, 0, -1)); } return (array) Cache::get(self::CACHE_KEY, []); } catch (\Throwable $e) { Log::warning('Failed to get completed jobs', ['error' => $e->getMessage()]); return []; } } public static function count(): int { try { return self::useRedis() ? (int) Redis::llen(self::REDIS_KEY) : count((array) Cache::get(self::CACHE_KEY, [])); } catch (\Throwable $e) { return 0; } } public static function clear(): void { try { if (self::useRedis()) { Redis::del(self::REDIS_KEY); } else { Cache::forget(self::CACHE_KEY); } } catch (\Throwable $e) { Log::warning('Failed to clear completed jobs', ['error' => $e->getMessage()]); } } public static function paginate(int $page = 1, int $perPage = 20): array { try { $total = self::count(); $start = ($page - 1) * $perPage; if (self::useRedis()) { $jobs = Redis::lrange(self::REDIS_KEY, $start, $start + $perPage - 1); $data = array_map(fn ($j) => json_decode($j, true), $jobs); } else { $data = array_slice((array) Cache::get(self::CACHE_KEY, []), $start, $perPage); } return [ 'data' => $data, 'total' => $total, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => (int) ceil($total / $perPage), ]; } catch (\Throwable $e) { Log::warning('Failed to paginate completed jobs', ['error' => $e->getMessage()]); return ['data' => [], 'total' => 0, 'per_page' => $perPage, 'current_page' => $page, 'last_page' => 0]; } } private static function cacheMutate(callable $mutator): void { $apply = function () use ($mutator): void { $list = (array) Cache::get(self::CACHE_KEY, []); Cache::put(self::CACHE_KEY, $mutator($list), self::TTL_SECONDS); }; try { Cache::lock(self::CACHE_KEY.':lock', 10)->block(5, $apply); } catch (\Throwable $e) { $apply(); } } private static function detectJobType(string $jobClass): string { $shortClass = class_basename($jobClass); if (str_contains($shortClass, 'Email') || str_contains($shortClass, 'SendEmail')) { return 'email'; } if (str_contains($shortClass, 'Product') && str_contains($shortClass, 'Provision')) { return 'product_provisioning'; } if (str_contains($shortClass, 'Domain') && str_contains($shortClass, 'Provision')) { return 'domain_provisioning'; } return 'other'; } } 