Files
2026-05-09 17:32:17 +02:00

256 lines
8.4 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Miscellaneous\WebsiteSetting;
use App\Models\RadioListenerPoint;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
readonly class PointsService
{
private const string POINTS_CACHE_KEY = 'user_points_';
private const string LEADERBOARD_CACHE_KEY = 'radio_leaderboard';
private const int LEADERBOARD_CACHE_DURATION = 300;
private const string SETTINGS_CACHE_KEY = 'points_service_settings';
private const int SETTINGS_CACHE_DURATION = 300;
private int $pointsPerMinute;
private int $maxPointsPerDay;
private int $requestPoints;
private int $votePoints;
private int $giveawayWinPoints;
private int $contestWinPoints;
public function __construct()
{
$settings = $this->getSettings();
$this->pointsPerMinute = (int) ($settings['points_per_minute'] ?? 1);
$this->maxPointsPerDay = (int) ($settings['max_points_per_day'] ?? 100);
$this->requestPoints = (int) ($settings['points_for_request'] ?? 5);
$this->votePoints = (int) ($settings['points_for_vote'] ?? 2);
$this->giveawayWinPoints = (int) ($settings['points_for_giveaway_win'] ?? 50);
$this->contestWinPoints = (int) ($settings['points_for_contest_win'] ?? 100);
}
public function awardPoints(User $user, int $points, string $reason): RadioListenerPoint
{
$pointRecord = RadioListenerPoint::awardPoints($user, $points, $reason);
$this->clearUserCache($user->id);
$this->clearLeaderboardCache();
return $pointRecord;
}
public function deductPoints(User $user, int $points, string $reason): RadioListenerPoint
{
$pointRecord = RadioListenerPoint::deductPoints($user, $points, $reason);
$this->clearUserCache($user->id);
$this->clearLeaderboardCache();
return $pointRecord;
}
public function getUserPoints(User $user): int
{
return Cache::remember(self::POINTS_CACHE_KEY . $user->id, 3600, fn (): int => (int) ($user->radio_points ?? 0));
}
/**
* @return array<int, array<string, mixed>>
*/
public function getUserHistory(User $user, int $limit = 50): array
{
return RadioListenerPoint::where('user_id', $user->id)
->orderBy('earned_at', 'desc')
->limit($limit)
->get()
->toArray();
}
/**
* @return array<int, array{rank: int, user_id: int, username: string, avatar: string|null, points: int}>
*/
public function getLeaderboard(int $limit = 100): array
{
return Cache::remember(self::LEADERBOARD_CACHE_KEY, self::LEADERBOARD_CACHE_DURATION, function () use ($limit): array {
/** @var Collection $collection */
$collection = User::where('radio_points', '>', 0)
->orderBy('radio_points', 'desc')
->limit($limit)
->get(['id', 'username', 'avatar', 'radio_points']);
return $collection->map(fn (User $user, int $index): array => [
'rank' => $index + 1,
'user_id' => $user->id,
'username' => $user->username,
'avatar' => $user->avatar ?? null,
'points' => (int) $user->radio_points,
])->toArray();
});
}
public function getWeeklyLeaderboard(int $limit = 50): array
{
return Cache::remember(self::LEADERBOARD_CACHE_KEY . '_weekly', self::LEADERBOARD_CACHE_DURATION, function () use ($limit) {
$weeklyPoints = RadioListenerPoint::where('earned_at', '>=', now()->subWeek())
->selectRaw('user_id, SUM(points) as total_points')
->groupBy('user_id')
->orderBy('total_points', 'desc')
->limit($limit)
->get();
$userIds = $weeklyPoints->pluck('user_id')->unique()->values();
$users = User::whereIn('id', $userIds)
->get(['id', 'username', 'avatar'])
->keyBy('id');
return $weeklyPoints->map(fn (object $item, int $index): array => [
'rank' => $index + 1,
'user_id' => $item->user_id,
'username' => $users[$item->user_id]?->username ?? 'Unknown',
'avatar' => $users[$item->user_id]?->avatar ?? null,
'points' => $item->total_points,
])->toArray();
});
}
public function getMonthlyLeaderboard(int $limit = 50): array
{
return Cache::remember(self::LEADERBOARD_CACHE_KEY . '_monthly', self::LEADERBOARD_CACHE_DURATION, function () use ($limit) {
$monthlyPoints = RadioListenerPoint::where('earned_at', '>=', now()->subMonth())
->selectRaw('user_id, SUM(points) as total_points')
->groupBy('user_id')
->orderBy('total_points', 'desc')
->limit($limit)
->get();
$userIds = $monthlyPoints->pluck('user_id')->unique()->values();
$users = User::whereIn('id', $userIds)
->get(['id', 'username', 'avatar'])
->keyBy('id');
return $monthlyPoints->map(fn (object $item, int $index): array => [
'rank' => $index + 1,
'user_id' => $item->user_id,
'username' => $users[$item->user_id]?->username ?? 'Unknown',
'avatar' => $users[$item->user_id]?->avatar ?? null,
'points' => $item->total_points,
])->toArray();
});
}
public function getUserRank(User $user): int
{
/** @var int $count */
$count = User::query()
->where('radio_points', '>', $user->radio_points ?? 0)
->count();
return $count + 1;
}
public function awardListeningPoints(User $user, int $minutes): int
{
$todayPoints = RadioListenerPoint::where('user_id', $user->id)
->where('earned_at', '>=', now()->startOfDay())
->sum('points');
$availablePoints = max(0, $this->maxPointsPerDay - $todayPoints);
$pointsToAward = min($minutes * $this->pointsPerMinute, $availablePoints);
if ($pointsToAward > 0) {
$this->awardPoints($user, $pointsToAward, 'Luisteren');
}
return $pointsToAward;
}
public function awardRequestPoints(User $user): int
{
$this->awardPoints($user, $this->requestPoints, 'Song Request');
return $this->requestPoints;
}
public function awardVotePoints(User $user): int
{
$this->awardPoints($user, $this->votePoints, 'Stemmen op Request');
return $this->votePoints;
}
public function awardWinPoints(User $user, string $type): int
{
$winPoints = match ($type) {
'giveaway' => $this->giveawayWinPoints,
'contest' => $this->contestWinPoints,
default => 10,
};
$this->awardPoints($user, $winPoints, 'Winst: ' . $type);
return $winPoints;
}
private function clearUserCache(int $userId): void
{
Cache::forget(self::POINTS_CACHE_KEY . $userId);
}
public function clearLeaderboardCache(): void
{
Cache::forget(self::LEADERBOARD_CACHE_KEY);
Cache::forget(self::LEADERBOARD_CACHE_KEY . '_weekly');
Cache::forget(self::LEADERBOARD_CACHE_KEY . '_monthly');
}
public function clearSettingsCache(): void
{
Cache::forget(self::SETTINGS_CACHE_KEY);
CacheService::clearPointsSettings();
}
public function getStats(): array
{
$totalPoints = RadioListenerPoint::sum('points');
$totalUsers = User::where('radio_points', '>', 0)->count();
return [
'total_points_awarded' => $totalPoints,
'total_active_users' => $totalUsers,
'points_per_minute' => $this->pointsPerMinute,
'max_points_per_day' => $this->maxPointsPerDay,
];
}
/**
* @return array<string, string|null>
*/
private function getSettings(): array
{
return Cache::remember(self::SETTINGS_CACHE_KEY, self::SETTINGS_CACHE_DURATION, fn (): array => WebsiteSetting::whereIn('key', [
'points_per_minute',
'max_points_per_day',
'points_for_request',
'points_for_vote',
'points_for_giveaway_win',
'points_for_contest_win',
])->pluck('value', 'key')->toArray());
}
}