Initial commit
This commit is contained in:
220
app/Services/PresenceService.php
Normal file
220
app/Services/PresenceService.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
// 1. Enhanced Activity Log Service
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class PresenceService
|
||||
{
|
||||
public const PRESENCE_ACTIVITY = 'presence_update';
|
||||
public const ONLINE_THRESHOLD_MINUTES = 5;
|
||||
|
||||
public function updatePresence($user = null, $guard = null)
|
||||
{
|
||||
$user = $user ?? Auth::user();
|
||||
$guard = $guard ?? Auth::getDefaultDriver();
|
||||
|
||||
if (!$user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Log activity using Spatie
|
||||
activity(self::PRESENCE_ACTIVITY)
|
||||
->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user