first init
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Modules\Auth\Entities\User;
|
||||
use Carbon\Carbon;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
|
||||
class OnlineUsersService
|
||||
{
|
||||
/**
|
||||
* Get all currently online users
|
||||
*
|
||||
* @param int $timeoutMinutes Minutes of inactivity to consider user offline
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getOnlineUsers(int $timeoutMinutes = 5)
|
||||
{
|
||||
$cacheKey = "online_users_{$timeoutMinutes}";
|
||||
|
||||
return Cache::remember($cacheKey, 30, function () use ($timeoutMinutes) {
|
||||
$timeoutSeconds = $timeoutMinutes * 60;
|
||||
$cutoffTime = now()->subSeconds($timeoutSeconds);
|
||||
|
||||
// Get active Sanctum tokens with user information
|
||||
$onlineTokens = PersonalAccessToken::with(['tokenable.unit', 'tokenable.rank', 'tokenable.position'])
|
||||
->where('tokenable_type', User::class)
|
||||
->where('last_used_at', '>', $cutoffTime)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->whereHas('tokenable', function ($query) {
|
||||
$query->whereNull('deleted_at');
|
||||
})
|
||||
->orderBy('last_used_at', 'desc')
|
||||
->get();
|
||||
|
||||
// Group by user to handle multiple tokens
|
||||
$onlineUsers = $onlineTokens->groupBy('tokenable_id')->map(function ($tokens) {
|
||||
$token = $tokens->first();
|
||||
$user = $token->tokenable;
|
||||
$latestToken = $tokens->sortByDesc('last_used_at')->first();
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'army_number' => $user->army_number,
|
||||
'image_url' => $user->image_url ? Storage::disk('public')->url($user->image_url) : null,
|
||||
'status' => $user->status,
|
||||
'unit_name' => $user->unit?->name,
|
||||
'rank_name' => $user->rank?->name,
|
||||
'position_name' => $user->position?->name,
|
||||
'ip_address' => 'N/A', // Sanctum doesn't store IP by default
|
||||
'user_agent' => $this->parseUserAgent('Sanctum Token'),
|
||||
'last_activity' => $latestToken->last_used_at?->timestamp ?? $latestToken->created_at->timestamp,
|
||||
'last_activity_human' => $latestToken->last_used_at?->diffForHumans() ?? $latestToken->created_at->diffForHumans(),
|
||||
'session_count' => $tokens->count(),
|
||||
'is_online' => true
|
||||
];
|
||||
});
|
||||
|
||||
return $onlineUsers->values();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users count
|
||||
*
|
||||
* @param int $timeoutMinutes Minutes of inactivity to consider user offline
|
||||
* @return int
|
||||
*/
|
||||
public function getOnlineUsersCount(int $timeoutMinutes = 5): int
|
||||
{
|
||||
$cacheKey = "online_users_count_{$timeoutMinutes}";
|
||||
|
||||
return Cache::remember($cacheKey, 30, function () use ($timeoutMinutes) {
|
||||
$timeoutSeconds = $timeoutMinutes * 60;
|
||||
$cutoffTime = now()->subSeconds($timeoutSeconds);
|
||||
|
||||
return PersonalAccessToken::where('tokenable_type', User::class)
|
||||
->where('last_used_at', '>', $cutoffTime)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->whereHas('tokenable', function ($query) {
|
||||
$query->whereNull('deleted_at');
|
||||
})
|
||||
->distinct('tokenable_id')
|
||||
->count('tokenable_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's current session information
|
||||
*
|
||||
* @param int $userId
|
||||
* @return array|null
|
||||
*/
|
||||
public function getUserSessionInfo(int $userId): ?array
|
||||
{
|
||||
$token = PersonalAccessToken::where('tokenable_type', User::class)
|
||||
->where('tokenable_id', $userId)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('expires_at')
|
||||
->orWhere('expires_at', '>', now());
|
||||
})
|
||||
->orderBy('last_used_at', 'desc')
|
||||
->first();
|
||||
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'session_id' => $token->id,
|
||||
'ip_address' => 'N/A', // Sanctum doesn't store IP by default
|
||||
'user_agent' => $this->parseUserAgent('Sanctum Token'),
|
||||
'last_activity' => $token->last_used_at?->timestamp ?? $token->created_at->timestamp,
|
||||
'last_activity_human' => $token->last_used_at?->diffForHumans() ?? $token->created_at->diffForHumans(),
|
||||
'is_online' => $token->last_used_at && $token->last_used_at->gt(now()->subMinutes(5))
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse user agent string to extract browser and OS info
|
||||
*
|
||||
* @param string $userAgent
|
||||
* @return array
|
||||
*/
|
||||
private function parseUserAgent(string $userAgent): array
|
||||
{
|
||||
$browser = 'Unknown';
|
||||
$os = 'Unknown';
|
||||
|
||||
// Simple browser detection
|
||||
if (strpos($userAgent, 'Chrome') !== false) {
|
||||
$browser = 'Chrome';
|
||||
} elseif (strpos($userAgent, 'Firefox') !== false) {
|
||||
$browser = 'Firefox';
|
||||
} elseif (strpos($userAgent, 'Safari') !== false) {
|
||||
$browser = 'Safari';
|
||||
} elseif (strpos($userAgent, 'Edge') !== false) {
|
||||
$browser = 'Edge';
|
||||
}
|
||||
|
||||
// Simple OS detection
|
||||
if (strpos($userAgent, 'Windows') !== false) {
|
||||
$os = 'Windows';
|
||||
} elseif (strpos($userAgent, 'Mac') !== false) {
|
||||
$os = 'macOS';
|
||||
} elseif (strpos($userAgent, 'Linux') !== false) {
|
||||
$os = 'Linux';
|
||||
} elseif (strpos($userAgent, 'Android') !== false) {
|
||||
$os = 'Android';
|
||||
} elseif (strpos($userAgent, 'iOS') !== false) {
|
||||
$os = 'iOS';
|
||||
}
|
||||
|
||||
return [
|
||||
'browser' => $browser,
|
||||
'os' => $os,
|
||||
'raw' => $userAgent
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear online users cache
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
Cache::forget('online_users_5');
|
||||
Cache::forget('online_users_10');
|
||||
Cache::forget('online_users_15');
|
||||
Cache::forget('online_users_30');
|
||||
Cache::forget('online_users_count_5');
|
||||
Cache::forget('online_users_count_10');
|
||||
Cache::forget('online_users_count_15');
|
||||
Cache::forget('online_users_count_30');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online users statistics
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOnlineUsersStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
// Get stats for different timeout periods
|
||||
$timeouts = [5, 10, 15, 30];
|
||||
|
||||
foreach ($timeouts as $timeout) {
|
||||
$stats["online_{$timeout}min"] = $this->getOnlineUsersCount($timeout);
|
||||
}
|
||||
|
||||
// Get total registered users
|
||||
$stats['total_users'] = User::count();
|
||||
|
||||
// Get users by status
|
||||
$stats['active_users'] = User::where('status', 'active')->count();
|
||||
$stats['inactive_users'] = User::where('status', 'inactive')->count();
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user