userId = $userId; $this->videoStoragePath = $videoStoragePath; } public function handle(): void { $absVideoPath = Storage::disk('local')->path("{$this->videoStoragePath}/enrollment.mp4"); if (!file_exists($absVideoPath)) { Log::error("Face enrollment: video tidak ditemukan untuk user {$this->userId}: {$absVideoPath}"); return; } try { $response = Http::timeout(120) ->withHeaders([ 'X-API-Key' => config('services.flask.api_key'), ]) ->attach( 'video', fopen($absVideoPath, 'r'), 'enrollment.mp4' ) ->post(config('services.flask.url') . '/extract-frames', [ 'user_id' => (string) $this->userId, 'target_frames' => 200, ]); if (!$response->successful()) { throw new \Exception("Flask API error: HTTP {$response->status()} - " . $response->body()); } $output = $response->json(); if (!isset($output['status']) || $output['status'] !== 'success') { throw new \Exception("Extract frames error: " . ($output['message'] ?? 'Unknown')); } $jumlahFrame = $output['total_extracted'] ?? 0; DB::table('data_wajah')->where('id_user', $this->userId)->update([ 'jumlah_frame' => $jumlahFrame, 'is_verified' => StatusVerifikasiWajah::PENDING, ]); Log::info("Face enrollment: extract frames berhasil untuk user {$this->userId}. Frames: {$jumlahFrame}. Menunggu HRD approve untuk training model."); } catch (\Exception $e) { Log::error("Face enrollment GAGAL untuk user {$this->userId}: " . $e->getMessage()); DB::table('data_wajah')->where('id_user', $this->userId)->update([ 'is_verified' => StatusVerifikasiWajah::PENDING, ]); } } }