get(); } public function queue() { return IntercomCall::whereIn('status', ['waiting', 'active']) ->orderBy('requested_at') ->limit(20)->get(); } public function history(Request $request) { return IntercomCall::orderByDesc('id')->limit($request->input('limit', 50))->get(); } // Dipanggil Go relay saat ESP32 kirim MQTT intercom/request public function request(Request $request) { $data = $request->validate([ 'device_id' => 'required|string|max:20', 'arah' => 'sometimes|string|in:in,out', ]); $deviceId = strtoupper($data['device_id']); $device = IntercomDevice::firstOrCreate( ['device_id' => $deviceId], ['nama' => $deviceId, 'status' => 'idle'] ); // Kalau device lagi active, tetap masuk antrean (tidak ditolak) $call = IntercomCall::create([ 'device_id' => $deviceId, 'arah' => $data['arah'] ?? 'in', 'status' => 'waiting', 'requested_at' => now(), ]); $device->update(['status' => 'calling', 'last_seen_at' => now()]); Log::info("Intercom request {$deviceId} call_id={$call->id}"); return response()->json(['status' => true, 'call_id' => $call->id, 'device_id' => $deviceId]); } // Admin klik [Terima] di dashboard public function accept(Request $request) { $data = $request->validate(['call_id' => 'required|integer']); $call = IntercomCall::findOrFail($data['call_id']); // Tutup sesi active lain di device yang sama (single private session) IntercomCall::where('device_id', $call->device_id) ->where('status', 'active')->update(['status' => 'ended', 'ended_at' => now(), 'ended_by' => 'admin-switch']); $call->update(['status' => 'active', 'started_at' => now()]); IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'active']); // Go relay yang bertugas publish MQTT intercom/invite/{DEVICE} + buka UDP port. // Laravel cuma ubah status, tidak publish MQTT langsung. return response()->json(['status' => true, 'call' => $call]); } public function end(Request $request) { $data = $request->validate([ 'call_id' => 'required|integer', 'ended_by' => 'sometimes|string|max:20', 'recording_path' => 'sometimes|string|max:255', ]); $call = IntercomCall::findOrFail($data['call_id']); $now = now(); $durasi = $call->started_at ? $now->diffInSeconds($call->started_at) : ($call->requested_at ? $now->diffInSeconds($call->requested_at) : 0); $call->update([ 'status' => $call->status === 'waiting' && ($data['ended_by'] ?? '') === 'timeout' ? 'missed' : 'ended', 'ended_at' => $now, 'durasi_detik' => $durasi, 'ended_by' => $data['ended_by'] ?? 'admin', 'recording_path' => $data['recording_path'] ?? $call->recording_path, ]); // Balikin status device ke idle kalau tidak ada sesi lain $masihAktif = IntercomCall::where('device_id', $call->device_id)->whereIn('status', ['waiting', 'active'])->exists(); if (!$masihAktif) { IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'idle']); } return response()->json(['status' => true, 'call' => $call]); } // Heartbeat / LWT dari Go relay: ESP32 online/offline public function heartbeat(Request $request) { $data = $request->validate([ 'device_id' => 'required|string|max:20', 'status' => 'sometimes|string|max:20', 'ip_address' => 'sometimes|string|max:45', ]); IntercomDevice::updateOrCreate( ['device_id' => strtoupper($data['device_id'])], ['status' => $data['status'] ?? 'idle', 'ip_address' => $data['ip_address'] ?? null, 'last_seen_at' => now()] ); return response()->json(['status' => true]); } }