$taskId, 'title' => null, 'message' => null, 'badge' => null, 'state' => 'queued', // queued|running|done|failed 'position' => 'bottom-right', 'updated_at' => time(), ], $data); // Task-JSON Redis::setex("task:$taskId", self::TTL, json_encode($payload)); // User-Set Redis::sadd("user:$userId:tasks", $taskId); Redis::expire("user:$userId:tasks", self::TTL); // WS raus event(new TaskUpdated($taskId, $userId, $payload)); } public static function done(int $userId, string $taskId, string $message = 'Fertig.'): void { self::update($userId, $taskId, ['state' => 'done', 'message' => $message]); } public static function fail(int $userId, string $taskId, string $message = 'Fehlgeschlagen.'): void { self::update($userId, $taskId, ['state' => 'failed', 'message' => $message]); } public static function update(int $userId, string $taskId, array $patch): void { $key = "task:$taskId"; $raw = Redis::get($key); $base = $raw ? json_decode($raw, true) : ['id' => $taskId]; $payload = array_merge($base, $patch, ['updated_at' => time()]); Redis::setex($key, self::TTL, json_encode($payload)); Redis::sadd("user:$userId:tasks", $taskId); Redis::expire("user:$userId:tasks", self::TTL); event(new TaskUpdated($taskId, $userId, $payload)); } /** Initiale Tasks eines Users lesen */ public static function listForUser(int $userId): array { $ids = Redis::smembers("user:$userId:tasks") ?: []; $items = []; foreach ($ids as $id) { $raw = Redis::get("task:$id"); if ($raw) $items[] = json_decode($raw, true); } // optional: nach updated_at sortiert usort($items, fn($a,$b) => ($b['updated_at']??0) <=> ($a['updated_at']??0)); return $items; } /** Optional: Client bestÃĪtigt (entfernen, aber Task-Key leben lassen bis TTL) */ public static function ack(int $userId, string $taskId): void { Redis::srem("user:$userId:tasks", $taskId); } }