performedOn($user) ->causedBy($user) ->withProperties([ 'guard' => $guard, 'ip_address' => request()->ip(), 'user_agent' => request()->userAgent(), 'timestamp' => now(), 'status' => 'online', ]) ->event('updated') ->log('User online'); // Cache for quick access $cacheKey = "presence_{$guard}_{$user->id}"; Cache::put($cacheKey, [ 'user_id' => $user->id, 'user_type' => get_class($user), 'guard' => $guard, 'name' => $user->name, 'avatar' => $user->avatar ?? null, 'last_seen' => now(), 'status' => 'online', ], now()->addMinutes(self::ONLINE_THRESHOLD_MINUTES + 2)); // Broadcast presence update broadcast(new \App\Events\UserPresenceUpdated($user, $guard, 'online')); // Clear cache for online users to force refresh Cache::forget("online_users_{$guard}_" . self::ONLINE_THRESHOLD_MINUTES); // Cleanup old presence records for this user $this->cleanupUserPresenceRecords($user, $guard); } public function setUserOffline($user, $guard = 'web') { if (!$user) { return; } // Log offline activity activity(self::PRESENCE_ACTIVITY) ->performedOn($user) ->causedBy($user) ->withProperties([ 'guard' => $guard, 'status' => 'offline', 'timestamp' => now(), ]) ->log('User offline'); // Remove from cache $cacheKey = "presence_{$guard}_{$user->id}"; Cache::forget($cacheKey); // Broadcast offline status broadcast(new \App\Events\UserPresenceUpdated($user, $guard, 'offline')); // Clear online users cache Cache::forget("online_users_{$guard}_" . self::ONLINE_THRESHOLD_MINUTES); // Don't cleanup on offline - preserve presence history } public function getOnlineUsers($guard = 'web', $minutes = null) { $minutes = $minutes ?? self::ONLINE_THRESHOLD_MINUTES; $cacheKey = "online_users_{$guard}_{$minutes}"; return Cache::remember($cacheKey, 30, function () use ($guard, $minutes) { try { // Simple and reliable database query $activities = Activity::where('log_name', self::PRESENCE_ACTIVITY) ->where('properties->guard', $guard) ->where('created_at', '>=', now()->subMinutes($minutes)) ->with('subject') ->latest() ->get(); // Group by user and get the latest activity for each $userActivities = $activities->groupBy('subject_id')->map(function ($userActivities) { return $userActivities->first(); // Get the most recent activity }); // Filter out offline users and map to user data $onlineUsers = $userActivities->map(function ($activity) { $user = $activity->subject; if (!$user) { return null; } // Skip if latest activity is "offline" $properties = is_string($activity->properties) ? json_decode($activity->properties, true) : $activity->properties; if (isset($properties['status']) && $properties['status'] === 'offline') { return null; } return [ 'id' => $user->id, 'name' => $user->name, 'avatar' => $user->avatar ?? null, 'guard' => $properties['guard'] ?? $guard, 'last_seen' => $activity->created_at, 'user_type' => $activity->subject_type, 'status' => $properties['status'] ?? 'online', ]; })->filter()->values(); return $onlineUsers; } catch (\Exception $e) { // Return empty collection on error \Log::error('PresenceService getOnlineUsers error: ' . $e->getMessage()); return collect([]); } }); } public function isUserOnline($user, $guard = 'web', $minutes = null) { $minutes = $minutes ?? self::ONLINE_THRESHOLD_MINUTES; $cacheKey = "presence_{$guard}_{$user->id}"; // Check cache first, but only for online status $cachedData = Cache::get($cacheKey); if ($cachedData && isset($cachedData['status']) && $cachedData['status'] === 'online') { return true; } // Get the most recent presence activity for this user $latestActivity = Activity::where('log_name', self::PRESENCE_ACTIVITY) ->where('subject_id', $user->id) ->where('subject_type', get_class($user)) ->where('properties->guard', $guard) ->where('created_at', '>=', now()->subMinutes($minutes)) ->latest() ->first(); if (!$latestActivity) { return false; } // Check if the latest activity is "offline" $properties = is_string($latestActivity->properties) ? json_decode($latestActivity->properties, true) : $latestActivity->properties; return isset($properties['status']) && $properties['status'] === 'online'; } public function getUserLastSeen($user, $guard = 'web') { $activity = Activity::where('log_name', self::PRESENCE_ACTIVITY) ->where('subject_id', $user->id) ->where('subject_type', get_class($user)) ->where('properties->guard', $guard) ->latest() ->first(); return $activity ? $activity->created_at : null; } public function getPresenceStats($guard = 'web') { $onlineUsers = $this->getOnlineUsers($guard); return [ 'online_count' => $onlineUsers->count(), 'online_users' => $onlineUsers, 'guard' => $guard, 'updated_at' => now(), ]; } /** * Clean up old presence records for a specific user, keeping only the latest, as defined in config */ protected function cleanupUserPresenceRecords($user, $guard = 'web') { $keepCount = timebank_config('presence_settings.keep_last_presence_updates', timebank_config('presence_settings.keep_last_presence_updates')); $userActivities = Activity::where('log_name', self::PRESENCE_ACTIVITY) ->where('causer_id', $user->id) ->where('causer_type', get_class($user)) ->where('properties->guard', $guard) ->orderBy('created_at', 'desc') ->get(); if ($userActivities->count() > $keepCount) { $toDelete = $userActivities->skip($keepCount)->pluck('id'); Activity::whereIn('id', $toDelete)->delete(); } } }