🆙 More fixes 🆙

This commit is contained in:
Remco
2026-01-19 20:43:46 +01:00
parent deed2158ca
commit 7b9849c159
77 changed files with 1084 additions and 13612 deletions
@@ -8,9 +8,13 @@ use Illuminate\Database\Eloquent\Collection;
class ArticleService
{
public function getArticles(bool $paginate = false, int $perPage = 8): array|Collection|LengthAwarePaginator
/**
* @return Collection<int, WebsiteArticle>|LengthAwarePaginator<int, WebsiteArticle>
*/
public function getArticles(bool $paginate = false, int $perPage = 8): Collection|LengthAwarePaginator
{
$query = WebsiteArticle::with(['user' => function ($query): void {
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query->select('id', 'username', 'look');
}])->orderByDesc('id');
@@ -9,11 +9,14 @@ use Illuminate\Http\Request;
class ReactionService
{
/**
* @return array{success: bool, added?: bool, username?: string}
*/
public function toggleReaction(WebsiteArticle $article, User $user, Request $request): array
{
$reaction = $request->get('reaction');
if (! is_string($reaction) || ! in_array($reaction, config('habbo.reactions'))) {
if (! is_string($reaction) || ! in_array($reaction, (array) config('habbo.reactions'))) {
return ['success' => false];
}
@@ -29,7 +32,7 @@ class ReactionService
return [
'success' => true,
'added' => $existingReaction?->active ?? true,
'added' => $existingReaction ? $existingReaction->active : true,
'username' => $user->username,
];
}
@@ -8,12 +8,18 @@ use Illuminate\Database\Eloquent\Collection;
class RareValueCategoriesService
{
/**
* @return Collection<int, WebsiteRareValueCategory>
*/
public function fetchAllCategories(): Collection
{
return WebsiteRareValueCategory::all();
}
public function fetchCategoriesByPriority(): Builder|Collection
/**
* @return Collection<int, WebsiteRareValueCategory>
*/
public function fetchCategoriesByPriority(): Collection
{
return WebsiteRareValueCategory::orderBy('priority')->with('furniture')->get();
}
@@ -23,12 +29,16 @@ class RareValueCategoriesService
return WebsiteRareValueCategory::orderBy('priority')->whereId($id)->with('furniture')->first();
}
/**
* @return Collection<int, WebsiteRareValueCategory>
*/
public function searchCategories(string $searchTerm): Collection
{
return WebsiteRareValueCategory::orderBy('priority')->whereHas('furniture', function ($query) use ($searchTerm): void {
$query->where('name', 'like', '%' . $searchTerm . '%');
})
->with(['furniture' => function ($query) use ($searchTerm): void {
/** @var Builder $query */
$query->where('name', 'like', '%' . $searchTerm . '%');
}])
->get();
@@ -16,17 +16,20 @@ class StaffApplicationService
]);
}
/**
* @return Collection<int, WebsiteOpenPosition>
*/
public function fetchOpenPositions(): Collection
{
return WebsiteOpenPosition::canApply()->with('permission')->get();
}
public function hasUserAppliedForPosition($user, $positionId): bool
public function hasUserAppliedForPosition(User $user, int $positionId): bool
{
return $user->applications()->where('rank_id', $positionId)->exists();
}
public function isPositionOpenForApplication($position): bool
public function isPositionOpenForApplication(WebsiteOpenPosition $position): bool
{
$currentTime = now();
@@ -10,22 +10,32 @@ use Illuminate\Support\Facades\Cache;
class StaffService
{
/**
* @return Collection<int, Permission>
*/
public function fetchStaffPositions(): Collection
{
$cacheEnabled = setting('enable_caching') === '1';
if ($cacheEnabled && Cache::has('staff_positions')) {
/** @var Collection<int, Permission> */
return Cache::get('staff_positions');
}
/** @var \App\Models\User|null $user */
$user = Auth::user();
$userRank = $user ? $user->rank : 0;
/** @var Collection<int, Permission> $employees */
$employees = Permission::query()
->select('id', 'rank_name', 'badge', 'staff_color', 'job_description')
->when(Auth::user()->rank < (int) setting('min_rank_to_see_hidden_staff'), fn ($query) => $query->where('hidden_rank', false))
->when($userRank < (int) setting('min_rank_to_see_hidden_staff'), fn ($query) => $query->where('hidden_rank', false))
->where('id', '>=', setting('min_staff_rank'))
->orderByDesc('id')
->with(['users' => function ($query): void {
->with(['users' => function ($query) use ($userRank): void {
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query->select('id', 'username', 'rank', 'motto', 'look', 'hidden_staff', 'online')
->when(Auth::user()->rank < (int) setting('min_rank_to_see_hidden_staff'), fn ($query) => $query->where('hidden_staff', false));
->when($userRank < (int) setting('min_rank_to_see_hidden_staff'), fn ($query) => $query->where('hidden_staff', false));
}])
->get();
@@ -37,14 +47,19 @@ class StaffService
return $employees;
}
/**
* @return array<int, int>
*/
public function fetchEmployeeIds(): array
{
$cacheEnabled = setting('enable_caching') === '1';
if ($cacheEnabled && Cache::has('staff_ids')) {
/** @var array<int, int> */
return Cache::get('staff_ids');
}
/** @var array<int, int> $staffIds */
$staffIds = User::select('id')
->where('rank', '>=', setting('min_staff_rank'))
->get()
@@ -8,18 +8,24 @@ use Illuminate\Support\Facades\Cache;
class TeamService
{
/**
* @return Collection<int, WebsiteTeam>
*/
public function fetchTeams(): Collection
{
$cacheEnabled = setting('enable_caching') === '1';
if (Cache::has('hotel_teams') && $cacheEnabled) {
/** @var Collection<int, WebsiteTeam> */
return Cache::get('hotel_teams');
}
/** @var Collection<int, WebsiteTeam> $employees */
$employees = WebsiteTeam::select(['id', 'rank_name', 'badge', 'staff_color', 'staff_background', 'job_description'])
->where('hidden_rank', false)
->orderByDesc('id')
->with(['users' => function ($query): void {
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query->select('id', 'username', 'look', 'motto', 'rank', 'team_id', 'online');
}])
->get();
+19 -5
View File
@@ -29,7 +29,7 @@ class FindRetrosService
/**
* Initialise Find Retros Service
*/
public function __construct(): void
public function __construct()
{
$this->client = new Client(['verify' => false]);
}
@@ -43,8 +43,9 @@ class FindRetrosService
return true;
}
$cacheKey = sprintf(self::FIND_RETROS_CACHE_KEY, request()->ip());
if (request()->ip() === '127.0.0.1') {
$ip = request()->ip();
$cacheKey = sprintf(self::FIND_RETROS_CACHE_KEY, is_scalar($ip) ? (string) $ip : '');
if ($ip === '127.0.0.1') {
return true;
}
@@ -56,7 +57,14 @@ class FindRetrosService
return true;
}
$uri = sprintf(self::FIND_RETROS_VERIFY_URI, config('habbo.findretros.api'), config('habbo.findretros.name'), request()->ip());
$api = config('habbo.findretros.api');
$name = config('habbo.findretros.name');
$uri = sprintf(self::FIND_RETROS_VERIFY_URI,
is_scalar($api) ? (string) $api : '',
is_scalar($name) ? (string) $name : '',
is_scalar($ip) ? (string) $ip : ''
);
$request = $this->client->get($uri);
$response = $request->getBody()->getContents();
@@ -74,6 +82,12 @@ class FindRetrosService
*/
public function getRedirectUri(): string
{
return sprintf(self::FIND_RETROS_REDIRECT_URI, config('habbo.findretros.api'), config('habbo.findretros.name'));
$api = config('habbo.findretros.api');
$name = config('habbo.findretros.name');
return sprintf(self::FIND_RETROS_REDIRECT_URI,
is_scalar($api) ? (string) $api : '',
is_scalar($name) ? (string) $name : ''
);
}
}
@@ -7,19 +7,29 @@ use Illuminate\Support\Collection;
class HousekeepingPermissionsService
{
/** @var Collection<string, int>|null */
public ?Collection $permissions;
public function __construct()
{
$this->permissions = WebsiteHousekeepingPermission::all()->pluck('min_rank', 'permission');
/** @var Collection<string, int> */
$permissions = WebsiteHousekeepingPermission::pluck('min_rank', 'permission');
$this->permissions = $permissions;
}
public function getOrDefault(string $permissionName, bool $default = false): bool
{
if (! array_key_exists($permissionName, $this->permissions->toArray())) {
if (! $this->permissions instanceof Collection || ! $this->permissions->has($permissionName)) {
return $default;
}
return auth()->check() && auth()->user()->rank >= (int) $this->permissions->get($permissionName);
if (! auth()->check()) {
return false;
}
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->rank >= (int) $this->permissions->get($permissionName);
}
}
+8 -2
View File
@@ -10,12 +10,18 @@ class IpLookupService
public function __construct(private readonly string $apiKey) {}
/**
* @return array<string, mixed>
*/
public function ipLookup(string $ip): array
{
$response = Http::acceptJson()->get(sprintf('%s/%s?api-key=%s', $this->baseUrl, $ip, $this->apiKey));
/** @var array<string, mixed> $json */
$json = $response->json() ?? [];
if (! $response->ok()) {
$message = array_key_exists('message', $response->json()) ? $response->json()['message'] : 'Unknown error';
$message = array_key_exists('message', $json) ? $json['message'] : 'Unknown error';
return [
'message' => $message,
@@ -23,6 +29,6 @@ class IpLookupService
];
}
return $response->json();
return $json;
}
}
@@ -8,15 +8,19 @@ use Illuminate\Support\Facades\Cache;
class PermissionsService
{
/** @var Collection<string, int>|null */
public private(set) ?Collection $permissions;
public function __construct()
{
$this->permissions = Cache::remember(
/** @var Collection<string, int> $permissions */
$permissions = Cache::remember(
key: 'website_permissions',
ttl: now()->addMinutes(30),
callback: fn () => WebsitePermission::all()->pluck('min_rank', 'permission')
callback: fn () => WebsitePermission::pluck('min_rank', 'permission')
);
$this->permissions = $permissions;
}
public function getOrDefault(string $permissionName, bool $default = false): bool
@@ -29,17 +33,23 @@ class PermissionsService
return false;
}
return auth()->user()->rank >= (int) $this->permissions->get($permissionName);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->rank >= (int) $this->permissions->get($permissionName);
}
public function refresh(): void
{
Cache::forget('website_permissions');
$this->permissions = Cache::remember(
/** @var Collection<string, int> $permissions */
$permissions = Cache::remember(
key: 'website_permissions',
ttl: now()->addMinutes(30),
callback: fn () => WebsitePermission::all()->pluck('min_rank', 'permission')
callback: fn () => WebsitePermission::pluck('min_rank', 'permission')
);
$this->permissions = $permissions;
}
}
+29 -21
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Enums\CurrencyTypes;
use App\Exceptions\RconConnectionException;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use JsonException;
use Socket;
@@ -14,13 +15,17 @@ class RconService
public private(set) bool $isConnected = false;
protected array $config = [];
/** @var array{ip: string, port: int} */
protected array $config;
public function __construct()
{
$ip = setting('rcon_ip');
$port = setting('rcon_port');
$this->config = [
'ip' => setting('rcon_ip'),
'port' => (int) setting('rcon_port'),
'ip' => (string) $ip,
'port' => is_numeric($port) ? (int) $port : 3001,
];
$this->initialize();
@@ -28,9 +33,9 @@ class RconService
private function initialize(): void
{
$this->socket = @socket_create(domain: AF_INET, type: SOCK_STREAM, protocol: SOL_TCP);
$socket = @socket_create(domain: AF_INET, type: SOCK_STREAM, protocol: SOL_TCP);
if (! $this->socket) {
if ($socket === false) {
$error = socket_strerror(socket_last_error());
Log::error("RCON initialization failed: {$error}");
@@ -39,6 +44,8 @@ class RconService
return;
}
$this->socket = $socket;
socket_set_option(
socket: $this->socket,
level: SOL_SOCKET,
@@ -53,7 +60,7 @@ class RconService
);
if (! @socket_connect($this->socket, $this->config['ip'], $this->config['port'])) {
$error = socket_strerror(socket_last_error());
$error = socket_strerror(socket_last_error($this->socket));
Log::error("RCON connection failed: {$error}");
$this->closeConnection();
@@ -80,12 +87,13 @@ class RconService
}
/**
* @param array<string, mixed>|null $data
* @throws RconConnectionException
* @throws JsonException
*/
public function sendCommand(string $command, ?array $data = null): bool
{
if (! $this->isConnected) {
if (! $this->isConnected || ! $this->socket) {
Log::error('RCON command failed: Not connected');
$this->closeConnection();
return false;
@@ -106,7 +114,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function sendGift($user, int $item_id, string $message = 'Here is a gift.'): void
public function sendGift(User $user, int $item_id, string $message = 'Here is a gift.'): void
{
$this->sendCommand('sendgift', [
'user_id' => $user->id,
@@ -118,7 +126,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function giveCredits($user, int $credits): void
public function giveCredits(User $user, int $credits): void
{
$this->sendCommand('givecredits', [
'user_id' => $user->id,
@@ -129,7 +137,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function giveBadge($user, string $badge): void
public function giveBadge(User $user, string $badge): void
{
$this->sendCommand('givebadge', [
'user_id' => $user->id,
@@ -140,7 +148,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function setMotto($user, string $motto): void
public function setMotto(User $user, string $motto): void
{
$this->sendCommand('setmotto', [
'user_id' => $user->id,
@@ -159,7 +167,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function disconnectUser($user): void
public function disconnectUser(User $user): void
{
$this->sendCommand('disconnect', [
'user_id' => $user->id,
@@ -170,7 +178,7 @@ class RconService
/**
* @throws RconConnectionException|JsonException
*/
public function givePoints($user, CurrencyTypes $type, int $amount): void
public function givePoints(User $user, CurrencyTypes $type, int $amount): void
{
$this->sendCommand('givepoints', [
'user_id' => $user->id,
@@ -183,7 +191,7 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function giveGotw($user, int $amount): void
public function giveGotw(User $user, int $amount): void
{
$this->givePoints($user, CurrencyTypes::Points, $amount);
}
@@ -192,7 +200,7 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function giveDiamonds($user, int $amount): void
public function giveDiamonds(User $user, int $amount): void
{
$this->givePoints($user, CurrencyTypes::Diamonds, $amount);
}
@@ -201,16 +209,16 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function giveDuckets($user, int $amount): void
public function giveDuckets(User $user, int $amount): void
{
$this->givePoints($user, CurrencyTypes::DUCKETS, $amount);
$this->givePoints($user, CurrencyTypes::Duckets, $amount);
}
/**
* @throws RconConnectionException
* @throws JsonException
*/
public function setRank($user, int $rank): void
public function setRank(User $user, int $rank): void
{
$this->sendCommand('setrank', [
'user_id' => $user->id,
@@ -231,7 +239,7 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function alertUser($user, string $message): void
public function alertUser(User $user, string $message): void
{
$this->sendCommand('alertuser', [
'user_id' => $user->id,
@@ -243,7 +251,7 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function forwardUser($user, int $roomId): void
public function forwardUser(User $user, int $roomId): void
{
$this->sendCommand('forwarduser', [
'user_id' => $user->id,
@@ -255,7 +263,7 @@ class RconService
* @throws RconConnectionException
* @throws JsonException
*/
public function updateConfig($user, string $command): void
public function updateConfig(User $user, string $command): void
{
$this->sendCommand('executecommand', [
'user_id' => $user->id,
+24 -21
View File
@@ -10,46 +10,49 @@ use Throwable;
class SettingsService
{
public private(set) ?Collection $settings;
/** @var Collection<string, string> */
public private(set) Collection $settings;
public function __construct()
{
try {
$this->settings = Cache::remember(
key: 'website_settings',
ttl: now()->addMinutes(5),
callback: fn () => Schema::hasTable('website_settings')
? WebsiteSetting::all()->pluck('value', 'key')
: collect()
);
} catch (Throwable) {
$this->settings = collect();
}
$this->refresh();
}
public function getOrDefault(string $settingName, ?string $default = null): string
{
if (! $this->settings instanceof Collection) {
return (string) $default;
}
return (string) $this->settings->get($settingName, $default);
}
public function refresh(): void
{
Cache::forget('website_settings');
try {
$this->settings = Cache::remember(
/** @var mixed $result */
$result = Cache::remember(
key: 'website_settings',
ttl: now()->addMinutes(5),
callback: fn () => Schema::hasTable('website_settings')
? WebsiteSetting::all()->pluck('value', 'key')
callback: fn () => Schema::hasTable('website_settings')
? WebsiteSetting::pluck('value', 'key')
: collect()
);
/** @var array<string, string> $data */
$data = [];
if ($result instanceof Collection) {
foreach ($result as $key => $value) {
$data[(string) $key] = is_scalar($value) ? (string) $value : '';
}
} elseif (is_array($result)) {
foreach ($result as $key => $value) {
$data[(string) $key] = is_scalar($value) ? (string) $value : '';
}
}
$this->settings = new Collection($data);
} catch (Throwable) {
$this->settings = collect();
$this->settings = new Collection();
}
}
}
@@ -8,16 +8,26 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Jenssegers\Agent\Agent;
use App\Models\Session;
class SessionService
{
/**
* @return Collection<int, \stdClass>
*/
public function fetchSessionLogs(Request $request): Collection
{
return collect(
Auth::user()->sessions,
)->map(function ($session) use ($request) {
/** @var \App\Models\User $user */
$user = Auth::user();
/** @var Collection<int, Session> $sessions */
$sessions = $user->sessions;
return $sessions->map(function (Session $session) use ($request): \stdClass {
$agent = $this->createAgent($session);
return (object) [
/** @var \stdClass $obj */
$obj = (object) [
'agent' => [
'is_desktop' => $agent->isDesktop(),
'platform' => $agent->platform(),
@@ -25,14 +35,16 @@ class SessionService
],
'ip_address' => $session->ip_address,
'is_current_device' => $session->id === $request->session()->getId(),
'last_active' => \Illuminate\Support\Facades\Date::createFromTimestamp($session->last_activity)->diffForHumans(),
'last_active' => \Illuminate\Support\Facades\Date::createFromTimestamp((int) $session->last_activity)->diffForHumans(),
];
});
return $obj;
})->values();
}
protected function createAgent($session): Agent
protected function createAgent(Session $session): Agent
{
return tap(new Agent, function ($agent) use ($session): void {
return tap(new Agent, function (Agent $agent) use ($session): void {
$agent->setUserAgent($session->user_agent);
});
}
@@ -7,11 +7,18 @@ use Illuminate\Database\Eloquent\Builder;
class UserApiService
{
/**
* @param array<int, string> $columns
*/
public function fetchUser(string $username, array $columns): User
{
return User::select($columns)->where('username', '=', $username)->first();
return User::select($columns)->where('username', '=', $username)->firstOrFail();
}
/**
* @param array<int, string> $columns
* @return Builder<User>
*/
public function onlineUsers($columns = ['username', 'motto', 'look'], bool $randomOrder = true): Builder
{
$query = User::select($columns)->where('online', '=', '1');