You've already forked Atomcms-edit
Medium priority fixes: CORS from env, shared HasRadioSettings trait, lazy RconService, validated() fixes, LogoGenerator hardening, DB indexes, user profile consistency, radio rank N+1 fix
This commit is contained in:
@@ -227,15 +227,21 @@ class HotelApiController extends Controller
|
|||||||
public function userProfile(string $username): JsonResponse
|
public function userProfile(string $username): JsonResponse
|
||||||
{
|
{
|
||||||
$user = User::where('username', $username)
|
$user = User::where('username', $username)
|
||||||
->firstOrFail();
|
->first();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json(['data' => null], 404);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
'data' => [
|
||||||
'id' => $user->id,
|
'id' => $user->id,
|
||||||
'username' => $user->username,
|
'username' => $user->username,
|
||||||
'look' => $user->look,
|
'look' => $user->look,
|
||||||
'motto' => $user->motto,
|
'motto' => $user->motto,
|
||||||
'account_created' => $user->account_created,
|
'account_created' => $user->account_created,
|
||||||
'online' => false,
|
'online' => false,
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,9 +296,11 @@ class HotelApiController extends Controller
|
|||||||
->where('user_id', $request->user()->id)
|
->where('user_id', $request->user()->id)
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
$reply = $ticket->replies()->create([
|
$reply = $ticket->replies()->create([
|
||||||
'user_id' => $request->user()->id,
|
'user_id' => $request->user()->id,
|
||||||
'message' => $request->input('message'),
|
'message' => $validated['message'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
|
return response()->json(['data' => $reply->load('user:id,username,look')], 201);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Community;
|
|||||||
|
|
||||||
use App\Enums\RadioSettings;
|
use App\Enums\RadioSettings;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||||
use App\Models\Miscellaneous\WebsiteSetting;
|
use App\Models\Miscellaneous\WebsiteSetting;
|
||||||
use App\Models\RadioApplication;
|
use App\Models\RadioApplication;
|
||||||
use App\Models\RadioBanner;
|
use App\Models\RadioBanner;
|
||||||
@@ -20,6 +21,7 @@ use Illuminate\View\View;
|
|||||||
|
|
||||||
class RadioController extends Controller
|
class RadioController extends Controller
|
||||||
{
|
{
|
||||||
|
use HasRadioSettings;
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly RadioStreamService $streamService,
|
private readonly RadioStreamService $streamService,
|
||||||
private readonly RadioScheduleService $scheduleService,
|
private readonly RadioScheduleService $scheduleService,
|
||||||
@@ -120,7 +122,8 @@ class RadioController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validated['rank_id']) {
|
if ($validated['rank_id']) {
|
||||||
$rank = Cache::remember("radio_rank_{$validated['rank_id']}", 300, fn () => RadioRank::find($validated['rank_id']));
|
$acceptingRanks = Cache::remember('radio_ranks_accepting', 300, fn () => RadioRank::where('accepts_applications', true)->get()->keyBy('id'));
|
||||||
|
$rank = $acceptingRanks->get((int) $validated['rank_id']);
|
||||||
|
|
||||||
if (! $rank || ! $rank->accepts_applications) {
|
if (! $rank || ! $rank->accepts_applications) {
|
||||||
return back()->withErrors([
|
return back()->withErrors([
|
||||||
@@ -336,13 +339,4 @@ class RadioController extends Controller
|
|||||||
return WebsiteSetting::whereIn('key', $stringKeys)->pluck('value', 'key')->all();
|
return WebsiteSetting::whereIn('key', $stringKeys)->pluck('value', 'key')->all();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSetting(RadioSettings $setting, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
return Cache::remember("setting_{$setting->value}", 60, function () use ($setting, $default): mixed {
|
|
||||||
$websiteSetting = WebsiteSetting::where('key', $setting->value)->first();
|
|
||||||
|
|
||||||
return $websiteSetting?->value ?? $default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Concerns;
|
||||||
|
|
||||||
|
use App\Enums\RadioSettings;
|
||||||
|
use App\Models\Miscellaneous\WebsiteSetting;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
trait HasRadioSettings
|
||||||
|
{
|
||||||
|
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
||||||
|
|
||||||
|
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
||||||
|
$setting = WebsiteSetting::where('key', $keyStr)->first();
|
||||||
|
|
||||||
|
return $setting?->value ?? $default;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use App\Models\Miscellaneous\WebsiteSetting;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
|
|
||||||
class LogoGeneratorController extends Controller
|
class LogoGeneratorController extends Controller
|
||||||
@@ -24,9 +25,25 @@ class LogoGeneratorController extends Controller
|
|||||||
|
|
||||||
public function store(Request $request): JsonResponse
|
public function store(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$request->validate(['logo' => 'required|image|mimes:jpeg,png,gif,webp|max:5120']);
|
$request->validate([
|
||||||
|
'logo' => [
|
||||||
|
'required',
|
||||||
|
'image',
|
||||||
|
'mimes:jpeg,png,gif,webp',
|
||||||
|
'max:5120',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
$path = $request->file('logo')->store('generated-logos', 'public');
|
$file = $request->file('logo');
|
||||||
|
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $file->getPathname());
|
||||||
|
$allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||||
|
|
||||||
|
if (! in_array($mime, $allowedMimes, true)) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Invalid file type.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = 'logo_' . Str::random(16) . '.' . $file->getClientOriginalExtension();
|
||||||
|
$path = $file->storeAs('generated-logos', $filename, 'public');
|
||||||
|
|
||||||
$setting = WebsiteSetting::where('key', 'cms_logo')->first();
|
$setting = WebsiteSetting::where('key', 'cms_logo')->first();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
|
|||||||
|
|
||||||
use App\Enums\RadioSettings;
|
use App\Enums\RadioSettings;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Miscellaneous\WebsiteSetting;
|
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||||
use App\Services\Community\RadioStreamService;
|
use App\Services\Community\RadioStreamService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -15,6 +15,7 @@ use Illuminate\View\View;
|
|||||||
|
|
||||||
class EmbedController extends Controller
|
class EmbedController extends Controller
|
||||||
{
|
{
|
||||||
|
use HasRadioSettings;
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly RadioStreamService $streamService,
|
private readonly RadioStreamService $streamService,
|
||||||
) {}
|
) {}
|
||||||
@@ -53,14 +54,4 @@ class EmbedController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
|
||||||
|
|
||||||
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
|
||||||
$websiteSetting = WebsiteSetting::where('key', $keyStr)->first();
|
|
||||||
|
|
||||||
return $websiteSetting?->value ?? $default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace App\Http\Controllers\Radio;
|
|||||||
|
|
||||||
use App\Enums\RadioSettings;
|
use App\Enums\RadioSettings;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Miscellaneous\WebsiteSetting;
|
use App\Http\Controllers\Concerns\HasRadioSettings;
|
||||||
use App\Services\Community\RadioScheduleService;
|
use App\Services\Community\RadioScheduleService;
|
||||||
use App\Services\Community\RadioStreamService;
|
use App\Services\Community\RadioStreamService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -15,6 +15,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
|
|||||||
|
|
||||||
class SseController extends Controller
|
class SseController extends Controller
|
||||||
{
|
{
|
||||||
|
use HasRadioSettings;
|
||||||
private const int SSE_KEEPALIVE = 15;
|
private const int SSE_KEEPALIVE = 15;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -179,15 +180,4 @@ class SseController extends Controller
|
|||||||
|
|
||||||
return rtrim($baseUrl, '/') . '/api/nowplaying/' . $stationId;
|
return rtrim($baseUrl, '/') . '/api/nowplaying/' . $stationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getSetting(RadioSettings|string $key, mixed $default = null): mixed
|
|
||||||
{
|
|
||||||
$keyStr = $key instanceof RadioSettings ? $key->value : $key;
|
|
||||||
|
|
||||||
return Cache::remember("setting_{$keyStr}", 60, function () use ($keyStr, $default): mixed {
|
|
||||||
$setting = WebsiteSetting::where('key', $keyStr)->first();
|
|
||||||
|
|
||||||
return $setting?->value ?? $default;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,13 +40,15 @@ class AccountSettingsController extends Controller
|
|||||||
return redirect()->back()->withErrors('User not found');
|
return redirect()->back()->withErrors('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->mail !== $request->input('mail')) {
|
$validated = $request->validated();
|
||||||
$this->userService->updateField($user, 'mail', $request->input('mail'));
|
|
||||||
|
if ($user->mail !== $validated['mail']) {
|
||||||
|
$this->userService->updateField($user, 'mail', $validated['mail']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->motto !== $request->input('motto')) {
|
if ($user->motto !== $validated['motto']) {
|
||||||
$this->rconService->setMotto($user, $request->input('motto'));
|
$this->rconService->setMotto($user, $validated['motto']);
|
||||||
$this->userService->updateField($user, 'motto', $request->input('motto'));
|
$this->userService->updateField($user, 'motto', $validated['motto']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('settings.account.show')->with('success', __('Your account settings has been updated'));
|
return redirect()->route('settings.account.show')->with('success', __('Your account settings has been updated'));
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ class GuestbookController extends Controller
|
|||||||
{
|
{
|
||||||
$this->validateGuestbookPost($user, $request);
|
$this->validateGuestbookPost($user, $request);
|
||||||
|
|
||||||
|
$validated = $request->validated();
|
||||||
|
|
||||||
$user->profileGuestbook()->create([
|
$user->profileGuestbook()->create([
|
||||||
'user_id' => Auth::id(),
|
'user_id' => Auth::id(),
|
||||||
'message' => $request->input('message'),
|
'message' => $validated['message'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->back()->with('success', __('Your message has been posted.'));
|
return redirect()->back()->with('success', __('Your message has been posted.'));
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ class RconService
|
|||||||
'ip' => setting('rcon_ip'),
|
'ip' => setting('rcon_ip'),
|
||||||
'port' => (int) setting('rcon_port'),
|
'port' => (int) setting('rcon_port'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function initialize(): void
|
public function connect(): bool
|
||||||
{
|
{
|
||||||
|
if ($this->isConnected) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
$this->socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
|
||||||
if ($this->socket === false) {
|
if ($this->socket === false) {
|
||||||
@@ -40,7 +42,7 @@ class RconService
|
|||||||
Log::error("RCON initialization failed: {$error}");
|
Log::error("RCON initialization failed: {$error}");
|
||||||
$this->closeConnection();
|
$this->closeConnection();
|
||||||
|
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) {
|
if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) {
|
||||||
@@ -48,10 +50,17 @@ class RconService
|
|||||||
Log::error("RCON connection failed: {$error}");
|
Log::error("RCON connection failed: {$error}");
|
||||||
$this->closeConnection();
|
$this->closeConnection();
|
||||||
|
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->isConnected = true;
|
$this->isConnected = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initialize(): void
|
||||||
|
{
|
||||||
|
$this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function closeConnection(): void
|
private function closeConnection(): void
|
||||||
@@ -66,6 +75,10 @@ class RconService
|
|||||||
|
|
||||||
public function isConnected(): bool
|
public function isConnected(): bool
|
||||||
{
|
{
|
||||||
|
if (! $this->isConnected) {
|
||||||
|
$this->connect();
|
||||||
|
}
|
||||||
|
|
||||||
return $this->isConnected;
|
return $this->isConnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -21,11 +21,11 @@ return [
|
|||||||
|
|
||||||
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
|
'allowed_methods' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_METHODS', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'))), fn ($v) => $v !== ''),
|
||||||
|
|
||||||
'allowed_origins' => ['*'], // Zorgt ervoor dat alle origins (zoals je client/CMS) de imaging mogen inladen
|
'allowed_origins' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_ORIGINS', '*'))), fn ($v) => $v !== ''),
|
||||||
|
|
||||||
'allowed_origins_patterns' => [],
|
'allowed_origins_patterns' => [],
|
||||||
|
|
||||||
'allowed_headers' => ['*'], // Flexibel instellen zodat er geen headers geblokkeerd worden
|
'allowed_headers' => array_filter(array_map(trim(...), explode(',', (string) env('CORS_ALLOWED_HEADERS', '*'))), fn ($v) => $v !== ''),
|
||||||
|
|
||||||
'exposed_headers' => [],
|
'exposed_headers' => [],
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->index('username', 'idx_users_username');
|
||||||
|
$table->index('mail', 'idx_users_mail');
|
||||||
|
$table->index('ip_current', 'idx_users_ip_current');
|
||||||
|
$table->index('ip_register', 'idx_users_ip_register');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('camera_web', function (Blueprint $table) {
|
||||||
|
$table->index('visible', 'idx_camera_web_visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('website_shop_articles', function (Blueprint $table) {
|
||||||
|
$table->index('category_id', 'idx_website_shop_articles_category_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropIndex('idx_users_username');
|
||||||
|
$table->dropIndex('idx_users_mail');
|
||||||
|
$table->dropIndex('idx_users_ip_current');
|
||||||
|
$table->dropIndex('idx_users_ip_register');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('camera_web', function (Blueprint $table) {
|
||||||
|
$table->dropIndex('idx_camera_web_visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('website_shop_articles', function (Blueprint $table) {
|
||||||
|
$table->dropIndex('idx_website_shop_articles_category_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user