Files
timebank-cc-public/app/Services/PresenceService.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

221 lines
7.7 KiB
PHP

<?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();
}
}
}