You've already forked Atomcms-edit
Initial commit
This commit is contained in:
+608
@@ -0,0 +1,608 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Community;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Miscellaneous\WebsiteSetting;
|
||||
use App\Models\RadioApplication;
|
||||
use App\Models\RadioBanner;
|
||||
use App\Models\RadioHistory;
|
||||
use App\Models\RadioRank;
|
||||
use App\Models\RadioSchedule;
|
||||
use App\Models\RadioShout;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RadioController extends Controller
|
||||
{
|
||||
private ?bool $azureCastDetected = null;
|
||||
|
||||
/** @var array<mixed> */
|
||||
private array $settingsCache = [];
|
||||
|
||||
/**
|
||||
* Get multiple settings at once (batched query)
|
||||
*
|
||||
* @param array<string> $keys
|
||||
*
|
||||
* @return array<string|null>
|
||||
*/
|
||||
private function getSettings(array $keys): array
|
||||
{
|
||||
$cacheKey = 'radio_settings_' . md5(implode(',', $keys));
|
||||
|
||||
return Cache::remember($cacheKey, 60, function () use ($keys): array {
|
||||
/** @var Collection $collection */
|
||||
$collection = WebsiteSetting::whereIn('key', $keys)
|
||||
->pluck('value', 'key');
|
||||
|
||||
return $collection->all();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single setting with caching
|
||||
*/
|
||||
private function getSetting(string $key, mixed $default = null): mixed
|
||||
{
|
||||
if (! isset($this->settingsCache[$key])) {
|
||||
$this->settingsCache[$key] = Cache::remember("setting_{$key}", 60, function () use ($key): mixed {
|
||||
/** @var WebsiteSetting|null $setting */
|
||||
$setting = WebsiteSetting::where('key', $key)->first();
|
||||
|
||||
return $setting?->value;
|
||||
});
|
||||
}
|
||||
|
||||
return $this->settingsCache[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
// Batch load all radio settings at once
|
||||
$settings = $this->getSettings([
|
||||
'radio_enabled',
|
||||
'radio_stream_url',
|
||||
'radio_current_dj_id',
|
||||
]);
|
||||
|
||||
if (! (bool) ($settings['radio_enabled'] ?? false)) {
|
||||
return view('community.radio.disabled');
|
||||
}
|
||||
|
||||
// Load all data with single queries each
|
||||
$ranks = Cache::remember('radio_ranks', 300, fn () => RadioRank::all());
|
||||
|
||||
$banners = Cache::remember('radio_banners_active', 60, fn () => RadioBanner::with('user:id,username,look')
|
||||
->active()
|
||||
->ordered()
|
||||
->take(10)
|
||||
->get());
|
||||
|
||||
$todaySchedule = Cache::remember('radio_schedule_today', 60, fn () => RadioSchedule::with('user:id,username,look')
|
||||
->active()
|
||||
->today()
|
||||
->orderBy('start_time')
|
||||
->get());
|
||||
|
||||
$currentDJ = $this->getCurrentDJFromSchedule();
|
||||
|
||||
$streamUrl = $this->formatStreamUrl($settings['radio_stream_url'] ?? '');
|
||||
|
||||
// Cache stream status check (30 seconds)
|
||||
$isOnline = Cache::remember('radio_stream_status', 30, fn () => $this->checkStreamOnline($streamUrl));
|
||||
|
||||
return view('community.radio.index', ['ranks' => $ranks, 'banners' => $banners, 'todaySchedule' => $todaySchedule, 'currentDJ' => $currentDJ, 'isOnline' => $isOnline, 'streamUrl' => $streamUrl]);
|
||||
}
|
||||
|
||||
public function rooster(): View
|
||||
{
|
||||
$schedule = Cache::remember('radio_schedule_all', 300, fn () => RadioSchedule::with('user:id,username,look')
|
||||
->active()
|
||||
->ordered()
|
||||
->get()
|
||||
->groupBy('day'));
|
||||
|
||||
return view('community.radio.rooster', ['schedule' => $schedule]);
|
||||
}
|
||||
|
||||
public function shouts(): RedirectResponse|View
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_shouts_enabled')) {
|
||||
return redirect()->route('radio.index')->with('error', __('radio.shouts_disabled'));
|
||||
}
|
||||
|
||||
return view('community.radio.shouts');
|
||||
}
|
||||
|
||||
public function apply(): RedirectResponse|View
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_applications_enabled')) {
|
||||
return redirect()->route('radio.index')->with('error', __('radio.applications_closed'));
|
||||
}
|
||||
|
||||
$userId = auth()->id();
|
||||
|
||||
$hasPendingApplication = $userId && RadioApplication::where('user_id', $userId)
|
||||
->where('status', 'pending')
|
||||
->exists();
|
||||
|
||||
$ranks = Cache::remember('radio_ranks_accepting', 300, fn () => RadioRank::where('accepts_applications', true)->get());
|
||||
|
||||
return view('community.radio.apply', ['ranks' => $ranks, 'hasPendingApplication' => $hasPendingApplication]);
|
||||
}
|
||||
|
||||
public function storeApplication(Request $request): RedirectResponse
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_applications_enabled')) {
|
||||
return redirect()->route('radio.index')->with('error', __('radio.applications_closed'));
|
||||
}
|
||||
|
||||
/** @var int|string $userId */
|
||||
$userId = auth()->id();
|
||||
|
||||
$hasPendingApplication = RadioApplication::where('user_id', $userId)
|
||||
->where('status', 'pending')
|
||||
->exists();
|
||||
|
||||
if ($hasPendingApplication) {
|
||||
return back()->withErrors([
|
||||
'general' => __('radio.application_pending'),
|
||||
])->withInput();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'real_name' => ['required', 'string', 'max:255'],
|
||||
'age' => ['required', 'integer', 'min:13', 'max:100'],
|
||||
'availability' => ['required', 'string', 'min:10'],
|
||||
'experience' => ['nullable', 'string'],
|
||||
'motivation' => ['required', 'string', 'min:50'],
|
||||
'music_style' => ['nullable', 'string', 'max:500'],
|
||||
'discord_username' => ['nullable', 'string', 'max:255'],
|
||||
'rank_id' => ['nullable', 'exists:radio_ranks,id'],
|
||||
]);
|
||||
|
||||
// Check rank accepts applications (cached)
|
||||
if ($validated['rank_id']) {
|
||||
$rank = Cache::remember("radio_rank_{$validated['rank_id']}", 300, fn () => RadioRank::find($validated['rank_id']));
|
||||
|
||||
if (! $rank || ! $rank->accepts_applications) {
|
||||
return back()->withErrors([
|
||||
'rank_id' => __('radio.rank_not_accepting', ['rank' => $rank?->name ?? 'Deze rank']),
|
||||
])->withInput();
|
||||
}
|
||||
}
|
||||
|
||||
RadioApplication::create([
|
||||
'user_id' => $userId,
|
||||
'real_name' => $validated['real_name'],
|
||||
'age' => $validated['age'],
|
||||
'availability' => $validated['availability'],
|
||||
'experience' => $validated['experience'] ?? null,
|
||||
'motivation' => $validated['motivation'],
|
||||
'music_style' => $validated['music_style'] ?? null,
|
||||
'discord_username' => $validated['discord_username'] ?? null,
|
||||
'rank_id' => $validated['rank_id'] ?? null,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
return redirect()->route('radio.apply')->with('success', __('radio.application_sent'));
|
||||
}
|
||||
|
||||
public function storeShout(Request $request): RedirectResponse
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_shouts_enabled')) {
|
||||
return redirect()->route('radio.index')->with('error', __('radio.shouts_disabled'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'message' => ['required', 'string', 'max:280'],
|
||||
]);
|
||||
|
||||
RadioShout::create([
|
||||
'user_id' => auth()->id(),
|
||||
'message' => $validated['message'],
|
||||
]);
|
||||
|
||||
// Clear shouts cache
|
||||
Cache::forget('radio_shouts_recent');
|
||||
|
||||
return redirect()->route('radio.shouts')->with('success', __('radio.shout_sent'));
|
||||
}
|
||||
|
||||
public function nowPlaying(): JsonResponse
|
||||
{
|
||||
// Cache now playing for 10 seconds to reduce API calls
|
||||
$nowPlaying = Cache::remember('radio_nowplaying', 10, fn () => $this->getNowPlaying());
|
||||
|
||||
return response()->json($nowPlaying);
|
||||
}
|
||||
|
||||
public function listeners(): JsonResponse
|
||||
{
|
||||
// Cache listeners count for 30 seconds
|
||||
$count = Cache::remember('radio_listeners', 30, fn () => $this->getListenersCount());
|
||||
|
||||
return response()->json(['count' => $count]);
|
||||
}
|
||||
|
||||
public function currentDJ(): JsonResponse
|
||||
{
|
||||
$dj = $this->getCurrentDJFromSchedule();
|
||||
|
||||
return response()->json([
|
||||
'dj' => $dj,
|
||||
'is_live' => $dj !== null,
|
||||
]);
|
||||
}
|
||||
|
||||
private function getCurrentDJFromSchedule(): ?array
|
||||
{
|
||||
$manualDjId = $this->getSetting('radio_current_dj_id');
|
||||
|
||||
if (! empty($manualDjId)) {
|
||||
$dj = Cache::remember("user_{$manualDjId}", 60, fn () => User::find($manualDjId));
|
||||
|
||||
if ($dj) {
|
||||
return [
|
||||
'username' => $dj->username,
|
||||
'look' => $dj->look,
|
||||
'show_name' => 'Live DJ',
|
||||
'is_manual' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$currentSlot = RadioSchedule::with('user:id,username,look')
|
||||
->active()
|
||||
->where('day', $this->getCurrentDay())
|
||||
->whereTime('start_time', '<=', now()->format('H:i:s'))
|
||||
->whereTime('end_time', '>=', now()->format('H:i:s'))
|
||||
->first();
|
||||
|
||||
if ($currentSlot?->user) {
|
||||
return [
|
||||
'username' => $currentSlot->user->username,
|
||||
'look' => $currentSlot->user->look,
|
||||
'show_name' => $currentSlot->show_name,
|
||||
'start_time' => $currentSlot->start_time->format('H:i'),
|
||||
'end_time' => $currentSlot->end_time->format('H:i'),
|
||||
'is_manual' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getNowPlaying(): array
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_now_playing_enabled')) {
|
||||
return ['enabled' => false, 'song' => null];
|
||||
}
|
||||
|
||||
$apiUrl = $this->getSetting('radio_now_playing_api_url') ?: $this->getAzureCastApiUrl();
|
||||
|
||||
if (! $apiUrl) {
|
||||
return ['enabled' => true, 'song' => null, 'artist' => null];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(5)->get($apiUrl);
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
if (isset($data['now_playing'])) {
|
||||
$song = $data['now_playing']['song'] ?? $data['now_playing'];
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'song' => $song['title'] ?? $song['text'] ?? null,
|
||||
'artist' => $song['artist'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => true,
|
||||
'song' => $data['song'] ?? $data['title'] ?? $data['now_playing'] ?? null,
|
||||
'artist' => $data['artist'] ?? null,
|
||||
];
|
||||
}
|
||||
} catch (\Exception) {
|
||||
// Silent fail
|
||||
}
|
||||
|
||||
return ['enabled' => true, 'song' => null, 'artist' => null];
|
||||
}
|
||||
|
||||
private function getAzureCastApiUrl(): ?string
|
||||
{
|
||||
$baseUrl = $this->getSetting('radio_azurecast_base_url');
|
||||
$stationId = $this->getSetting('radio_azurecast_station_id', '1');
|
||||
|
||||
if (! empty($baseUrl)) {
|
||||
return rtrim((string) $baseUrl, '/') . '/api/nowplaying/' . $stationId;
|
||||
}
|
||||
|
||||
$streamUrl = $this->getSetting('radio_stream_url');
|
||||
if (! $streamUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parsed = parse_url((string) $streamUrl);
|
||||
if (! $parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$scheme = $parsed['scheme'] ?? 'https';
|
||||
$host = $parsed['host'] ?? '';
|
||||
|
||||
return $scheme . '://' . $host . '/api/nowplaying/' . $stationId;
|
||||
}
|
||||
|
||||
private function getListenersCount(): int
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_listeners_enabled')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$apiUrl = $this->getSetting('radio_listeners_api_url') ?: $this->getAzureCastApiUrl();
|
||||
|
||||
if (! $apiUrl) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(5)->get($apiUrl);
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
return $data['listeners']['total']
|
||||
?? $data['listeners']['current']
|
||||
?? $data['listeners']
|
||||
?? $data['count']
|
||||
?? $data['total']
|
||||
?? 0;
|
||||
}
|
||||
} catch (\Exception) {
|
||||
// Silent fail
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getCurrentDay(): string
|
||||
{
|
||||
return strtolower(now()->format('l'));
|
||||
}
|
||||
|
||||
private function formatStreamUrl(string $url): string
|
||||
{
|
||||
if ($url === '' || $url === '0') {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = str_replace('http://', 'https://', $url);
|
||||
|
||||
if (preg_match('/^(https?:\/\/[^\/]+):(\d+)\/(.+)$/', $url, $matches)) {
|
||||
$baseUrl = $matches[1];
|
||||
$port = $matches[2];
|
||||
$path = $matches[3];
|
||||
|
||||
if (in_array($port, ['8000', '8010', '8020', '8030', '8040', '8050'])) {
|
||||
return $baseUrl . '/radio/' . $port . '/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
private function checkStreamOnline(string $streamUrl): bool
|
||||
{
|
||||
if ($streamUrl === '' || $streamUrl === '0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return Http::timeout(2)->withOptions(['verify' => false])->head($streamUrl)->successful();
|
||||
} catch (\Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function detectAzureCast(): bool
|
||||
{
|
||||
if ($this->azureCastDetected !== null) {
|
||||
return $this->azureCastDetected;
|
||||
}
|
||||
|
||||
$baseUrl = $this->getSetting('radio_azurecast_base_url');
|
||||
if (! empty($baseUrl)) {
|
||||
$this->azureCastDetected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$streamUrl = $this->getSetting('radio_stream_url', '');
|
||||
if (empty($streamUrl)) {
|
||||
$this->azureCastDetected = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$parsed = parse_url((string) $streamUrl);
|
||||
if (! $parsed) {
|
||||
$this->azureCastDetected = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$scheme = $parsed['scheme'] ?? 'https';
|
||||
$host = $parsed['host'] ?? '';
|
||||
$testUrl = $scheme . '://' . $host . '/api/nowplaying';
|
||||
|
||||
try {
|
||||
$response = Http::timeout(3)->get($testUrl);
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
if (is_array($data) && (isset($data[0]['station']) || isset($data['station']))) {
|
||||
$this->azureCastDetected = true;
|
||||
|
||||
$stationId = $data[0]['station']['id'] ?? $data['station']['id'] ?? 1;
|
||||
$detectedBaseUrl = $scheme . '://' . $host;
|
||||
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_azurecast_base_url'],
|
||||
['value' => $detectedBaseUrl, 'comment' => 'Auto-detected AzureCast'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_azurecast_station_id'],
|
||||
['value' => $stationId, 'comment' => 'Auto-detected Station ID'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_now_playing_enabled'],
|
||||
['value' => '1', 'comment' => 'Auto-enabled'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_listeners_enabled'],
|
||||
['value' => '1', 'comment' => 'Auto-enabled'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_show_current_dj'],
|
||||
['value' => '1', 'comment' => 'Auto-enabled'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_widget_enabled'],
|
||||
['value' => '1', 'comment' => 'Auto-enabled'],
|
||||
);
|
||||
WebsiteSetting::updateOrCreate(
|
||||
['key' => 'radio_widget_show_globally'],
|
||||
['value' => '1', 'comment' => 'Auto-enabled'],
|
||||
);
|
||||
|
||||
$this->settingsCache = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (\Exception) {
|
||||
// Not AzureCast
|
||||
}
|
||||
|
||||
$this->azureCastDetected = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function config(): JsonResponse
|
||||
{
|
||||
$settings = $this->getSettings([
|
||||
'radio_enabled',
|
||||
'radio_stream_url',
|
||||
'radio_style',
|
||||
'radio_now_playing_enabled',
|
||||
'radio_listeners_enabled',
|
||||
'radio_show_current_dj',
|
||||
'radio_widget_enabled',
|
||||
'radio_widget_show_globally',
|
||||
'radio_widget_position',
|
||||
]);
|
||||
|
||||
$streamUrl = $this->formatStreamUrl($settings['radio_stream_url'] ?? '');
|
||||
$isAzurecast = $this->detectAzureCast();
|
||||
|
||||
return response()->json([
|
||||
'enabled' => (bool) ($settings['radio_enabled'] ?? false),
|
||||
'stream_url' => $streamUrl,
|
||||
'style' => $settings['radio_style'] ?? 'dark',
|
||||
'dj' => $this->getCurrentDJFromSchedule(),
|
||||
'now_playing_enabled' => (bool) ($settings['radio_now_playing_enabled'] ?? false),
|
||||
'listeners_enabled' => (bool) ($settings['radio_listeners_enabled'] ?? false),
|
||||
'show_current_dj' => (bool) ($settings['radio_show_current_dj'] ?? false),
|
||||
'widget_enabled' => (bool) ($settings['radio_widget_enabled'] ?? false),
|
||||
'widget_show_globally' => (bool) ($settings['radio_widget_show_globally'] ?? false),
|
||||
'widget_position' => $settings['radio_widget_position'] ?? 'bottom-right',
|
||||
'is_azurecast' => $isAzurecast,
|
||||
'azurecast_detected' => $isAzurecast,
|
||||
]);
|
||||
}
|
||||
|
||||
public function startSession(Request $request): JsonResponse
|
||||
{
|
||||
$userId = auth()->id();
|
||||
|
||||
$activeSession = RadioHistory::where('user_id', $userId)
|
||||
->whereNull('ended_at')
|
||||
->first();
|
||||
|
||||
if ($activeSession) {
|
||||
return response()->json([
|
||||
'error' => 'Je hebt al een actieve sessie',
|
||||
'session_id' => $activeSession->id,
|
||||
], 400);
|
||||
}
|
||||
|
||||
$session = RadioHistory::create([
|
||||
'user_id' => $userId,
|
||||
'show_name' => $request->input('show_name'),
|
||||
'started_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Sessie gestart',
|
||||
'session_id' => $session->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function endSession(): JsonResponse
|
||||
{
|
||||
$userId = auth()->id();
|
||||
|
||||
$activeSession = RadioHistory::where('user_id', $userId)
|
||||
->whereNull('ended_at')
|
||||
->first();
|
||||
|
||||
if (! $activeSession) {
|
||||
return response()->json([
|
||||
'error' => 'Geen actieve sessie gevonden',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$activeSession->endSession();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Sessie beëindigd',
|
||||
'duration' => $activeSession->duration,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getShouts(): JsonResponse
|
||||
{
|
||||
if (! (bool) $this->getSetting('radio_shouts_enabled')) {
|
||||
return response()->json([
|
||||
'error' => 'Shouts zijn uitgeschakeld',
|
||||
'shouts' => [],
|
||||
], 403);
|
||||
}
|
||||
|
||||
$shouts = Cache::remember('radio_shouts_recent', 30, fn () => RadioShout::with('user:id,username')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(50)
|
||||
->get()
|
||||
->map(fn ($shout) => [
|
||||
'id' => $shout->id,
|
||||
'username' => $shout->user?->username ?? 'Anoniem',
|
||||
'message' => $shout->message,
|
||||
'created_at' => $shout->created_at->diffForHumans(),
|
||||
]));
|
||||
|
||||
return response()->json([
|
||||
'shouts' => $shouts,
|
||||
'total' => $shouts->count(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user