Merge pull request #4929 from HDInnovations/development

(Release) UNIT3D v9.1.6
This commit is contained in:
HDVinnie
2025-08-31 11:36:47 -04:00
committed by GitHub
225 changed files with 7271 additions and 4938 deletions

View File

@@ -10,6 +10,7 @@ limewire
mal
mediainfo
phpstorm
rottentomatoes
shareaza
tmdb
tvdb

View File

@@ -71,9 +71,13 @@ class AutoRemoveTimedTorrentBuffs extends Command
Unit3dAnnounce::addTorrent($torrent);
}
Torrent::query()->whereNotNull('bumped_at')->where('bumped_at', '<', now()->subWeek())->update([
'bumped_at' => DB::raw('created_at'),
]);
Torrent::query()
->whereNotNull('bumped_at')
->where('bumped_at', '<', now()->subWeek())
->whereColumn('bumped_at', '!=', 'created_at')
->update([
'bumped_at' => DB::raw('created_at'),
]);
$this->comment('Automated Removal Of Expired Torrent Buffs Command Complete');
}

View File

@@ -448,7 +448,10 @@ readonly class TorrentSearchFiltersDTO
$filters[] = 'user.username = '.json_encode($this->uploader);
if (!$group->is_modo) {
$filters[] = 'anon = false';
$filters[] = [
'anon = false',
'user.username = '.json_encode($this->user->username),
];
}
}

View File

@@ -183,8 +183,8 @@ class Bbcode
'code' => [
'openBbcode' => '/^\[code\]/i',
'closeBbcode' => '[/code]',
'openHtml' => '<pre>',
'closeHtml' => '</pre>',
'openHtml' => '<div class="bbcode-rendered__clipboard" x-data="clipboardButton"><pre><code>',
'closeHtml' => '</code></pre><div class="bbcode-rendered__clipboard-container"><button class="bbcode-rendered__clipboard-button" x-bind="button"><i class="fa fa-clone"></i></button></div></div>',
'block' => true,
],
'pre' => [

View File

@@ -56,6 +56,16 @@ if (!\function_exists('href_request')) {
}
}
if (!\function_exists('href_rottentomatoes')) {
function href_rottentomatoes(string|null $title, string|null $date): string
{
$year = substr($date ?? '', 0, 4);
$query = "{$title} ({$year}) site:rottentomatoes.com";
return "https://html.duckduckgo.com/html/?q=\\".rawurlencode($query);
}
}
if (!\function_exists('href_poll')) {
function href_poll(App\Models\Poll $poll): string
{

View File

@@ -48,6 +48,7 @@ class TorrentTools
$result['info']['source'] = config('torrent.source');
$result['info']['private'] = 1;
$result['info']['entropy'] = bin2hex(random_bytes(64));
if (config('torrent.created_by_append') && \array_key_exists('created by', $result)) {
$result['created by'] = trim((string) $result['created by'], '. ').'. '.config('torrent.created_by', '');

View File

@@ -420,7 +420,7 @@ class ChatController extends Controller
}
/* USERS */
public function updateUserChatStatus(Request $request): \Illuminate\Http\JsonResponse
public function updateUserChatStatus(Request $request): \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
{
$user = $request->user();
$user->load(['chatStatus', 'chatroom', 'group', 'echoes']);
@@ -432,7 +432,7 @@ class ChatController extends Controller
$user->chatStatus()->associate($status);
$user->save();
return response()->json($user);
return response('success');
}
public function updateUserRoom(Request $request): \Illuminate\Http\JsonResponse

View File

@@ -27,7 +27,7 @@ class QuickSearchController extends Controller
{
public function index(Request $request): \Illuminate\Http\JsonResponse
{
$query = $request->input('query');
$query = $request->input('query', '');
$filters = [
'deleted_at IS NULL',

View File

@@ -671,7 +671,6 @@ class TorrentController extends BaseController
'media_info' => $hit['mediainfo'],
'bd_info' => $hit['bdinfo'],
'description' => $hit['description'],
'info_hash' => $hit['info_hash'],
'size' => $hit['size'],
'num_file' => $hit['num_file'],
'files' => $hit['files'],

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\TorrentRequestResource;
use App\Models\TorrentRequest;
use Illuminate\Http\Request;
class RequestController extends Controller
{
/**
* Request search filter.
*/
public function filter(Request $request): \Illuminate\Http\JsonResponse
{
$query = TorrentRequest::query()
->with(['user', 'claim.user', 'filler'])
->withSum('bounties', 'seedbonus')
->when($request->filled('name'), fn ($query) => $query->where('name', 'LIKE', '%'.str_replace(' ', '%', $request->input('name')).'%'))
->when($request->filled('category_id'), fn ($query) => $query->whereIntegerInRaw('category_id', (array) $request->input('category_id')))
->when($request->filled('type_id'), fn ($query) => $query->whereIntegerInRaw('type_id', (array) $request->input('type_id')))
->when($request->filled('resolution_id'), fn ($query) => $query->whereIntegerInRaw('resolution_id', (array) $request->input('resolution_id')))
->when($request->filled('tmdb'), fn ($query) => $query->whereAny(['tmdb_movie_id', 'tmdb_tv_id'], '=', $request->integer('tmdb')))
->when($request->filled('imdb'), fn ($query) => $query->where('imdb', '=', $request->integer('imdb')))
->when($request->filled('tvdb'), fn ($query) => $query->where('tvdb', '=', $request->integer('tvdb')))
->when($request->filled('mal'), fn ($query) => $query->where('mal', '=', $request->integer('mal')))
->when($request->filled('filled'), fn ($query) => $request->boolean('filled')
? $query->whereNotNull('filled_by')
: $query->whereNull('filled_by'))
->when($request->filled('claimed'), fn ($query) => $request->boolean('claimed')
? $query->whereNotNull('claim')
: $query->whereNull('claim'));
$perPage = min($request->integer('perPage', 25), 100);
$page = max($request->integer('page', 1), 1);
$requests = $query->paginate(
perPage: $perPage,
page: $page
);
return TorrentRequestResource::collection($requests)->response();
}
/**
* View a single request.
*/
public function show(int $id): \Illuminate\Http\JsonResponse
{
$request = TorrentRequest::with(['user', 'claim.user', 'filler'])
->withSum('bounties', 'seedbonus')
->findOrFail($id);
return new TorrentRequestResource($request)->response();
}
}

View File

@@ -58,7 +58,7 @@ class RequestController extends Controller
public function show(Request $request, TorrentRequest $torrentRequest): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
return view('requests.show', [
'torrentRequest' => $torrentRequest->load(['category', 'claim' => ['user'], 'bounties', 'torrent']),
'torrentRequest' => $torrentRequest->load(['category', 'claim.user.group', 'bounties.user.group', 'torrent']),
'user' => $request->user(),
'canEdit' => $request->user()->group->is_modo || TorrentRequest::query()
->whereDoesntHave('bounties', fn ($query) => $query->where('user_id', '!=', $request->user()->id))

View File

@@ -20,7 +20,6 @@ use App\Enums\ModerationStatus;
use App\Helpers\StringHelper;
use App\Models\Conversation;
use App\Services\Unit3dAnnounce;
use Carbon\Carbon;
use App\Models\Donation;
use Illuminate\Http\Request;
use App\Models\PrivateMessage;
@@ -68,7 +67,7 @@ class DonationController extends Controller
{
abort_unless($request->user()->group->is_owner, 403);
$now = Carbon::now();
$now = now();
$donation = Donation::with(['user', 'package'])->findOrFail($id);
$donation->status = ModerationStatus::APPROVED;

View File

@@ -47,7 +47,7 @@ class DonationGatewayController extends Controller
/**
* Store A Donation Gateway.
*/
public function store(StoreDonationGatewayRequest $request)
public function store(StoreDonationGatewayRequest $request): \Illuminate\Http\RedirectResponse
{
abort_unless($request->user()->group->is_owner, 403);
@@ -83,7 +83,7 @@ class DonationGatewayController extends Controller
/**
* Destroy A Donation Gateway.
*/
public function destroy(Request $request, DonationGateway $gateway)
public function destroy(Request $request, DonationGateway $gateway): \Illuminate\Http\RedirectResponse
{
abort_unless($request->user()->group->is_owner, 403);

View File

@@ -18,32 +18,67 @@ namespace App\Http\Controllers;
use App\Models\History;
use App\Models\Torrent;
use App\Models\TorrentReseed;
use App\Models\User;
use App\Notifications\NewReseedRequest;
use App\Repositories\ChatRepository;
use Illuminate\Http\Request;
class ReseedController extends Controller
class TorrentReseedController extends Controller
{
/**
* ReseedController Constructor.
* TorrentReseedController Constructor.
*/
public function __construct(private readonly ChatRepository $chatRepository)
{
}
/**
* Display a listing of torrent reseed requests.
*/
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application
{
return view('torrent-reseed.index');
}
/**
* Reseed Request A Torrent.
*/
public function store(Request $request, int $id): \Illuminate\Http\RedirectResponse
{
// TODO: Store reseed requests so can be viewed in a table view.
$torrent = Torrent::findOrFail($id);
$potentialReseeds = History::where('torrent_id', '=', $torrent->id)->where('active', '=', 0)->get();
$userId = $request->user()->id;
// Check if this user has already made a reseed request for this torrent
$existingUserReseed = TorrentReseed::where('torrent_id', '=', $torrent->id)
->where('user_id', '=', $userId)
->first();
if ($existingUserReseed) {
return to_route('torrents.show', ['id' => $torrent->id])
->withErrors('You have already made a reseed request for this torrent.');
}
// Check seeders condition and if a request already exists for this torrent
$existingReseed = TorrentReseed::where('torrent_id', '=', $torrent->id)->first();
if ($torrent->seeders <= 2) {
// Send Notification
if ($existingReseed) {
$existingReseed->increment('requests_count');
$existingReseed->save();
return to_route('torrents.show', ['id' => $torrent->id])
->with('success', 'A reseed request already exists. Your request has been counted.');
}
TorrentReseed::create([
'torrent_id' => $torrent->id,
'user_id' => $userId,
'requests_count' => 1,
]);
// Send notifications
$potentialReseeds = History::where('torrent_id', '=', $torrent->id)->where('active', '=', 0)->get();
foreach ($potentialReseeds as $potentialReseed) {
User::find($potentialReseed->user_id)->notify(new NewReseedRequest($torrent));
}

View File

@@ -77,7 +77,6 @@ class FollowController extends Controller
$user->notify(new NewUnfollow('user', $request->user()));
return to_route('users.show', ['user' => $user])
->with('success', \sprintf(trans('user.follow-revoked'), $user->username));
return back()->with('success', \sprintf(trans('user.follow-revoked'), $user->username));
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class UnregisteredInfoHashController extends Controller
{
/**
* Show user unregistered info hashes.
*/
public function index(Request $request, User $user): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
{
abort_unless($request->user()->group->is_modo || $request->user()->is($user), 403);
return view('user.unregistered-info-hash.index', [
'user' => $user,
]);
}
}

View File

@@ -231,7 +231,7 @@ class UserController extends Controller
// Define data
$request->validate([
'title' => 'nullable|max:255',
'about' => 'nullable|max:1000',
'about' => 'nullable|max:20000',
'signature' => 'nullable|max:1000'
]);
$user->title = $request->input('title');

View File

@@ -18,14 +18,10 @@ namespace App\Http\Livewire;
use App\Models\Announce;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Announce> $announces
*/
class AnnounceSearch extends Component
{
use LivewireSort;
@@ -59,12 +55,10 @@ class AnnounceSearch extends Component
}
/**
* @return \Illuminate\Pagination\Paginator<int, Announce>
* @var \Illuminate\Pagination\Paginator<int, Announce>
*/
#[Computed]
final public function announces(): \Illuminate\Pagination\Paginator
{
return Announce::query()
protected \Illuminate\Pagination\Paginator $announces {
get => Announce::query()
->when($this->torrentId !== '', fn ($query) => $query->where('torrent_id', '=', $this->torrentId))
->when($this->userId !== '', fn ($query) => $query->where('user_id', '=', $this->userId))
->when($this->sortField !== '', fn ($query) => $query->orderBy($this->sortField, $this->sortDirection))

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Apikey;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Apikey> $apikeys
*/
class ApikeySearch extends Component
{
use LivewireSort;
@@ -50,14 +46,13 @@ class ApikeySearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Apikey>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Apikey>
*/
#[Computed]
final public function apikeys(): \Illuminate\Pagination\LengthAwarePaginator
{
return Apikey::with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $apikeys {
get => Apikey::query()
->with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->username.'%')))
->when($this->apikey, fn ($query) => $query->where('content', 'LIKE', '%'.$this->apikey.'%'))
->orderBy($this->sortField, $this->sortDirection)

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Enums\ModerationStatus;
use App\Models\Application;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -47,17 +46,17 @@ class ApplicationSearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Application>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Application>
*/
#[Computed]
final public function applications(): \Illuminate\Pagination\LengthAwarePaginator
{
return Application::withoutGlobalScopes()->with([
'user.group',
'moderated.group',
'imageProofs',
'urlProofs'
])
protected \Illuminate\Pagination\LengthAwarePaginator $applications {
get => Application::query()
->withoutGlobalScopes()
->with([
'user.group',
'moderated.group',
'imageProofs',
'urlProofs'
])
->when($this->email, fn ($query) => $query->where('email', 'LIKE', '%'.$this->email.'%'))
->when($this->status === '1', fn ($query) => $query->where('status', '=', ModerationStatus::APPROVED))
->when($this->status === '0', fn ($query) => $query->where('status', '=', ModerationStatus::PENDING))

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\User;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\WithFileUploads;
@@ -67,16 +66,16 @@ class AttachmentUpload extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, TicketAttachment>
* @var \Illuminate\Database\Eloquent\Collection<int, TicketAttachment>
*/
#[Computed]
final public function attachments(): \Illuminate\Database\Eloquent\Collection
{
$ticket = Ticket::find($this->ticket);
protected \Illuminate\Database\Eloquent\Collection $attachments {
get {
$ticket = Ticket::find($this->ticket);
abort_unless($ticket->user_id === $this->user->id || $this->user->group->is_modo, 403);
abort_unless($ticket->user_id === $this->user->id || $this->user->group->is_modo, 403);
return $ticket->attachments;
return $ticket->attachments;
}
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Audit;
use App\Traits\Auditable;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -56,12 +55,10 @@ class AuditLogSearch extends Component
public string $sortDirection = 'desc';
/**
* @return string[]
* @var string[]
*/
#[Computed]
final public function modelNames(): array
{
return collect(scandir(app_path('Models')) ?: [])
final protected array $modelNames {
get => collect(scandir(app_path('Models')) ?: [])
->filter(fn ($path) => str_ends_with($path, '.php'))
->map(fn ($path) => 'App\\Models\\'.basename($path, '.php'))
->filter(fn ($class) => class_exists($class))
@@ -73,25 +70,23 @@ class AuditLogSearch extends Component
/**
* @throws JsonException
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Audit>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Audit>
*/
#[Computed]
final public function audits(): \Illuminate\Pagination\LengthAwarePaginator
{
$audits = Audit::with('user')
final protected \Illuminate\Pagination\LengthAwarePaginator $audits {
get => Audit::query()
->with('user')
->when($this->username, fn ($query) => $query->whereRelation('user', 'username', '=', $this->username))
->when($this->modelName, fn ($query) => $query->where('model_name', '=', $this->modelName))
->when($this->modelId, fn ($query) => $query->where('model_entry_id', '=', $this->modelId))
->when($this->action, fn ($query) => $query->where('action', '=', $this->action))
->when($this->record, fn ($query) => $query->where('record', 'LIKE', '%'.$this->record.'%'))
->latest()
->paginate($this->perPage);
->paginate($this->perPage)
->through(function ($audit) {
$audit->values = json_decode((string) $audit->record, true, 512, JSON_THROW_ON_ERROR);
foreach ($audits as $audit) {
$audit->values = json_decode((string) $audit->record, true, 512, JSON_THROW_ON_ERROR);
}
return $audits;
return $audit;
});
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -22,10 +22,10 @@ use App\Rules\PathToZip;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Spatie\Backup\BackupDestination\Backup;
use Spatie\Backup\BackupDestination\BackupDestination;
use Spatie\Backup\Config\MonitoredBackupsConfig;
use Spatie\Backup\Helpers\Format;
use Spatie\Backup\Tasks\Monitor\BackupDestinationStatus;
use Spatie\Backup\Tasks\Monitor\BackupDestinationStatusFactory;
@@ -40,14 +40,10 @@ class BackupPanel extends Component
protected $listeners = ['refreshBackups' => '$refresh'];
/**
* @return array<mixed>
* @var array<mixed>
*/
#[Computed]
final public function backupStatuses(): array
{
$monitorConfig = \Spatie\Backup\Config\MonitoredBackupsConfig::fromArray(config('backup.monitor_backups'));
return BackupDestinationStatusFactory::createForMonitorConfig($monitorConfig)
final protected array $backupStatuses {
get => BackupDestinationStatusFactory::createForMonitorConfig(MonitoredBackupsConfig::fromArray(config('backup.monitor_backups')))
->map(fn (BackupDestinationStatus $backupDestinationStatus) => [
'name' => $backupDestinationStatus->backupDestination()->backupName(),
'disk' => $backupDestinationStatus->backupDestination()->diskName(),
@@ -63,46 +59,37 @@ class BackupPanel extends Component
->toArray();
}
#[Computed]
final public function activeDisk(): ?string
{
if (\count($this->backupStatuses)) {
return $this->backupStatuses[0]['disk'];
}
return null;
final protected ?string $activeDisk {
get => $this->backupStatuses[0]['disk'] ?? null;
}
/**
* @return array<mixed>
* @var array<mixed>
*/
#[Computed]
final public function disks(): array
{
return collect($this->backupStatuses)
final protected array $disks {
get => collect($this->backupStatuses)
->map(fn ($backupStatus): mixed => $backupStatus['disk'])
->values()
->all();
}
/**
* @var array<mixed>
* @throws ValidationException
*/
#[Computed]
final public function backups(): array
{
$this->validateActiveDisk();
final protected array $backups {
get {
$this->validateActiveDisk();
$backupDestination = BackupDestination::create($this->activeDisk, config('backup.backup.name'));
return $backupDestination
->backups()
->map(fn (Backup $backup) => [
'path' => $backup->path(),
'date' => $backup->date()->format('Y-m-d H:i:s'),
'size' => Format::humanReadableSize($backup->sizeInBytes()),
])
->toArray();
return BackupDestination::create($this->activeDisk, config('backup.backup.name'))
->backups()
->map(fn (Backup $backup) => [
'path' => $backup->path(),
'date' => $backup->date()->format('Y-m-d H:i:s'),
'size' => Format::humanReadableSize($backup->sizeInBytes()),
])
->toArray();
}
}
final public function deleteBackup(int $fileIndex): void

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\BlockedIp;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Attributes\Validate;
use Livewire\Component;
@@ -75,12 +74,10 @@ class BlockIpAddress extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, BlockedIp>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, BlockedIp>
*/
#[Computed]
final public function ipAddresses(): \Illuminate\Pagination\LengthAwarePaginator
{
return BlockedIp::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $ipAddresses {
get => BlockedIp::query()
->when($this->ipSearch, fn ($query) => $query->where('ip_address', 'LIKE', '%'.$this->ipSearch.'%'))
->when($this->reasonSearch, fn ($query) => $query->where('reason', 'LIKE', '%'.$this->reasonSearch.'%'))
->latest()

View File

@@ -41,7 +41,6 @@ use App\Notifications\NewCommentTag;
use App\Repositories\ChatRepository;
use App\Traits\CastLivewireProperties;
use Illuminate\Support\Facades\Notification;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\WithPagination;
@@ -199,12 +198,10 @@ class Comments extends Component
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, \App\Models\Comment>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, \App\Models\Comment>
*/
#[Computed]
final public function comments(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return $this->model
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $comments {
get => $this->model
->comments()
->with(['user:id,username,group_id,image,title', 'user.group', 'children.user:id,username,group_id,image,title', 'children.user.group'])
->parent()

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Conversation;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Rule;
use Livewire\Attributes\Url;
use Livewire\Component;
@@ -49,12 +48,10 @@ class ConversationSearch extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Conversation>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Conversation>
*/
#[Computed]
final public function conversations(): \Illuminate\Pagination\LengthAwarePaginator
{
return Conversation::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $conversations {
get => Conversation::query()
->with([
'users' => fn ($query) => $query->with('group')->where('users.id', '!=', auth()->id()),
'participants' => fn ($query) => $query->where('user_id', auth()->id()),

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\EmailUpdate;
use App\Models\User;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -44,14 +43,13 @@ class EmailUpdateSearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, EmailUpdate>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, EmailUpdate>
*/
#[Computed]
final public function emailUpdates(): \Illuminate\Pagination\LengthAwarePaginator
{
return EmailUpdate::with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $emailUpdates {
get => EmailUpdate::query()
->with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->username.'%')))
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);

View File

@@ -20,7 +20,6 @@ use App\Models\FailedLoginAttempt;
use App\Traits\LivewireSort;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -51,12 +50,10 @@ class FailedLoginSearch extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Database\Eloquent\Collection<int, FailedLoginAttempt>
* @var \Illuminate\Database\Eloquent\Collection<int, FailedLoginAttempt>
*/
#[Computed]
final public function failedLoginsTop10Ip(): \Illuminate\Database\Eloquent\Collection
{
return FailedLoginAttempt::query()
final protected \Illuminate\Database\Eloquent\Collection $failedLoginsTop10Ip {
get => FailedLoginAttempt::query()
->select(['ip_address', DB::raw('COUNT(*) as login_attempts'), DB::raw('MAX(created_at) as latest_created_at')])
->groupBy('ip_address')
->having('login_attempts', '>', 3)
@@ -67,12 +64,10 @@ class FailedLoginSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, FailedLoginAttempt>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, FailedLoginAttempt>
*/
#[Computed]
final public function failedLogins(): \Illuminate\Pagination\LengthAwarePaginator
{
return FailedLoginAttempt::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $failedLogins {
get => FailedLoginAttempt::query()
->with('user.group')
->when($this->username, fn ($query) => $query->where('username', 'LIKE', $this->username.'%'))
->when($this->userId, fn ($query) => $query->where('user_id', '=', $this->userId))

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\ForumCategory;
use App\Models\Topic;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -63,12 +62,10 @@ class ForumCategoryTopicSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
*/
#[Computed]
final public function topics(): \Illuminate\Pagination\LengthAwarePaginator
{
return Topic::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $topics {
get => Topic::query()
->select('topics.*')
->with([
'user.group',

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Forum;
use App\Models\Subscription;
use App\Models\Topic;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -68,12 +67,10 @@ class ForumTopicSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
*/
#[Computed]
final public function topics(): \Illuminate\Pagination\LengthAwarePaginator
{
return Topic::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $topics {
get => Topic::query()
->select('topics.*')
->with([
'user.group',

View File

@@ -18,14 +18,10 @@ namespace App\Http\Livewire;
use App\Models\Gift;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Gift> $gifts
*/
class GiftLogSearch extends Component
{
use LivewireSort;
@@ -52,15 +48,14 @@ class GiftLogSearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Gift>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Gift>
*/
#[Computed]
final public function gifts(): \Illuminate\Pagination\LengthAwarePaginator
{
return Gift::with([
'sender' => fn ($query) => $query->withTrashed()->with('group'),
'recipient' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $gifts {
get => Gift::query()
->with([
'sender' => fn ($query) => $query->withTrashed()->with('group'),
'recipient' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->sender, fn ($query) => $query->whereRelation('sender', 'username', '=', $this->sender))
->when($this->receiver, fn ($query) => $query->whereRelation('recipient', 'username', '=', $this->receiver))
->when($this->comment, fn ($query) => $query->where('comment', 'LIKE', '%'.$this->comment.'%'))

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\History;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, History> $histories
*/
class HistorySearch extends Component
{
use LivewireSort;
@@ -92,12 +88,10 @@ class HistorySearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, History>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, History>
*/
#[Computed]
final public function histories(): \Illuminate\Pagination\LengthAwarePaginator
{
return History::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $histories {
get => History::query()
->with('user', 'torrent:id,name')
->when(
$this->groupBy === 'user_id',

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Invite;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -87,12 +86,10 @@ class InviteLogSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Invite>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Invite>
*/
#[Computed]
final public function invites(): \Illuminate\Pagination\LengthAwarePaginator
{
return Invite::withTrashed()
final protected \Illuminate\Pagination\LengthAwarePaginator $invites {
get => Invite::withTrashed()
->with([
'sender' => fn ($query) => $query->withTrashed()->with('group'),
'receiver' => fn ($query) => $query->withTrashed()->with('group'),

View File

@@ -19,16 +19,11 @@ namespace App\Http\Livewire;
use App\Traits\CastLivewireProperties;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\File;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
use SplFileInfo;
/**
* @property \Illuminate\Support\Collection $logFiles
* @property LengthAwarePaginator $entries
*/
class LaravelLogViewer extends Component
{
use CastLivewireProperties;
@@ -54,64 +49,64 @@ class LaravelLogViewer extends Component
$this->perPage += 5;
}
#[Computed]
final public function logFiles()
{
$directory = storage_path('logs');
return collect(File::allFiles($directory))
->sortByDesc(fn (SplFileInfo $file) => $file->getMTime())->values();
/**
* @var \Illuminate\Support\Collection<int, \Symfony\Component\Finder\SplFileInfo>
*/
final protected \Illuminate\Support\Collection $logFiles {
get => collect(File::allFiles(storage_path('logs')))
->sortByDesc(fn (SplFileInfo $file) => $file->getMTime())
->values();
}
/**
* @return LengthAwarePaginator<\Illuminate\Support\Collection<string|int, mixed>>
* @var LengthAwarePaginator<string, \Illuminate\Support\Collection<int, mixed>>
*/
#[Computed]
final public function entries(): LengthAwarePaginator
{
$files = $this->logFiles;
$logString = '';
final protected LengthAwarePaginator $entries {
get {
$files = $this->logFiles;
$logString = '';
foreach ($this->logs as $log) {
if ($files[$log] ?? []) {
$logString .= file_get_contents($files[$log]->getPathname());
foreach ($this->logs as $log) {
if ($files[$log] ?? []) {
$logString .= file_get_contents($files[$log]->getPathname());
}
}
}
$entryPattern = '/^\[(?<date>.*)\]\s(?<env>\w+)\.(?<level>\w+)\:\s/m';
$contextPattern = '/^(?<message>[^\{]*)?(?:\{"exception"\:"\[object\]\s\((?<exception>[^\s\(]+))?.*\s(?:in|at)\s(?<in>.*)\:(?<line>\d+)\)?/ms';
$entryPattern = '/^\[(?<date>.*)\]\s(?<env>\w+)\.(?<level>\w+)\:\s/m';
$contextPattern = '/^(?<message>[^\{]*)?(?:\{"exception"\:"\[object\]\s\((?<exception>[^\s\(]+))?.*\s(?:in|at)\s(?<in>.*)\:(?<line>\d+)\)?/ms';
$entries = collect();
$entries = collect();
if (preg_match_all($entryPattern, $logString, $entryMatches, PREG_SET_ORDER) !== false) {
$stacktraces = preg_split($entryPattern, $logString);
// Delete the empty first entry
array_shift($stacktraces);
$numEntries = \count($entryMatches);
if (preg_match_all($entryPattern, $logString, $entryMatches, PREG_SET_ORDER) !== false) {
$stacktraces = preg_split($entryPattern, $logString);
// Delete the empty first entry
array_shift($stacktraces);
$numEntries = \count($entryMatches);
for ($i = 0; $i < $numEntries; $i++) {
// The context is the portion before the first stack trace
$context = preg_split('/^\[stacktrace\]|Stack trace\:/ms', (string) $stacktraces[$i])[0];
// The `context` consists of a message, an exception, a filename, and a line count
preg_match($contextPattern, $context, $contextMatches);
for ($i = 0; $i < $numEntries; $i++) {
// The context is the portion before the first stack trace
$context = preg_split('/^\[stacktrace\]|Stack trace\:/ms', (string) $stacktraces[$i])[0];
// The `context` consists of a message, an exception, a filename, and a line count
preg_match($contextPattern, $context, $contextMatches);
$entries->push([
'date' => $entryMatches[$i]['date'],
'env' => $entryMatches[$i]['env'],
'level' => $entryMatches[$i]['level'],
'message' => $contextMatches['message'] ?? '',
'exception' => $contextMatches['exception'] ?? '',
'in' => $contextMatches['in'] ?? '',
'line' => $contextMatches['line'] ?? '',
'stacktrace' => $stacktraces[$i],
]);
$entries->push([
'date' => $entryMatches[$i]['date'],
'env' => $entryMatches[$i]['env'],
'level' => $entryMatches[$i]['level'],
'message' => $contextMatches['message'] ?? '',
'exception' => $contextMatches['exception'] ?? '',
'in' => $contextMatches['in'] ?? '',
'line' => $contextMatches['line'] ?? '',
'stacktrace' => $stacktraces[$i],
]);
}
}
$groupedEntries = $entries->groupBy(fn ($entry) => $entry['message'].'-'.$entry['exception'].'-'.$entry['in'].'-'.$entry['line']);
$currentEntries = $groupedEntries->forPage($this->page, $this->perPage);
return new LengthAwarePaginator($currentEntries, $groupedEntries->count(), $this->perPage, $this->page);
}
$groupedEntries = $entries->groupBy(fn ($entry) => $entry['message'].'-'.$entry['exception'].'-'.$entry['in'].'-'.$entry['line']);
$currentEntries = $groupedEntries->forPage($this->page, $this->perPage);
return new LengthAwarePaginator($currentEntries, $groupedEntries->count(), $this->perPage, $this->page);
}
final public function clearLatestLog(): void

View File

@@ -20,7 +20,6 @@ use App\Models\History;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -52,12 +51,10 @@ class LeakerSearch extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, History>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, History>
*/
#[Computed]
final public function leakers(): \Illuminate\Pagination\LengthAwarePaginator
{
return History::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $leakers {
get => History::query()
->select([
'history.user_id',
DB::raw('count(*) as leak_count'),
@@ -81,10 +78,8 @@ class LeakerSearch extends Component
->paginate($this->perPage);
}
#[Computed]
final public function torrentIdCount(): int
{
return \count(array_filter(array_map('trim', explode(',', $this->torrentIds))));
final protected int $torrentIdCount {
get => \count(array_filter(array_map('trim', explode(',', $this->torrentIds))));
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\TmdbMovie;
use App\Models\Type;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -50,12 +49,11 @@ class MissingMediaSearch extends Component
public int $perPage = 50;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TmdbMovie>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TmdbMovie>
*/
#[Computed]
final public function medias(): \Illuminate\Pagination\LengthAwarePaginator
{
return TmdbMovie::with(['torrents:tmdb_movie_id,tmdb_tv_id,resolution_id,type_id' => ['resolution:id,position,name']])
final protected \Illuminate\Pagination\LengthAwarePaginator $medias {
get => TmdbMovie::query()
->with(['torrents:tmdb_movie_id,tmdb_tv_id,resolution_id,type_id' => ['resolution:id,position,name']])
->when($this->name, fn ($query) => $query->where('title', 'LIKE', '%'.$this->name.'%'))
->when($this->year, fn ($query) => $query->where('release_date', 'LIKE', '%'.$this->year.'%'))
->withCount(['requests' => fn ($query) => $query->whereNull('torrent_id')->whereDoesntHave('claim')])
@@ -65,12 +63,13 @@ class MissingMediaSearch extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Type>
* @var \Illuminate\Database\Eloquent\Collection<int, Type>
*/
#[Computed]
final public function types(): \Illuminate\Database\Eloquent\Collection
{
return Type::select(['id', 'position', 'name'])->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $types {
get => Type::query()
->select(['id', 'position', 'name'])
->orderBy('position')
->get();
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\Note;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -40,12 +39,10 @@ class NoteSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Note>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Note>
*/
#[Computed]
final public function notes(): \Illuminate\Pagination\LengthAwarePaginator
{
return Note::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $notes {
get => Note::query()
->with([
'noteduser' => fn ($query) => $query->withTrashed()->with(['group']),
'staffuser' => fn ($query) => $query->withTrashed()->with(['group']),

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -103,12 +102,10 @@ class NotificationSearch extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, \Illuminate\Notifications\DatabaseNotification>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, \Illuminate\Notifications\DatabaseNotification>
*/
#[Computed]
final public function notifications(): \Illuminate\Pagination\LengthAwarePaginator
{
return auth()->user()->notifications()
final protected \Illuminate\Pagination\LengthAwarePaginator $notifications {
get => auth()->user()->notifications()
->select('*')
->selectRaw("CASE WHEN read_at IS NULL THEN 'FALSE' ELSE 'TRUE' END as is_read")
->where(function ($query): void {

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Passkey;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Passkey> $passkeys
*/
class PasskeySearch extends Component
{
use LivewireSort;
@@ -50,14 +46,13 @@ class PasskeySearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Passkey>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Passkey>
*/
#[Computed]
final public function passkeys(): \Illuminate\Pagination\LengthAwarePaginator
{
return Passkey::with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $passkeys {
get => Passkey::query()
->with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', '=', $this->username)))
->when($this->passkey, fn ($query) => $query->where('content', 'LIKE', '%'.$this->passkey.'%'))
->orderBy($this->sortField, $this->sortDirection)

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\PasswordResetHistory;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, PasswordResetHistory> $passwordResetHistories
*/
class PasswordResetHistorySearch extends Component
{
use LivewireSort;
@@ -47,14 +43,13 @@ class PasswordResetHistorySearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, PasswordResetHistory>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, PasswordResetHistory>
*/
#[Computed]
final public function passwordResetHistories(): \Illuminate\Pagination\LengthAwarePaginator
{
return PasswordResetHistory::with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $passwordResetHistories {
get => PasswordResetHistory::query()
->with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->username.'%')))
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Peer;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -98,12 +97,10 @@ class PeerSearch extends Component
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Peer>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Peer>
*/
#[Computed]
final public function peers(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return Peer::query()
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $peers {
get => Peer::query()
->when(
$this->groupBy === 'none',
fn ($query) => $query

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Playlist;
use App\Models\PlaylistCategory;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -55,14 +54,13 @@ class PlaylistSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Playlist>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Playlist>
*/
#[Computed]
final public function playlists()
{
return Playlist::with([
'user:id,username,group_id,image' => ['group'],
])
final protected $playlists {
get => Playlist::query()
->with([
'user:id,username,group_id,image' => ['group'],
])
->withCount('torrents')
->when(
! auth()->user()->group->is_modo,
@@ -81,12 +79,14 @@ class PlaylistSearch extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, PlaylistCategory>
* @var \Illuminate\Database\Eloquent\Collection<int, PlaylistCategory>
*/
#[Computed(seconds: 3600, cache: true)]
final public function playlistCategories(): \Illuminate\Database\Eloquent\Collection
{
return PlaylistCategory::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $playlistCategories {
get => cache()->remember(
'playlist-categories',
3600,
fn () => PlaylistCategory::query()->orderBy('position')->get()
);
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\Post;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -37,12 +36,10 @@ class PostSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Post>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Post>
*/
#[Computed]
final public function posts(): \Illuminate\Pagination\LengthAwarePaginator
{
return Post::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $posts {
get => Post::query()
->with('user', 'user.group', 'topic:id,name,state')
->withCount('likes', 'dislikes', 'authorPosts', 'authorTopics')
->withSum('tips', 'bon')

View File

@@ -19,67 +19,49 @@ namespace App\Http\Livewire;
use App\Models\TmdbTv;
use App\Models\TmdbMovie;
use Illuminate\Support\Facades\Redis;
use Livewire\Attributes\Computed;
use Livewire\Component;
class RandomMedia extends Component
{
/**
* @return \Illuminate\Support\Collection<int, TmdbMovie>
* @var \Illuminate\Support\Collection<int, TmdbMovie>
*/
#[Computed]
final public function movies(): \Illuminate\Support\Collection
{
$cacheKey = config('cache.prefix').':random-media-movie-ids';
final protected \Illuminate\Support\Collection $movies {
get {
$cacheKey = config('cache.prefix').':random-media-movie-ids';
$movieIds = Redis::connection('cache')->command('SRANDMEMBER', [$cacheKey, 3]);
$movieIds = Redis::connection('cache')->command('SRANDMEMBER', [$cacheKey, 3]);
return TmdbMovie::query()
->select(['id', 'backdrop', 'title', 'release_date'])
->withMin('torrents', 'category_id')
->whereIn('id', $movieIds)
->get();
return TmdbMovie::query()
->select(['id', 'backdrop', 'title', 'release_date'])
->withMin('torrents', 'category_id')
->whereIn('id', $movieIds)
->get();
}
}
/**
* @return \Illuminate\Support\Collection<int, TmdbMovie>
* @var \Illuminate\Support\Collection<int, TmdbTv>
*/
#[Computed]
final public function movies2(): \Illuminate\Support\Collection
{
$cacheKey = config('cache.prefix').':random-media-movie-ids';
final protected \Illuminate\Support\Collection $tvs {
get {
$cacheKey = config('cache.prefix').':random-media-tv-ids';
$movieIds = Redis::connection('cache')->command('SRANDMEMBER', [$cacheKey, 3]);
$tvIds = Redis::connection('cache')->command('SRANDMEMBER', [$cacheKey, 3]);
return TmdbMovie::query()
->select(['id', 'backdrop', 'title', 'release_date'])
->withMin('torrents', 'category_id')
->whereIn('id', $movieIds)
->get();
}
/**
* @return \Illuminate\Support\Collection<int, TmdbTv>
*/
#[Computed]
final public function tvs(): \Illuminate\Support\Collection
{
$cacheKey = config('cache.prefix').':random-media-tv-ids';
$tvIds = Redis::connection('cache')->command('SRANDMEMBER', [$cacheKey, 3]);
return TmdbTv::query()
->select(['id', 'backdrop', 'name', 'first_air_date'])
->withMin('torrents', 'category_id')
->whereIn('id', $tvIds)
->get();
return TmdbTv::query()
->select(['id', 'backdrop', 'name', 'first_air_date'])
->withMin('torrents', 'category_id')
->whereIn('id', $tvIds)
->get();
}
}
final public function render(): \Illuminate\Contracts\View\Factory | \Illuminate\Contracts\View\View | \Illuminate\Contracts\Foundation\Application
{
return view('livewire.random-media', [
'movies' => $this->movies,
'movies2' => $this->movies2,
'movies2' => $this->movies,
'tvs' => $this->tvs
]);
}

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Report;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Report> $reports
*/
class ReportSearch extends Component
{
use LivewireSort;
@@ -68,12 +64,10 @@ class ReportSearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Report>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Report>
*/
#[Computed]
final public function reports(): \Illuminate\Pagination\LengthAwarePaginator
{
return Report::orderBy('solved')
final protected \Illuminate\Pagination\LengthAwarePaginator $reports {
get => Report::query()
->with('reported.group', 'reporter.group', 'staff.group')
->when($this->type !== null, fn ($query) => $query->where('type', '=', $this->type))
->when($this->reporter !== null, fn ($query) => $query->whereIn('reporter_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->reporter.'%')))
@@ -86,6 +80,7 @@ class ReportSearch extends Component
->when($this->status === 'snoozed', fn ($query) => $query->where('solved', '=', false)->where('snoozed_until', '>', now()))
->when($this->status === 'closed', fn ($query) => $query->where('solved', '=', true))
->when($this->status === 'all_open', fn ($query) => $query->where('solved', '=', false))
->orderBy('solved')
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
}

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Rsskey;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Rsskey> $rsskeys
*/
class RsskeySearch extends Component
{
use LivewireSort;
@@ -50,14 +46,13 @@ class RsskeySearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Rsskey>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Rsskey>
*/
#[Computed]
final public function rsskeys(): \Illuminate\Pagination\LengthAwarePaginator
{
return Rsskey::with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
final protected \Illuminate\Pagination\LengthAwarePaginator $rsskeys {
get => Rsskey::query()
->with([
'user' => fn ($query) => $query->withTrashed()->with('group'),
])
->when($this->username, fn ($query) => $query->whereIn('user_id', User::withTrashed()->select('id')->where('username', 'LIKE', '%'.$this->username.'%')))
->when($this->rsskey, fn ($query) => $query->where('content', 'LIKE', '%'.$this->rsskey.'%'))
->orderBy($this->sortField, $this->sortDirection)

View File

@@ -35,7 +35,6 @@ use App\Services\Unit3dAnnounce;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\Notification;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
@@ -228,138 +227,138 @@ class SimilarTorrent extends Component
/**
* @phpstan-ignore missingType.generics (The return type is too complex for phpstan, something to do with lack of support of Collection array shapes)
*/
#[Computed]
final public function torrents(): \Illuminate\Support\Collection
{
$user = auth()->user();
final protected \Illuminate\Support\Collection $torrents {
get {
$user = auth()->user();
return Torrent::query()
->with('type:id,name,position', 'resolution:id,name,position')
->withCount([
'comments',
])
->when(
!config('announce.external_tracker.is_enabled'),
fn ($query) => $query->withCount([
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
]),
)
->when(
config('other.thanks-system.is-enabled'),
fn ($query) => $query->withCount('thanks')
)
->withExists([
'featured as featured',
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'bookmarks' => fn ($query) => $query->where('user_id', '=', $user->id),
'history as seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
'trump',
])
->when(
$this->category->movie_meta,
fn ($query) => $query->whereRelation('category', 'movie_meta', '=', true),
)
->when(
$this->category->tv_meta,
fn ($query) => $query->whereRelation('category', 'tv_meta', '=', true),
)
->when($this->category->tv_meta, fn ($query) => $query->where('tmdb_tv_id', '=', $this->tmdbId))
->when($this->category->movie_meta, fn ($query) => $query->where('tmdb_movie_id', '=', $this->tmdbId))
->when($this->category->game_meta, fn ($query) => $query->where('igdb', '=', $this->igdbId))
->where((new TorrentSearchFiltersDTO(
name: $this->name,
description: $this->description,
mediainfo: $this->mediainfo,
keywords: $this->keywords ? array_map('trim', explode(',', $this->keywords)) : [],
uploader: $this->uploader,
episodeNumber: $this->episodeNumber,
seasonNumber: $this->seasonNumber,
minSize: $this->minSize === null ? null : $this->minSize * $this->minSizeMultiplier,
maxSize: $this->maxSize === null ? null : $this->maxSize * $this->maxSizeMultiplier,
playlistId: $this->playlistId,
typeIds: $this->typeIds,
resolutionIds: $this->resolutionIds,
free: $this->free,
doubleup: $this->doubleup,
featured: $this->featured,
refundable: $this->refundable,
internal: $this->internal,
personalRelease: $this->personalRelease,
trumpable: $this->trumpable,
highspeed: $this->highspeed,
userBookmarked: $this->bookmarked,
userWished: $this->wished,
alive: $this->alive,
dying: $this->dying,
dead: $this->dead,
graveyard: $this->graveyard,
userDownloaded: match (true) {
$this->downloaded => true,
$this->notDownloaded => false,
default => null,
},
userSeeder: match (true) {
$this->seeding => true,
$this->leeching => false,
default => null,
},
userActive: match (true) {
$this->seeding => true,
$this->leeching => true,
default => null,
},
))->toSqlQueryBuilder())
->orderBy($this->sortField, $this->sortDirection)
->get()
->when(
$this->category->movie_meta,
fn ($torrents) => $this->groupByTypeAndSort($torrents),
fn ($torrents) => $torrents
->when($this->category->tv_meta, function ($torrents) {
return $torrents
->groupBy(fn ($torrent) => $torrent->season_number === 0 ? ($torrent->episode_number === 0 ? 'Complete Pack' : 'Specials') : 'Seasons')
->map(fn ($packOrSpecialOrSeasons, $key) => match ($key) {
'Complete Pack' => $this->groupByTypeAndSort($packOrSpecialOrSeasons),
'Specials' => $packOrSpecialOrSeasons
->groupBy(fn ($torrent) => 'Special '.$torrent->episode_number)
->sortKeysDesc(SORT_NATURAL)
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
'Seasons' => $packOrSpecialOrSeasons
->groupBy(fn ($torrent) => 'Season '.$torrent->season_number)
->sortKeysDesc(SORT_NATURAL)
->map(
fn ($season) => $season
->groupBy(fn ($torrent) => $torrent->episode_number === 0 ? 'Season Pack' : 'Episodes')
->map(fn ($packOrEpisodes, $key) => match ($key) {
'Season Pack' => $this->groupByTypeAndSort($packOrEpisodes),
'Episodes' => $packOrEpisodes
->groupBy(fn ($torrent) => 'Episode '.$torrent->episode_number)
->sortKeysDesc(SORT_NATURAL)
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
default => abort(500, 'Group found that isn\'t one of: Season Pack, Episodes.'),
})
),
default => abort(500, 'Group found that isn\'t one of: Complete Pack, Specials, Seasons'),
});
})
);
return Torrent::query()
->with('type:id,name,position', 'resolution:id,name,position')
->withCount([
'comments',
])
->when(
!config('announce.external_tracker.is_enabled'),
fn ($query) => $query->withCount([
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
]),
)
->when(
config('other.thanks-system.is-enabled'),
fn ($query) => $query->withCount('thanks')
)
->withExists([
'featured as featured',
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'bookmarks' => fn ($query) => $query->where('user_id', '=', $user->id),
'history as seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $user->id)
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
'trump',
])
->when(
$this->category->movie_meta,
fn ($query) => $query->whereRelation('category', 'movie_meta', '=', true),
)
->when(
$this->category->tv_meta,
fn ($query) => $query->whereRelation('category', 'tv_meta', '=', true),
)
->when($this->category->tv_meta, fn ($query) => $query->where('tmdb_tv_id', '=', $this->tmdbId))
->when($this->category->movie_meta, fn ($query) => $query->where('tmdb_movie_id', '=', $this->tmdbId))
->when($this->category->game_meta, fn ($query) => $query->where('igdb', '=', $this->igdbId))
->where((new TorrentSearchFiltersDTO(
name: $this->name,
description: $this->description,
mediainfo: $this->mediainfo,
keywords: $this->keywords ? array_map('trim', explode(',', $this->keywords)) : [],
uploader: $this->uploader,
episodeNumber: $this->episodeNumber,
seasonNumber: $this->seasonNumber,
minSize: $this->minSize === null ? null : $this->minSize * $this->minSizeMultiplier,
maxSize: $this->maxSize === null ? null : $this->maxSize * $this->maxSizeMultiplier,
playlistId: $this->playlistId,
typeIds: $this->typeIds,
resolutionIds: $this->resolutionIds,
free: $this->free,
doubleup: $this->doubleup,
featured: $this->featured,
refundable: $this->refundable,
internal: $this->internal,
personalRelease: $this->personalRelease,
trumpable: $this->trumpable,
highspeed: $this->highspeed,
userBookmarked: $this->bookmarked,
userWished: $this->wished,
alive: $this->alive,
dying: $this->dying,
dead: $this->dead,
graveyard: $this->graveyard,
userDownloaded: match (true) {
$this->downloaded => true,
$this->notDownloaded => false,
default => null,
},
userSeeder: match (true) {
$this->seeding => true,
$this->leeching => false,
default => null,
},
userActive: match (true) {
$this->seeding => true,
$this->leeching => true,
default => null,
},
))->toSqlQueryBuilder())
->orderBy($this->sortField, $this->sortDirection)
->get()
->when(
$this->category->movie_meta,
fn ($torrents) => $this->groupByTypeAndSort($torrents),
fn ($torrents) => $torrents
->when($this->category->tv_meta, function ($torrents) {
return $torrents
->groupBy(fn ($torrent) => $torrent->season_number === 0 ? ($torrent->episode_number === 0 ? 'Complete Pack' : 'Specials') : 'Seasons')
->map(fn ($packOrSpecialOrSeasons, $key) => match ($key) {
'Complete Pack' => $this->groupByTypeAndSort($packOrSpecialOrSeasons),
'Specials' => $packOrSpecialOrSeasons
->groupBy(fn ($torrent) => 'Special '.$torrent->episode_number)
->sortKeysDesc(SORT_NATURAL)
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
'Seasons' => $packOrSpecialOrSeasons
->groupBy(fn ($torrent) => 'Season '.$torrent->season_number)
->sortKeysDesc(SORT_NATURAL)
->map(
fn ($season) => $season
->groupBy(fn ($torrent) => $torrent->episode_number === 0 ? 'Season Pack' : 'Episodes')
->map(fn ($packOrEpisodes, $key) => match ($key) {
'Season Pack' => $this->groupByTypeAndSort($packOrEpisodes),
'Episodes' => $packOrEpisodes
->groupBy(fn ($torrent) => 'Episode '.$torrent->episode_number)
->sortKeysDesc(SORT_NATURAL)
->map(fn ($episode) => $this->groupByTypeAndSort($episode)),
default => abort(500, 'Group found that isn\'t one of: Season Pack, Episodes.'),
})
),
default => abort(500, 'Group found that isn\'t one of: Complete Pack, Specials, Seasons'),
});
})
);
}
}
/**
@@ -383,12 +382,10 @@ class SimilarTorrent extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, TorrentRequest>
* @var \Illuminate\Database\Eloquent\Collection<int, TorrentRequest>
*/
#[Computed]
final public function torrentRequests(): \Illuminate\Database\Eloquent\Collection
{
return TorrentRequest::with(['user:id,username,group_id', 'user.group', 'category', 'type', 'resolution'])
final protected \Illuminate\Database\Eloquent\Collection $torrentRequests {
get => TorrentRequest::with(['user:id,username,group_id', 'user.group', 'category', 'type', 'resolution'])
->withCount(['comments'])
->withExists('claim')
->when($this->category->movie_meta, fn ($query) => $query->where('tmdb_movie_id', '=', $this->tmdbId))
@@ -404,12 +401,10 @@ class SimilarTorrent extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, PlaylistCategory>
* @var \Illuminate\Database\Eloquent\Collection<int, PlaylistCategory>
*/
#[Computed]
final public function playlistCategories(): \Illuminate\Database\Eloquent\Collection
{
return PlaylistCategory::query()
final protected \Illuminate\Database\Eloquent\Collection $playlistCategories {
get => PlaylistCategory::query()
->with([
'playlists' => fn ($query) => $query
->withCount('torrents')
@@ -433,16 +428,10 @@ class SimilarTorrent extends Component
}
/**
* @return ?\Illuminate\Database\Eloquent\Collection<int, TmdbMovie>
* @var ?\Illuminate\Database\Eloquent\Collection<int, TmdbMovie>
*/
#[Computed]
final public function collectionMovies(): ?\Illuminate\Database\Eloquent\Collection
{
if (!$this->work instanceof TmdbMovie) {
return null;
}
return $this->work->collections()->first()?->movies()->get();
final protected ?\Illuminate\Database\Eloquent\Collection $collectionMovies {
get => $this->work instanceof TmdbMovie ? $this->work->collections()->first()?->movies()->get() : null;
}
final public function alertConfirm(): void
@@ -475,9 +464,9 @@ class SimilarTorrent extends Component
$torrents = Torrent::whereKey($this->checked)->get();
$users = [];
$title = match (true) {
$this->category->movie_meta => ($movie = TmdbMovie::find($this->tmdbId))->title.' ('.$movie->release_date->format('Y').')',
$this->category->tv_meta => ($tv = TmdbTv::find($this->tmdbId))->name.' ('.$tv->first_air_date->format('Y').')',
$this->category->game_meta => ($game = IgdbGame::find($this->igdbId))->name.' ('.$game->first_release_date->format('Y').')',
$this->category->movie_meta => ($movie = TmdbMovie::find($this->tmdbId))->title.($movie->release_date === null ? '' : ' ('.$movie->release_date->format('Y').')'),
$this->category->tv_meta => ($tv = TmdbTv::find($this->tmdbId))->name.($tv->first_air_date === null ? '' : ' ('.$tv->first_air_date->format('Y').')'),
$this->category->game_meta => ($game = IgdbGame::find($this->igdbId))->name.($game->first_release_date === null ? '' : ' ('.$game->first_release_date->format('Y').')'),
default => $torrents->pluck('name')->join(', '),
};
@@ -537,46 +526,52 @@ class SimilarTorrent extends Component
);
}
#[Computed]
final public function personalFreeleech(): bool
{
return cache()->get('personal_freeleech:'.auth()->id()) ?? false;
final protected bool $personalFreeleech {
get => cache()->get('personal_freeleech:'.auth()->id()) ?? false;
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Type>
* @var \Illuminate\Database\Eloquent\Collection<int, Type>
*/
#[Computed(seconds: 3600, cache: true)]
final public function types(): \Illuminate\Database\Eloquent\Collection
{
return Type::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $types {
get => cache()->remember(
'types',
3600,
fn () => Type::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Resolution>
* @var \Illuminate\Database\Eloquent\Collection<int, Resolution>
*/
#[Computed(seconds: 3600, cache: true)]
final public function resolutions(): \Illuminate\Database\Eloquent\Collection
{
return Resolution::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $resolutions {
get => cache()->remember(
'resolutions',
3600,
fn () => Resolution::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Region>
* @var \Illuminate\Database\Eloquent\Collection<int, Region>
*/
#[Computed(seconds: 3600, cache: true)]
final public function regions(): \Illuminate\Database\Eloquent\Collection
{
return Region::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $regions {
get => cache()->remember(
'regions',
3600,
fn () => Region::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Distributor>
* @var \Illuminate\Database\Eloquent\Collection<int, Distributor>
*/
#[Computed(seconds: 3600, cache: true)]
final public function distributors(): \Illuminate\Database\Eloquent\Collection
{
return Distributor::query()->orderBy('name')->get();
final protected \Illuminate\Database\Eloquent\Collection $distributors {
get => cache()->remember(
'distributors',
3600,
fn () => Distributor::query()->orderBy('name')->get(),
);
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -18,34 +18,39 @@ namespace App\Http\Livewire\Stats;
use App\Models\History;
use App\Models\Peer;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Lazy;
use Livewire\Component;
#[Lazy(isolate: true)]
class PeerStats extends Component
{
#[Computed(cache: true, seconds: 10 * 60)]
final public function leecherCount(): int
{
// Generally sites have more seeders than leechers, so it ends up being faster (by approximately 50%) to compute leechers and total instead of seeders and leechers.
return Peer::query()->where('seeder', '=', false)->where('active', '=', true)->count();
final protected int $leecherCount {
get => (int) cache()->remember(
'peer-stats:leecher-count',
10 * 60,
// Generally sites have more seeders than leechers, so it ends up being faster (by approximately 50%) to compute leechers and total instead of seeders and leechers.
fn () => Peer::query()->where('seeder', '=', false)->where('active', '=', true)->count()
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function peerCount(): int
{
return Peer::query()->where('active', '=', true)->count();
final protected int $peerCount {
get => (int) cache()->remember(
'peer-stats:peer-count',
10 * 60,
fn () => Peer::query()->where('active', '=', true)->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function totalSeeded(): int
{
return (int) History::query()
->join('torrents', 'history.torrent_id', '=', 'torrents.id')
->where('history.active', '=', true)
->where('history.seeder', '=', true)
->sum('size');
final protected int $totalSeeded {
get => (int) cache()->remember(
'peer-stats:total-seeded',
10 * 60,
fn () => History::query()
->join('torrents', 'history.torrent_id', '=', 'torrents.id')
->where('history.active', '=', true)
->where('history.seeder', '=', true)
->sum('size'),
);
}
final public function placeholder(): string

View File

@@ -19,41 +19,48 @@ namespace App\Http\Livewire\Stats;
use App\Models\Category;
use App\Models\Resolution;
use App\Models\Torrent;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Lazy;
use Livewire\Component;
#[Lazy(isolate: true)]
class TorrentStats extends Component
{
#[Computed(cache: true, seconds: 10 * 60)]
final public function totalCount(): int
{
return Torrent::query()->count();
final protected int $totalCount {
get => (int) cache()->remember(
'torrent-stats:total-count',
10 * 60,
fn () => Torrent::query()->count(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Resolution>
* @var \Illuminate\Database\Eloquent\Collection<int, Resolution>
*/
#[Computed(cache: true, seconds: 10 * 60)]
final public function resolutions(): \Illuminate\Database\Eloquent\Collection
{
return Resolution::query()->withCount('torrents')->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $resolutions {
get => cache()->remember(
'torrent-stats:resolutions',
10 * 60,
fn () => Resolution::query()->withCount('torrents')->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Category>
* @var \Illuminate\Database\Eloquent\Collection<int, Category>
*/
#[Computed(cache: true, seconds: 10 * 60)]
final public function categories(): \Illuminate\Database\Eloquent\Collection
{
return Category::query()->withCount('torrents')->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $categories {
get => cache()->remember(
'torrent-stats:categories',
10 * 60,
fn () => Category::query()->withCount('torrents')->orderBy('position')->get(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function sizeSum(): int
{
return (int) Torrent::query()->sum('size');
final protected int $sizeSum {
get => (int) cache()->remember(
'torrent-stats:size-sum',
10 * 60,
fn () => Torrent::query()->sum('size'),
);
}
final public function placeholder(): string

View File

@@ -17,35 +17,42 @@ declare(strict_types=1);
namespace App\Http\Livewire\Stats;
use App\Models\History;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Lazy;
use Livewire\Component;
#[Lazy(isolate: true)]
class TrafficStats extends Component
{
#[Computed(cache: true, seconds: 10 * 60)]
final public function actualUpload(): int
{
return (int) History::query()->sum('actual_uploaded');
final protected int $actualUpload {
get => (int) cache()->remember(
'traffic-stats:actual-upload',
10 * 60,
fn () => History::query()->sum('actual_uploaded'),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function creditedUpload(): int
{
return (int) History::query()->sum('uploaded');
final protected int $creditedUpload {
get => (int) cache()->remember(
'traffic-stats:credited-upload',
10 * 60,
fn () => History::query()->sum('uploaded'),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function actualDownload(): int
{
return (int) History::query()->sum('actual_downloaded');
final protected int $actualDownload {
get => (int) cache()->remember(
'traffic-stats:actual-download',
10 * 60,
fn () => History::query()->sum('actual_downloaded'),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function creditedDownload(): int
{
return (int) History::query()->sum('downloaded');
final protected int $creditedDownload {
get => (int) cache()->remember(
'traffic-stats:credited-download',
10 * 60,
fn () => History::query()->sum('downloaded'),
);
}
final public function placeholder(): string

View File

@@ -17,59 +17,74 @@ declare(strict_types=1);
namespace App\Http\Livewire\Stats;
use App\Models\User;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Lazy;
use Livewire\Component;
#[Lazy(isolate: true)]
class UserStats extends Component
{
#[Computed(cache: true, seconds: 10 * 60)]
final public function allUsers(): int
{
return User::query()->withTrashed()->count();
final protected int $allUsers {
get => (int) cache()->remember(
'user-stats:all-users',
10 * 60,
fn () => User::query()->withTrashed()->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function activeUsers(): int
{
return User::query()->whereHas('group', fn ($query) => $query->whereNotIn('slug', ['banned', 'validating', 'disabled', 'pruned']))->count();
final protected int $activeUsers {
get => (int) cache()->remember(
'user-stats:active-users',
10 * 60,
fn () => User::query()->whereHas('group', fn ($query) => $query->whereNotIn('slug', ['banned', 'validating', 'disabled', 'pruned']))->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function disableUsers(): int
{
return User::query()->whereRelation('group', 'slug', '=', 'disabled')->count();
final protected int $disableUsers {
get => (int) cache()->remember(
'user-stats:disable-users',
10 * 60,
fn () => User::query()->whereRelation('group', 'slug', '=', 'disabled')->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function prunedUsers(): int
{
return User::query()->onlyTrashed()->whereRelation('group', 'slug', '=', 'pruned')->count();
final protected int $prunedUsers {
get => (int) cache()->remember(
'user-stats:pruned-users',
10 * 60,
fn () => User::query()->onlyTrashed()->whereRelation('group', 'slug', '=', 'pruned')->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function bannedUsers(): int
{
return User::query()->whereRelation('group', 'slug', '=', 'banned')->count();
final protected int $bannedUsers {
get => (int) cache()->remember(
'user-stats:banned-users',
10 * 60,
fn () => User::query()->whereRelation('group', 'slug', '=', 'banned')->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function usersActiveToday(): int
{
return User::query()->where('last_action', '>', now()->subDay())->count();
final protected int $usersActiveToday {
get => (int) cache()->remember(
'user-stats:users-active-today',
10 * 60,
fn () => User::query()->where('last_action', '>', now()->subDay())->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function usersActiveThisWeek(): int
{
return User::query()->where('last_action', '>', now()->subWeek())->count();
final protected int $usersActiveThisWeek {
get => (int) cache()->remember(
'user-stats:users-active-this-week',
10 * 60,
fn () => User::query()->where('last_action', '>', now()->subWeek())->count(),
);
}
#[Computed(cache: true, seconds: 10 * 60)]
final public function usersActiveThisMonth(): int
{
return User::query()->where('last_action', '>', now()->subMonth())->count();
final protected int $usersActiveThisMonth {
get => (int) cache()->remember(
'user-stats:users-active-this-month',
10 * 60,
fn () => User::query()->where('last_action', '>', now()->subMonth())->count(),
);
}
final public function placeholder(): string

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\Forum;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
@@ -26,12 +25,10 @@ class SubscribedForum extends Component
use WithPagination;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Forum>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Forum>
*/
#[Computed]
final public function forums()
{
return Forum::query()
final protected $forums {
get => Forum::query()
->with('latestPoster', 'lastRepliedTopic')
->whereRelation('subscribedUsers', 'users.id', '=', auth()->id())
->authorized(canReadTopic: true)

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\Topic;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
@@ -26,12 +25,10 @@ class SubscribedTopic extends Component
use WithPagination;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
*/
#[Computed]
final public function topics(): \Illuminate\Pagination\LengthAwarePaginator
{
return Topic::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $topics {
get => Topic::query()
->select('topics.*')
->with([
'user.group',

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Subtitle;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -60,12 +59,11 @@ class SubtitleSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Subtitle>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Subtitle>
*/
#[Computed]
final public function subtitles(): \Illuminate\Pagination\LengthAwarePaginator
{
return Subtitle::with(['user.group', 'torrent.category', 'language'])
final protected \Illuminate\Pagination\LengthAwarePaginator $subtitles {
get => Subtitle::query()
->with(['user.group', 'torrent.category', 'language'])
->whereHas('torrent')
->when($this->search, fn ($query) => $query->where('title', 'like', '%'.$this->search.'%'))
->when($this->categories, fn ($query) => $query->whereHas('torrent', fn ($query) => $query->whereIn('category_id', $this->categories)))

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Ticket;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Ticket> $tickets
*/
class TicketSearch extends Component
{
use LivewireSort;
@@ -71,12 +67,10 @@ class TicketSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Ticket>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Ticket>
*/
#[Computed]
final public function tickets(): \Illuminate\Pagination\LengthAwarePaginator
{
return Ticket::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $tickets {
get => Ticket::query()
->with(['user.group', 'staff.group', 'category', 'priority'])
->when(!$this->user->group->is_modo, fn ($query) => $query->where('user_id', '=', $this->user->id))
->when(

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\TmdbCollection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -37,12 +36,10 @@ class TmdbCollectionSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TmdbCollection>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TmdbCollection>
*/
#[Computed]
final public function collections(): \Illuminate\Pagination\LengthAwarePaginator
{
return TmdbCollection::withCount('movies')
final protected \Illuminate\Pagination\LengthAwarePaginator $collections {
get => TmdbCollection::withCount('movies')
->with('movies')
->when($this->search !== '', fn ($query) => $query->where('name', 'LIKE', '%'.$this->search.'%'))
->oldest('name')

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\TmdbCompany;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -37,12 +36,10 @@ class TmdbCompanySearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TmdbCompany>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TmdbCompany>
*/
#[Computed]
final public function companies(): \Illuminate\Pagination\LengthAwarePaginator
{
return TmdbCompany::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $companies {
get => TmdbCompany::query()
->withCount([
'movie' => fn ($query) => $query->has('torrents'),
'tv' => fn ($query) => $query->has('torrents'),

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\TmdbNetwork;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -37,12 +36,10 @@ class TmdbNetworkSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TmdbNetwork>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TmdbNetwork>
*/
#[Computed]
final public function networks(): \Illuminate\Pagination\LengthAwarePaginator
{
return TmdbNetwork::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $networks {
get => TmdbNetwork::query()
->withCount([
'tv' => fn ($query) => $query->has('torrents'),
])

View File

@@ -20,7 +20,6 @@ use App\Enums\Occupation;
use App\Models\TmdbPerson;
use App\Models\Torrent;
use App\Models\User;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
@@ -50,10 +49,8 @@ class TmdbPersonCredit extends Component
};
}
#[Computed]
final public function personalFreeleech(): bool
{
return cache()->get('personal_freeleech:'.auth()->user()->id) ?? false;
final protected bool $personalFreeleech {
get => cache()->get('personal_freeleech:'.auth()->user()->id) ?? false;
}
/*
@@ -64,291 +61,271 @@ class TmdbPersonCredit extends Component
$value = Occupation::from($value);
}
#[Computed]
public function directedCount(): int
{
return $this->person->directedMovies()->count() + $this->person->directedTv()->count();
final protected int $directedCount {
get => $this->person->directedMovies()->count() + $this->person->directedTv()->count();
}
#[Computed]
public function createdCount(): int
{
return $this->person->createdTv()->count();
final protected int $createdCount {
get => $this->person->createdTv()->count();
}
#[Computed]
public function writtenCount(): int
{
return $this->person->writtenMovies()->count() + $this->person->writtenTv()->count();
final protected int $writtenCount {
get => $this->person->writtenMovies()->count() + $this->person->writtenTv()->count();
}
#[Computed]
public function producedCount(): int
{
return $this->person->producedMovies()->count() + $this->person->producedTv()->count();
final protected int $producedCount {
get => $this->person->producedMovies()->count() + $this->person->producedTv()->count();
}
#[Computed]
public function composedCount(): int
{
return $this->person->composedMovies()->count() + $this->person->composedTv()->count();
final protected int $composedCount {
get => $this->person->composedMovies()->count() + $this->person->composedTv()->count();
}
#[Computed]
public function cinematographedCount(): int
{
return $this->person->cinematographedMovies()->count() + $this->person->cinematographedTv()->count();
final protected int $cinematographedCount {
get => $this->person->cinematographedMovies()->count() + $this->person->cinematographedTv()->count();
}
#[Computed]
public function editedCount(): int
{
return $this->person->editedMovies()->count() + $this->person->editedTv()->count();
final protected int $editedCount {
get => $this->person->editedMovies()->count() + $this->person->editedTv()->count();
}
#[Computed]
public function productionDesignedCount(): int
{
return $this->person->productionDesignedMovies()->count() + $this->person->productionDesignedTv()->count();
final protected int $productionDesignedCount {
get => $this->person->productionDesignedMovies()->count() + $this->person->productionDesignedTv()->count();
}
#[Computed]
public function artDirectedCount(): int
{
return $this->person->artDirectedMovies()->count() + $this->person->artDirectedTv()->count();
final protected int $artDirectedCount {
get => $this->person->artDirectedMovies()->count() + $this->person->artDirectedTv()->count();
}
#[Computed]
public function actedCount(): int
{
return $this->person->actedMovies()->count() + $this->person->actedTv()->count();
final protected int $actedCount {
get => $this->person->actedMovies()->count() + $this->person->actedTv()->count();
}
/**
* @return \Illuminate\Support\Collection<int, Torrent>
*/
#[Computed]
final public function medias(): \Illuminate\Support\Collection
{
if ($this->occupationId === null) {
return collect();
}
final protected \Illuminate\Support\Collection $medias {
get {
if ($this->occupationId === null) {
return collect();
}
$movies = $this->person
->movie()
->with('genres', 'directors')
->wherePivot('occupation_id', '=', $this->occupationId)
->orderBy('release_date')
->get()
// Since the credits table unique index has nullable columns, we get duplicate credits, which means duplicate movies
->unique();
$tv = $this->person
->tv()
->with('genres', 'creators')
->wherePivot('occupation_id', '=', $this->occupationId)
->orderBy('first_air_date')
->get()
// Since the credits table unique index has nullable columns, we get duplicate credits, which means duplicate tv
->unique();
$movies = $this->person
->movie()
->with('genres', 'directors')
->wherePivot('occupation_id', '=', $this->occupationId)
->orderBy('release_date')
->get()
// Since the credits table unique index has nullable columns, we get duplicate credits, which means duplicate movies
->unique();
$tv = $this->person
->tv()
->with('genres', 'creators')
->wherePivot('occupation_id', '=', $this->occupationId)
->orderBy('first_air_date')
->get()
// Since the credits table unique index has nullable columns, we get duplicate credits, which means duplicate tv
->unique();
$movieIds = $movies->pluck('id');
$tvIds = $tv->pluck('id');
$movieIds = $movies->pluck('id');
$tvIds = $tv->pluck('id');
$torrents = Torrent::query()
->with('type:id,name,position', 'resolution:id,name,position')
->select([
'id',
'name',
'info_hash',
'size',
'leechers',
'seeders',
'times_completed',
'category_id',
'user_id',
'season_number',
'episode_number',
'tmdb_movie_id',
'tmdb_tv_id',
'free',
'doubleup',
'highspeed',
'sticky',
'internal',
'created_at',
'bumped_at',
'type_id',
'resolution_id',
'personal_release',
])
->selectRaw(<<<'SQL'
$torrents = Torrent::query()
->with('type:id,name,position', 'resolution:id,name,position')
->select([
'id',
'name',
'info_hash',
'size',
'leechers',
'seeders',
'times_completed',
'category_id',
'user_id',
'season_number',
'episode_number',
'tmdb_movie_id',
'tmdb_tv_id',
'free',
'doubleup',
'highspeed',
'sticky',
'internal',
'created_at',
'bumped_at',
'type_id',
'resolution_id',
'personal_release',
])
->selectRaw(<<<'SQL'
CASE
WHEN category_id IN (SELECT `id` from `categories` where `movie_meta` = 1) THEN 'movie'
WHEN category_id IN (SELECT `id` from `categories` where `tv_meta` = 1) THEN 'tv'
END as meta
SQL)
->withCount([
'comments',
])
->when(
!config('announce.external_tracker.is_enabled'),
fn ($query) => $query->withCount([
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
]),
)
->when(
config('other.thanks-system.is-enabled'),
fn ($query) => $query->withCount('thanks')
)
->withExists([
'featured as featured',
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'bookmarks' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'history as seeding' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
'trump',
])
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereIntegerInRaw('tmdb_movie_id', $movieIds)
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereIntegerInRaw('tmdb_tv_id', $tvIds)
)
)
->get();
->withCount([
'comments',
])
->when(
!config('announce.external_tracker.is_enabled'),
fn ($query) => $query->withCount([
'seeds' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
'leeches' => fn ($query) => $query->where('active', '=', true)->where('visible', '=', true),
]),
)
->when(
config('other.thanks-system.is-enabled'),
fn ($query) => $query->withCount('thanks')
)
->withExists([
'featured as featured',
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'bookmarks' => fn ($query) => $query->where('user_id', '=', auth()->id()),
'history as seeding' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', auth()->id())
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
'trump',
])
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereIntegerInRaw('tmdb_movie_id', $movieIds)
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereIntegerInRaw('tmdb_tv_id', $tvIds)
)
)
->get();
$groupedTorrents = [];
$groupedTorrents = [];
foreach ($torrents as &$torrent) {
// Memoizing and avoiding casts reduces runtime duration from 70ms to 40ms.
// If accessing laravel's attributes array directly, it's reduced to 11ms,
// but the attributes array is marked as protected so we can't access it.
$tmdb = $torrent->getAttributeValue('tmdb_movie_id') ?: $torrent->getAttributeValue('tmdb_tv_id');
$type = $torrent->getRelationValue('type')->getAttributeValue('name');
foreach ($torrents as &$torrent) {
// Memoizing and avoiding casts reduces runtime duration from 70ms to 40ms.
// If accessing laravel's attributes array directly, it's reduced to 11ms,
// but the attributes array is marked as protected so we can't access it.
$tmdb = $torrent->getAttributeValue('tmdb_movie_id') ?: $torrent->getAttributeValue('tmdb_tv_id');
$type = $torrent->getRelationValue('type')->getAttributeValue('name');
switch ($torrent->getAttributeValue('meta')) {
case 'movie':
$groupedTorrents['movie'][$tmdb]['Movie'][$type][] = $torrent;
$groupedTorrents['movie'][$tmdb]['category_id'] = $torrent->getAttributeValue('category_id');
switch ($torrent->getAttributeValue('meta')) {
case 'movie':
$groupedTorrents['movie'][$tmdb]['Movie'][$type][] = $torrent;
$groupedTorrents['movie'][$tmdb]['category_id'] = $torrent->getAttributeValue('category_id');
break;
case 'tv':
$episode = $torrent->getAttributeValue('episode_number');
$season = $torrent->getAttributeValue('season_number');
break;
case 'tv':
$episode = $torrent->getAttributeValue('episode_number');
$season = $torrent->getAttributeValue('season_number');
if ($season == 0) {
if ($episode == 0) {
$groupedTorrents['tv'][$tmdb]['Complete Pack'][$type][] = $torrent;
if ($season == 0) {
if ($episode == 0) {
$groupedTorrents['tv'][$tmdb]['Complete Pack'][$type][] = $torrent;
} else {
$groupedTorrents['tv'][$tmdb]['Specials']["Special {$episode}"][$type][] = $torrent;
}
} else {
$groupedTorrents['tv'][$tmdb]['Specials']["Special {$episode}"][$type][] = $torrent;
}
} else {
if ($episode == 0) {
$groupedTorrents['tv'][$tmdb]['Seasons']["Season {$season}"]['Season Pack'][$type][] = $torrent;
} else {
$groupedTorrents['tv'][$tmdb]['Seasons']["Season {$season}"]['Episodes']["Episode {$episode}"][$type][] = $torrent;
}
}
$groupedTorrents['tv'][$tmdb]['category_id'] = $torrent->getAttributeValue('category_id');
}
}
foreach ($groupedTorrents as $mediaType => &$workTorrents) {
switch ($mediaType) {
case 'movie':
foreach ($workTorrents as &$movieTorrents) {
$this->sortTorrentTypes($movieTorrents['Movie']);
}
break;
case 'tv':
foreach ($workTorrents as &$tvTorrents) {
foreach ($tvTorrents as $packOrSpecialOrSeasonsType => &$packOrSpecialOrSeasons) {
switch ($packOrSpecialOrSeasonsType) {
case 'Complete Pack':
$this->sortTorrentTypes($packOrSpecialOrSeasons);
break;
case 'Specials':
krsort($packOrSpecialOrSeasons, SORT_NATURAL);
foreach ($packOrSpecialOrSeasons as &$specialTorrents) {
$this->sortTorrentTypes($specialTorrents);
}
break;
case 'Seasons':
krsort($packOrSpecialOrSeasons, SORT_NATURAL);
foreach ($packOrSpecialOrSeasons as &$season) {
foreach ($season as $packOrEpisodesType => &$packOrEpisodes) {
switch ($packOrEpisodesType) {
case 'Season Pack':
$this->sortTorrentTypes($packOrEpisodes);
break;
case 'Episodes':
krsort($packOrEpisodes, SORT_NATURAL);
foreach ($packOrEpisodes as &$episodeTorrents) {
$this->sortTorrentTypes($episodeTorrents);
}
break;
}
}
}
if ($episode == 0) {
$groupedTorrents['tv'][$tmdb]['Seasons']["Season {$season}"]['Season Pack'][$type][] = $torrent;
} else {
$groupedTorrents['tv'][$tmdb]['Seasons']["Season {$season}"]['Episodes']["Episode {$episode}"][$type][] = $torrent;
}
}
}
$groupedTorrents['tv'][$tmdb]['category_id'] = $torrent->getAttributeValue('category_id');
}
}
}
$medias = collect();
foreach ($groupedTorrents as $mediaType => &$workTorrents) {
switch ($mediaType) {
case 'movie':
foreach ($workTorrents as &$movieTorrents) {
$this->sortTorrentTypes($movieTorrents['Movie']);
}
foreach ($movies as $movie) {
if (\array_key_exists('movie', $groupedTorrents) && \array_key_exists($movie->id, $groupedTorrents['movie'])) {
$media = $movie;
$media->setAttribute('meta', 'movie');
$media->setRelation('torrents', $groupedTorrents['movie'][$movie->id]);
$media->setAttribute('category_id', $media->torrents['category_id']);
$medias->add($media);
break;
case 'tv':
foreach ($workTorrents as &$tvTorrents) {
foreach ($tvTorrents as $packOrSpecialOrSeasonsType => &$packOrSpecialOrSeasons) {
switch ($packOrSpecialOrSeasonsType) {
case 'Complete Pack':
$this->sortTorrentTypes($packOrSpecialOrSeasons);
break;
case 'Specials':
krsort($packOrSpecialOrSeasons, SORT_NATURAL);
foreach ($packOrSpecialOrSeasons as &$specialTorrents) {
$this->sortTorrentTypes($specialTorrents);
}
break;
case 'Seasons':
krsort($packOrSpecialOrSeasons, SORT_NATURAL);
foreach ($packOrSpecialOrSeasons as &$season) {
foreach ($season as $packOrEpisodesType => &$packOrEpisodes) {
switch ($packOrEpisodesType) {
case 'Season Pack':
$this->sortTorrentTypes($packOrEpisodes);
break;
case 'Episodes':
krsort($packOrEpisodes, SORT_NATURAL);
foreach ($packOrEpisodes as &$episodeTorrents) {
$this->sortTorrentTypes($episodeTorrents);
}
break;
}
}
}
}
}
}
}
}
}
foreach ($tv as $show) {
if (\array_key_exists('tv', $groupedTorrents) && \array_key_exists($show->id, $groupedTorrents['tv'])) {
$media = $show;
$media->setAttribute('meta', 'tv');
$media->setRelation('torrents', $groupedTorrents['tv'][$show->id]);
$media->setAttribute('category_id', $media->torrents['category_id']);
$medias->add($media);
$medias = collect();
foreach ($movies as $movie) {
if (\array_key_exists('movie', $groupedTorrents) && \array_key_exists($movie->id, $groupedTorrents['movie'])) {
$media = $movie;
$media->setAttribute('meta', 'movie');
$media->setRelation('torrents', $groupedTorrents['movie'][$movie->id]);
$media->setAttribute('category_id', $media->torrents['category_id']);
$medias->add($media);
}
}
}
return $medias;
foreach ($tv as $show) {
if (\array_key_exists('tv', $groupedTorrents) && \array_key_exists($show->id, $groupedTorrents['tv'])) {
$media = $show;
$media->setAttribute('meta', 'tv');
$media->setRelation('torrents', $groupedTorrents['tv'][$show->id]);
$media->setAttribute('category_id', $media->torrents['category_id']);
$medias->add($media);
}
}
return $medias;
}
}
/**

View File

@@ -17,7 +17,6 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\TmdbPerson;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -46,12 +45,11 @@ class TmdbPersonSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TmdbPerson>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TmdbPerson>
*/
#[Computed]
final public function persons(): \Illuminate\Pagination\LengthAwarePaginator
{
return TmdbPerson::select(['id', 'still', 'name'])
final protected \Illuminate\Pagination\LengthAwarePaginator $persons {
get => TmdbPerson::query()
->select(['id', 'still', 'name'])
->when($this->search !== '', fn ($query) => $query->where('name', 'LIKE', '%'.$this->search.'%'))
->when($this->occupationIds !== [], fn ($query) => $query->whereHas('credits', fn ($query) => $query->whereIn('occupation_id', $this->occupationIds)))
->when($this->firstCharacter !== '', fn ($query) => $query->where('name', 'LIKE', $this->firstCharacter.'%'))
@@ -60,12 +58,11 @@ class TmdbPersonSearch extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, TmdbPerson>
* @var \Illuminate\Database\Eloquent\Collection<int, TmdbPerson>
*/
#[Computed]
final public function firstCharacters(): \Illuminate\Database\Eloquent\Collection
{
return TmdbPerson::selectRaw('substr(name, 1, 1) as alpha, count(*) as count')
final protected \Illuminate\Database\Eloquent\Collection $firstCharacters {
get => TmdbPerson::query()
->selectRaw('substr(name, 1, 1) as alpha, count(*) as count')
->when($this->search !== '', fn ($query) => $query->where('name', 'LIKE', '%'.$this->search.'%'))
->when($this->occupationIds !== [], fn ($query) => $query->whereHas('credits', fn ($query) => $query->whereIn('occupation_id', $this->occupationIds)))
->groupBy('alpha')

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Torrent;
use App\Models\User;
use App\Traits\TorrentMeta;
use Livewire\Attributes\Computed;
use Livewire\Component;
class TopTorrents extends Component
@@ -36,74 +35,72 @@ class TopTorrents extends Component
}
/**
* @return \Illuminate\Support\Collection<int, Torrent>
* @var \Illuminate\Support\Collection<int, Torrent>
*/
#[Computed]
final public function torrents(): \Illuminate\Support\Collection
{
$torrents = Torrent::query()
->with(['user.group', 'category', 'type', 'resolution'])
->withExists([
'featured as featured',
'bookmarks' => fn ($query) => $query->where('user_id', '=', $this->user->id),
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', $this->user->id),
'history as seeding' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
])
->selectRaw(<<<SQL
CASE
WHEN category_id IN (SELECT id FROM categories WHERE movie_meta = 1) THEN 'movie'
WHEN category_id IN (SELECT id FROM categories WHERE tv_meta = 1) THEN 'tv'
WHEN category_id IN (SELECT id FROM categories WHERE game_meta = 1) THEN 'game'
WHEN category_id IN (SELECT id FROM categories WHERE music_meta = 1) THEN 'music'
WHEN category_id IN (SELECT id FROM categories WHERE no_meta = 1) THEN 'no'
END AS meta
SQL)
->withCount(['thanks', 'comments'])
->when($this->tab === 'newest', fn ($query) => $query->orderByDesc('id'))
->when($this->tab === 'seeded', fn ($query) => $query->orderByDesc('seeders'))
->when(
$this->tab === 'dying',
fn ($query) => $query
->where('seeders', '=', 1)
->where('times_completed', '>=', 1)
->orderByDesc('leechers')
)
->when($this->tab === 'leeched', fn ($query) => $query->orderByDesc('leechers'))
->when(
$this->tab === 'dead',
fn ($query) => $query
->where('seeders', '=', 0)
->orderByDesc('leechers')
)
->take(5)
->get();
final protected \Illuminate\Support\Collection $torrents {
get {
$torrents = Torrent::query()
->with(['user.group', 'category', 'type', 'resolution'])
->withExists([
'featured as featured',
'bookmarks' => fn ($query) => $query->where('user_id', '=', $this->user->id),
'freeleechTokens' => fn ($query) => $query->where('user_id', '=', $this->user->id),
'history as seeding' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 1)
->where('seeder', '=', 1),
'history as leeching' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 1)
->where('seeder', '=', 0),
'history as not_completed' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 0)
->where('seeder', '=', 0)
->whereNull('completed_at'),
'history as not_seeding' => fn ($query) => $query->where('user_id', '=', $this->user->id)
->where('active', '=', 0)
->where(
fn ($query) => $query
->where('seeder', '=', 1)
->orWhereNotNull('completed_at')
),
])
->selectRaw(<<<SQL
CASE
WHEN category_id IN (SELECT id FROM categories WHERE movie_meta = 1) THEN 'movie'
WHEN category_id IN (SELECT id FROM categories WHERE tv_meta = 1) THEN 'tv'
WHEN category_id IN (SELECT id FROM categories WHERE game_meta = 1) THEN 'game'
WHEN category_id IN (SELECT id FROM categories WHERE music_meta = 1) THEN 'music'
WHEN category_id IN (SELECT id FROM categories WHERE no_meta = 1) THEN 'no'
END AS meta
SQL)
->withCount(['thanks', 'comments'])
->when($this->tab === 'newest', fn ($query) => $query->orderByDesc('id'))
->when($this->tab === 'seeded', fn ($query) => $query->orderByDesc('seeders'))
->when(
$this->tab === 'dying',
fn ($query) => $query
->where('seeders', '=', 1)
->where('times_completed', '>=', 1)
->orderByDesc('leechers')
)
->when($this->tab === 'leeched', fn ($query) => $query->orderByDesc('leechers'))
->when(
$this->tab === 'dead',
fn ($query) => $query
->where('seeders', '=', 0)
->orderByDesc('leechers')
)
->take(5)
->get();
// See app/Traits/TorrentMeta.php
$this->scopeMeta($torrents);
// See app/Traits/TorrentMeta.php
$this->scopeMeta($torrents);
return $torrents;
return $torrents;
}
}
#[Computed]
final public function personalFreeleech(): bool
{
return cache()->get('personal_freeleech:'.$this->user->id) ?? false;
final protected bool $personalFreeleech {
get => cache()->get('personal_freeleech:'.$this->user->id) ?? false;
}
final public function render(): \Illuminate\Contracts\View\Factory | \Illuminate\Contracts\View\View | \Illuminate\Contracts\Foundation\Application

View File

@@ -12,7 +12,6 @@ use App\Models\Thank;
use App\Models\Torrent;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Component;
class TopUsers extends Component
@@ -20,175 +19,208 @@ class TopUsers extends Component
public string $tab = 'uploaders';
/**
* @return \Illuminate\Support\Collection<int, Torrent>
* @var \Illuminate\Support\Collection<int, Torrent>
*/
#[Computed(cache: true)]
final public function uploaders(): \Illuminate\Support\Collection
{
return Torrent::with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $uploaders {
get => cache()->remember(
'top-users:uploaders',
3600,
fn () => Torrent::query()
->with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, History>
* @var \Illuminate\Support\Collection<int, History>
*/
#[Computed(cache: true)]
final public function downloaders(): \Illuminate\Support\Collection
{
return History::with(['user.group'])
->select(DB::raw('user_id, count(distinct torrent_id) as value'))
->whereNotNull('completed_at')
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $downloaders {
get => cache()->remember(
'top-users:downloaders',
3600,
fn () => History::query()
->with(['user.group'])
->select(DB::raw('user_id, count(distinct torrent_id) as value'))
->whereNotNull('completed_at')
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, User>
* @var \Illuminate\Support\Collection<int, User>
*/
#[Computed(cache: true)]
final public function uploaded(): \Illuminate\Support\Collection
{
return User::select(['id', 'group_id', 'username', 'uploaded', 'image'])
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('uploaded')
->take(8)
->get();
final protected \Illuminate\Support\Collection $uploaded {
get => cache()->remember(
'top-users:uploaded',
3600,
fn () => User::query()
->select(['id', 'group_id', 'username', 'uploaded', 'image'])
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('uploaded')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, User>
* @var \Illuminate\Support\Collection<int, User>
*/
#[Computed(cache: true)]
final public function downloaded(): \Illuminate\Support\Collection
{
return User::select(['id', 'group_id', 'username', 'downloaded', 'image'])
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('downloaded')
->take(8)
->get();
final protected \Illuminate\Support\Collection $downloaded {
get => cache()->remember(
'top-users:downloaded',
3600,
fn () => User::query()
->select(['id', 'group_id', 'username', 'downloaded', 'image'])
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('downloaded')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, Peer>
* @var \Illuminate\Support\Collection<int, Peer>
*/
#[Computed(cache: true)]
final public function seeders(): \Illuminate\Support\Collection
{
return Peer::with(['user.group'])
->select(DB::raw('user_id, count(distinct torrent_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('seeder', '=', 1)
->where('active', '=', 1)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $seeders {
get => cache()->remember(
'top-users:seeders',
3600,
fn () => Peer::query()
->with(['user.group'])
->select(DB::raw('user_id, count(distinct torrent_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('seeder', '=', 1)
->where('active', '=', 1)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, User>
* @var \Illuminate\Support\Collection<int, User>
*/
#[Computed(cache: true)]
final public function seedtimes(): \Illuminate\Support\Collection
{
return User::withSum('history as seedtime', 'seedtime')
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('seedtime')
->take(8)
->get();
final protected \Illuminate\Support\Collection $seedtimes {
get => cache()->remember(
'top-users:seedtimes',
3600,
fn () => User::query()
->withSum('history as seedtime', 'seedtime')
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('seedtime')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, User>
* @var \Illuminate\Support\Collection<int, User>
*/
#[Computed(cache: true)]
final public function served(): \Illuminate\Support\Collection
{
return User::withCount('uploadSnatches')
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('upload_snatches_count')
->take(8)
->get();
final protected \Illuminate\Support\Collection $served {
get => cache()->remember(
'top-users:served',
3600,
fn () => User::query()
->withCount('uploadSnatches')
->with('group')
->where('id', '!=', User::SYSTEM_USER_ID)
->whereDoesntHave('group', fn ($query) => $query->whereIn('slug', ['banned', 'validating', 'disabled', 'pruned']))
->orderByDesc('upload_snatches_count')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, Comment>
* @var \Illuminate\Support\Collection<int, Comment>
*/
#[Computed(cache: true)]
final public function commenters(): \Illuminate\Support\Collection
{
return Comment::with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $commenters {
get => cache()->remember(
'top-users:commenters',
3600,
fn () => Comment::query()
->with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, Post>
* @var \Illuminate\Support\Collection<int, Post>
*/
#[Computed(cache: true)]
final public function posters(): \Illuminate\Support\Collection
{
return Post::with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $posters {
get => cache()->remember(
'top-users:posters',
3600,
fn () => Post::query()
->with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, Thank>
* @var \Illuminate\Support\Collection<int, Thank>
*/
#[Computed(cache: true)]
final public function thankers(): \Illuminate\Support\Collection
{
return Thank::with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $thankers {
get => cache()->remember(
'top-users:thankers',
3600,
fn () => Thank::query()
->with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, Torrent>
* @var \Illuminate\Support\Collection<int, Torrent>
*/
#[Computed(cache: true)]
final public function personals(): \Illuminate\Support\Collection
{
return Torrent::with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->where('personal_release', '=', true)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get();
final protected \Illuminate\Support\Collection $personals {
get => cache()->remember(
'top-users:personals',
3600,
fn () => Torrent::query()
->with(['user.group'])
->select(DB::raw('user_id, COUNT(user_id) as value'))
->where('user_id', '!=', User::SYSTEM_USER_ID)
->where('anon', '=', false)
->where('personal_release', '=', true)
->groupBy('user_id')
->orderByDesc('value')
->take(8)
->get(),
);
}
public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -20,7 +20,6 @@ use App\Models\Post;
use App\Models\Topic;
use App\Models\TopicRead;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -47,35 +46,35 @@ class TopicPostSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Post>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Post>
*/
#[Computed]
final public function posts(): \Illuminate\Pagination\LengthAwarePaginator
{
$posts = Post::query()
->with('user', 'user.group')
->withCount('likes', 'dislikes', 'authorPosts', 'authorTopics')
->withSum('tips', 'bon')
->where('topic_id', '=', $this->topic->id)
->authorized(canReadTopic: true)
->when($this->search !== '', fn ($query) => $query->where('content', 'LIKE', '%'.$this->search.'%'))
->orderBy('created_at')
->paginate(25);
final protected \Illuminate\Pagination\LengthAwarePaginator $posts {
get {
$posts = Post::query()
->with('user', 'user.group')
->withCount('likes', 'dislikes', 'authorPosts', 'authorTopics')
->withSum('tips', 'bon')
->where('topic_id', '=', $this->topic->id)
->authorized(canReadTopic: true)
->when($this->search !== '', fn ($query) => $query->where('content', 'LIKE', '%'.$this->search.'%'))
->orderBy('created_at')
->paginate(25);
if ($lastPost = $posts->getCollection()->last()) {
TopicRead::upsert([[
'topic_id' => $this->topic->id,
'user_id' => auth()->id(),
'last_read_post_id' => $lastPost->id,
]], [
'topic_id',
'user_id'
], [
'last_read_post_id' => DB::raw('GREATEST(last_read_post_id, VALUES(last_read_post_id))')
]);
if ($lastPost = $posts->getCollection()->last()) {
TopicRead::upsert([[
'topic_id' => $this->topic->id,
'user_id' => auth()->id(),
'last_read_post_id' => $lastPost->id,
]], [
'topic_id',
'user_id'
], [
'last_read_post_id' => DB::raw('GREATEST(last_read_post_id, VALUES(last_read_post_id))')
]);
}
return $posts;
}
return $posts;
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\ForumCategory;
use App\Models\Topic;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -59,12 +58,10 @@ class TopicSearch extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, ForumCategory>
* @var \Illuminate\Database\Eloquent\Collection<int, ForumCategory>
*/
#[Computed]
final public function forumCategories(): \Illuminate\Database\Eloquent\Collection
{
return ForumCategory::query()
final protected \Illuminate\Database\Eloquent\Collection $forumCategories {
get => ForumCategory::query()
->with([
'forums' => fn ($query) => $query->authorized(canReadTopic: true)
])
@@ -74,12 +71,10 @@ class TopicSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Topic>
*/
#[Computed]
final public function topics(): \Illuminate\Pagination\LengthAwarePaginator
{
return Topic::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $topics {
get => Topic::query()
->select('topics.*')
->with([
'user.group',

View File

@@ -16,19 +16,14 @@ declare(strict_types=1);
namespace App\Http\Livewire;
use App\Models\Announce;
use App\Models\TorrentDownload;
use App\Traits\LivewireSort;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Announce> $announces
*/
class TorrentDownloadSearch extends Component
{
use LivewireSort;
@@ -92,12 +87,10 @@ class TorrentDownloadSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TorrentDownload>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TorrentDownload>
*/
#[Computed]
final public function torrentDownloads(): \Illuminate\Pagination\LengthAwarePaginator
{
return TorrentDownload::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $torrentDownloads {
get => TorrentDownload::query()
->with([
'user' => fn ($query) => $query->with('group')->withTrashed(),
'torrent:id,name'

View File

@@ -25,7 +25,6 @@ use App\Models\Type;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -125,68 +124,74 @@ class TorrentRequestSearch extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Category>
* @var \Illuminate\Database\Eloquent\Collection<int, Category>
*/
#[Computed(seconds: 3600, cache: true)]
final public function categories(): \Illuminate\Database\Eloquent\Collection
{
return Category::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $categories {
get => cache()->remember(
'categories',
3600,
fn () => Category::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Type>
* @var \Illuminate\Database\Eloquent\Collection<int, Type>
*/
#[Computed(seconds: 3600, cache: true)]
final public function types(): \Illuminate\Database\Eloquent\Collection
{
return Type::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $types {
get => cache()->remember(
'types',
3600,
fn () => Type::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, Resolution>
* @var \Illuminate\Database\Eloquent\Collection<int, Resolution>
*/
#[Computed(seconds: 3600, cache: true)]
final public function resolutions(): \Illuminate\Database\Eloquent\Collection
{
return Resolution::query()->orderBy('position')->get();
final protected \Illuminate\Database\Eloquent\Collection $resolutions {
get => cache()->remember(
'resolutions',
3600,
fn () => Resolution::query()->orderBy('position')->get(),
);
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, TmdbGenre>
* @var \Illuminate\Database\Eloquent\Collection<int, TmdbGenre>
*/
#[Computed(seconds: 3600, cache: true)]
final public function genres(): \Illuminate\Database\Eloquent\Collection
{
return TmdbGenre::query()->orderBy('name')->get();
final protected \Illuminate\Database\Eloquent\Collection $genres {
get => cache()->remember(
'genres',
3600,
fn () => TmdbGenre::query()->orderBy('name')->get(),
);
}
/**
* @return \Illuminate\Support\Collection<int, TmdbMovie>
* @var \Illuminate\Support\Collection<int, TmdbMovie>
*/
#[Computed(seconds: 3600, cache: true)]
final public function primaryLanguages(): \Illuminate\Support\Collection
{
return TmdbMovie::query()
->select('original_language')
->distinct()
->orderBy('original_language')
->pluck('original_language');
final protected \Illuminate\Support\Collection $primaryLanguages {
get => cache()->remember(
'original-languages',
3600,
fn () => TmdbMovie::query()
->select('original_language')
->distinct()
->orderBy('original_language')
->pluck('original_language'),
);
}
#[Computed]
final public function torrentRequestStat(): ?object
{
return DB::table('requests')
final protected ?object $torrentRequestStat {
get => DB::table('requests')
->selectRaw('count(*) as total')
->selectRaw('count(case when filled_by is not null then 1 end) as filled')
->selectRaw('count(case when filled_by is null then 1 end) as unfilled')
->first();
}
#[Computed]
final public function torrentRequestBountyStat(): ?object
{
return DB::table('requests')
final protected ?object $torrentRequestBountyStat {
get => DB::table('requests')
->selectRaw('coalesce(sum(bounty), 0) as total')
->selectRaw('coalesce(sum(case when filled_by is not null then bounty end), 0) as claimed')
->selectRaw('coalesce(sum(case when filled_by is null then bounty end), 0) as unclaimed')
@@ -194,129 +199,129 @@ class TorrentRequestSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TorrentRequest>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TorrentRequest>
*/
#[Computed]
final public function torrentRequests(): \Illuminate\Pagination\LengthAwarePaginator
{
$user = auth()->user();
$isRegexAllowed = $user->group->is_modo;
$isRegex = fn ($field) => $isRegexAllowed
&& \strlen((string) $field) > 2
&& $field[0] === '/'
&& $field[-1] === '/'
&& @preg_match($field, 'Validate regex') !== false;
final protected \Illuminate\Pagination\LengthAwarePaginator $torrentRequests {
get {
$user = auth()->user();
$isRegexAllowed = $user->group->is_modo;
$isRegex = fn ($field) => $isRegexAllowed
&& \strlen((string) $field) > 2
&& $field[0] === '/'
&& $field[-1] === '/'
&& @preg_match($field, 'Validate regex') !== false;
return TorrentRequest::with(['user:id,username,group_id', 'user.group', 'category', 'type', 'resolution'])
->withCount(['comments', 'bounties'])
->withExists('claim')
->when(
$this->name !== '',
fn ($query) => $query
->when(
$isRegex($this->name),
fn ($query) => $query->where('name', 'REGEXP', substr($this->name, 1, -1)),
fn ($query) => $query->where('name', 'LIKE', '%'.str_replace(' ', '%', $this->name).'%')
)
)
->when(
$this->requestor !== '',
fn ($query) => $query
->whereRelation('user', 'username', '=', $this->requestor)
->when(
$user === null,
fn ($query) => $query->where('anon', '=', false),
fn ($query) => $query
->when(
!$user->group->is_modo,
fn ($query) => $query
->where(
fn ($query) => $query
->where('anon', '=', false)
->orWhere('user_id', '=', $user->id)
)
)
)
)
->when($this->categoryIds !== [], fn ($query) => $query->whereIntegerInRaw('category_id', $this->categoryIds))
->when($this->typeIds !== [], fn ($query) => $query->whereIntegerInRaw('type_id', $this->typeIds))
->when($this->resolutionIds !== [], fn ($query) => $query->whereIntegerInRaw('resolution_id', $this->resolutionIds))
->when($this->tmdbId !== null, fn ($query) => $query->where(fn ($query) => $query->where('tmdb_movie_id', '=', $this->tmdbId)->orWhere('tmdb_tv_id', '=', $this->tmdbId)))
->when($this->imdbId !== '', fn ($query) => $query->where('imdb', '=', (preg_match('/tt0*(\d{7,})/', $this->imdbId, $matches) ? $matches[1] : $this->imdbId)))
->when($this->tvdbId !== null, fn ($query) => $query->where('tvdb', '=', $this->tvdbId))
->when($this->malId !== null, fn ($query) => $query->where('mal', '=', $this->malId))
->when(
$this->genreIds !== [],
fn ($query) => $query
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereIn('tmdb_movie_id', DB::table('tmdb_genre_tmdb_movie')->select('tmdb_movie_id')->whereIn('tmdb_genre_id', $this->genreIds))
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereIn('tmdb_tv_id', DB::table('tmdb_genre_tmdb_tv')->select('tmdb_tv_id')->whereIn('tmdb_genre_id', $this->genreIds))
)
)
)
->when(
$this->primaryLanguageNames !== [],
fn ($query) => $query
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereHas('movie', fn ($query) => $query->whereIn('original_language', $this->primaryLanguageNames))
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereHas('tv', fn ($query) => $query->whereIn('original_language', $this->primaryLanguageNames))
)
)
)
->when($this->unfilled || $this->claimed || $this->pending || $this->filled, function ($query): void {
$query->where(function ($query): void {
return TorrentRequest::with(['user:id,username,group_id', 'user.group', 'category', 'type', 'resolution'])
->withCount(['comments', 'bounties'])
->withExists('claim')
->when(
$this->name !== '',
fn ($query) => $query
->when(
$isRegex($this->name),
fn ($query) => $query->where('name', 'REGEXP', substr($this->name, 1, -1)),
fn ($query) => $query->where('name', 'LIKE', '%'.str_replace(' ', '%', $this->name).'%')
)
)
->when(
$this->requestor !== '',
fn ($query) => $query
->whereRelation('user', 'username', '=', $this->requestor)
->when(
$user === null,
fn ($query) => $query->where('anon', '=', false),
fn ($query) => $query
->when(
!$user->group->is_modo,
fn ($query) => $query
->where(
fn ($query) => $query
->where('anon', '=', false)
->orWhere('user_id', '=', $user->id)
)
)
)
)
->when($this->categoryIds !== [], fn ($query) => $query->whereIntegerInRaw('category_id', $this->categoryIds))
->when($this->typeIds !== [], fn ($query) => $query->whereIntegerInRaw('type_id', $this->typeIds))
->when($this->resolutionIds !== [], fn ($query) => $query->whereIntegerInRaw('resolution_id', $this->resolutionIds))
->when($this->tmdbId !== null, fn ($query) => $query->where(fn ($query) => $query->where('tmdb_movie_id', '=', $this->tmdbId)->orWhere('tmdb_tv_id', '=', $this->tmdbId)))
->when($this->imdbId !== '', fn ($query) => $query->where('imdb', '=', (preg_match('/tt0*(\d{7,})/', $this->imdbId, $matches) ? $matches[1] : $this->imdbId)))
->when($this->tvdbId !== null, fn ($query) => $query->where('tvdb', '=', $this->tvdbId))
->when($this->malId !== null, fn ($query) => $query->where('mal', '=', $this->malId))
->when(
$this->genreIds !== [],
fn ($query) => $query
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereIn('tmdb_movie_id', DB::table('tmdb_genre_tmdb_movie')->select('tmdb_movie_id')->whereIn('tmdb_genre_id', $this->genreIds))
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereIn('tmdb_tv_id', DB::table('tmdb_genre_tmdb_tv')->select('tmdb_tv_id')->whereIn('tmdb_genre_id', $this->genreIds))
)
)
)
->when(
$this->primaryLanguageNames !== [],
fn ($query) => $query
->where(
fn ($query) => $query
->where(
fn ($query) => $query
->whereRelation('category', 'movie_meta', '=', true)
->whereHas('movie', fn ($query) => $query->whereIn('original_language', $this->primaryLanguageNames))
)
->orWhere(
fn ($query) => $query
->whereRelation('category', 'tv_meta', '=', true)
->whereHas('tv', fn ($query) => $query->whereIn('original_language', $this->primaryLanguageNames))
)
)
)
->when($this->unfilled || $this->claimed || $this->pending || $this->filled, function ($query): void {
$query->where(function ($query): void {
if ($this->unfilled) {
$query->whereNull('torrent_id')->whereDoesntHave('claim');
}
})
->orWhere(function ($query): void {
if ($this->claimed) {
$query->whereHas('claim')->whereNull('torrent_id')->whereNull('approved_when');
$query->where(function ($query): void {
if ($this->unfilled) {
$query->whereNull('torrent_id')->whereDoesntHave('claim');
}
})
->orWhere(function ($query): void {
if ($this->pending) {
$query->whereNotNull('torrent_id')->whereNull('approved_when');
}
})
->orWhere(function ($query): void {
if ($this->filled) {
$query->whereNotNull('torrent_id')->whereNotNull('approved_when');
}
});
});
})
->when($this->myRequests, function ($query) use ($user): void {
$query->where('user_id', '=', $user->id);
})
->when($this->myClaims, function ($query) use ($user): void {
$query->whereRelation('claim', 'user_id', '=', $user->id)->whereNull('torrent_id')->whereNull('approved_by');
})
->when($this->myVoted, function ($query) use ($user): void {
$query->whereRelation('bounties', 'user_id', '=', $user->id);
})
->when($this->myFilled, function ($query) use ($user): void {
$query->where('filled_by', '=', $user->id);
})
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
->orWhere(function ($query): void {
if ($this->claimed) {
$query->whereHas('claim')->whereNull('torrent_id')->whereNull('approved_when');
}
})
->orWhere(function ($query): void {
if ($this->pending) {
$query->whereNotNull('torrent_id')->whereNull('approved_when');
}
})
->orWhere(function ($query): void {
if ($this->filled) {
$query->whereNotNull('torrent_id')->whereNotNull('approved_when');
}
});
});
})
->when($this->myRequests, function ($query) use ($user): void {
$query->where('user_id', '=', $user->id);
})
->when($this->myClaims, function ($query) use ($user): void {
$query->whereRelation('claim', 'user_id', '=', $user->id)->whereNull('torrent_id')->whereNull('approved_by');
})
->when($this->myVoted, function ($query) use ($user): void {
$query->whereRelation('bounties', 'user_id', '=', $user->id);
})
->when($this->myFilled, function ($query) use ($user): void {
$query->where('filled_by', '=', $user->id);
})
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
}
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Livewire;
use App\Models\TorrentReseed;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class TorrentReseedSearch extends Component
{
use CastLivewireProperties;
use LivewireSort;
use WithPagination;
#TODO: Update URL attributes once Livewire 3 fixes upstream bug. See: https://github.com/livewire/livewire/discussions/7746
#[Url(history: true)]
public int $perPage = 25;
#[Url(history: true)]
public string $torrentName = '';
#[Url(history: true)]
public bool $myRequests = false;
#[Url(history: true)]
public string $sortField = 'created_at';
#[Url(history: true)]
public string $sortDirection = 'desc';
final public function updatingTorrentName(): void
{
$this->resetPage();
}
final public function updatingMyRequests(): void
{
$this->resetPage();
}
/**
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TorrentReseed>
*/
final protected \Illuminate\Pagination\LengthAwarePaginator $torrentReseeds {
get => TorrentReseed::query()
->with([
'user:id,username,group_id,deleted_at',
'torrent:id,name,seeders,leechers,deleted_at',
])
->when($this->torrentName !== '', fn ($query) => $query->whereRelation('torrent', 'name', 'LIKE', '%'.$this->torrentName.'%'))
->when($this->myRequests, fn ($query) => $query->where('user_id', '=', auth()->id()))
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application
{
return view('livewire.torrent-reseed-search', [
'torrentReseeds' => $this->torrentReseeds,
]);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\TorrentTrump;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -46,12 +45,10 @@ class TorrentTrumpSearch extends Component
public int $perPage = 25;
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, TorrentTrump>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, TorrentTrump>
*/
#[Computed]
final public function torrentTrumps(): \Illuminate\Pagination\LengthAwarePaginator
{
return TorrentTrump::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $torrentTrumps {
get => TorrentTrump::query()
->with([
'user:id,username,group_id,deleted_at',
'torrent:id,name,deleted_at',

View File

@@ -25,16 +25,11 @@ use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Throwable;
/**
* @property Collection<int, Torrent> $works
* @property array<string, string> $metaTypes
*/
class Trending extends Component
{
#TODO: Update URL attributes once Livewire 3 fixes upstream bug. See: https://github.com/livewire/livewire/discussions/7746
@@ -74,214 +69,214 @@ class Trending extends Component
}
/**
* @return Collection<int, Torrent>
* @var Collection<int, Torrent>
*/
#[Computed]
final public function works(): Collection
{
$this->validate();
final protected Collection $works {
get {
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'trending-'.$this->interval.'-'.($this->from ?? '').'-'.($this->until ?? '').'-'.$this->metaType,
0, //3600,
fn () => Torrent::query()
->with('movie', 'tv')
->addSelect([
$metaIdColumn,
DB::raw('MIN(category_id) as category_id'),
DB::raw('COUNT(*) as download_count'),
])
->join('history', 'history.torrent_id', '=', 'torrents.id')
->where($metaIdColumn, '!=', 0)
->when($this->interval === 'day', fn ($query) => $query->whereBetween('history.completed_at', [now()->subDay(), now()]))
->when($this->interval === 'week', fn ($query) => $query->whereBetween('history.completed_at', [now()->subWeek(), now()]))
->when($this->interval === 'month', fn ($query) => $query->whereBetween('history.completed_at', [now()->subMonth(), now()]))
->when($this->interval === 'year', fn ($query) => $query->whereBetween('history.completed_at', [now()->subYear(), now()]))
->when($this->interval === 'all', fn ($query) => $query->whereNotNull('history.completed_at'))
->when($this->interval === 'custom', fn ($query) => $query->whereBetween('history.completed_at', [$this->from ?: now(), $this->until ?: now()]))
->whereRelation('category', $this->metaType, '=', true)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy($metaIdColumn)
->orderByRaw('COUNT(*) DESC')
->limit(250)
->get($metaIdColumn)
);
}
/**
* @return Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
#[Computed]
final public function weekly(): Collection
{
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'weekly-charts:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with('movie', 'tv')
->fromSub(
History::query()
->withoutGlobalScopes()
->join('torrents', 'torrents.id', '=', 'history.torrent_id')
->join('categories', fn (JoinClause $join) => $join->on('torrents.category_id', '=', 'categories.id')->where($this->metaType, '=', true))
->select([
DB::raw('FROM_DAYS(TO_DAYS(history.created_at) - MOD(TO_DAYS(history.created_at) - 1, 7)) AS week_start'),
$metaIdColumn,
DB::raw('MIN(categories.id) as category_id'),
DB::raw('COUNT(*) AS download_count'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY FROM_DAYS(TO_DAYS(history.created_at) - MOD(TO_DAYS(history.created_at) - 1, 7)) ORDER BY COUNT(*) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy('week_start', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('week_start')
->orderBy('place')
->withCasts([
'week_start' => 'datetime',
])
->get()
->groupBy('week_start')
);
}
/**
* @return Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
#[Computed]
final public function monthly(): Collection
{
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'monthly-charts:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with($this->metaType === 'movie_meta' ? 'movie' : 'tv')
->fromSub(
History::query()
->withoutGlobalScopes()
->join('torrents', 'torrents.id', '=', 'history.torrent_id')
->join('categories', fn (JoinClause $join) => $join->on('torrents.category_id', '=', 'categories.id')->where($this->metaType, '=', true))
->select([
DB::raw('EXTRACT(YEAR_MONTH FROM history.created_at) AS the_year_month'),
$metaIdColumn,
DB::raw('MIN(categories.id) as category_id'),
DB::raw('COUNT(*) AS download_count'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR_MONTH FROM history.created_at) ORDER BY COUNT(*) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy('the_year_month', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('the_year_month')
->orderBy('place')
->get()
->groupBy('the_year_month')
);
}
/**
* @return Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
#[Computed]
final public function releaseYear(): Collection
{
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'trending-by-release-year:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with($this->metaType === 'movie_meta' ? 'movie' : 'tv')
->fromSub(
Torrent::query()
->withoutGlobalScopes()
->whereRelation('category', $this->metaType, '=', true)
->leftJoin('tmdb_movies', 'torrents.tmdb_movie_id', '=', 'tmdb_movies.id')
->leftJoin('tmdb_tv', 'torrents.tmdb_tv_id', '=', 'tmdb_tv.id')
->select([
$metaIdColumn,
DB::raw('MIN(category_id) as category_id'),
DB::raw('SUM(times_completed) AS download_count'),
'the_year' => $this->metaType === 'movie_meta'
? TmdbMovie::query()
->selectRaw('EXTRACT(YEAR FROM tmdb_movies.release_date)')
->whereColumn('tmdb_movies.id', '=', 'torrents.tmdb_movie_id')
: TmdbTv::query()
->selectRaw('EXTRACT(YEAR FROM tmdb_tv.first_air_date)')
->whereColumn('tmdb_tv.id', '=', 'torrents.tmdb_tv_id'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY COALESCE(EXTRACT(YEAR FROM MAX(tmdb_movies.release_date)), EXTRACT(YEAR FROM MAX(tmdb_tv.first_air_date))) ORDER BY SUM(times_completed) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 2 * 1024 * 1024 * 1024)
->when($this->metaType === 'tv_meta', fn ($query) => $query->where('episode_number', '=', 0))
->havingNotNull('the_year')
->where(fn ($query) => $query->whereNotNull('tmdb_movies.id')->orWhereNotNull('tmdb_tv.id'))
->groupBy('the_year', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('the_year')
->orderBy('place')
->get()
->groupBy('the_year')
);
}
/**
* @return array<string, string>
*/
#[Computed]
final public function metaTypes(): array
{
$metaTypes = [];
if (Category::where('movie_meta', '=', true)->exists()) {
$metaTypes[(string) __('mediahub.movie')] = 'movie_meta';
return cache()->remember(
'trending-'.$this->interval.'-'.($this->from ?? '').'-'.($this->until ?? '').'-'.$this->metaType,
0, //3600,
fn () => Torrent::query()
->with('movie', 'tv')
->addSelect([
$metaIdColumn,
DB::raw('MIN(category_id) as category_id'),
DB::raw('COUNT(*) as download_count'),
])
->join('history', 'history.torrent_id', '=', 'torrents.id')
->where($metaIdColumn, '!=', 0)
->when($this->interval === 'day', fn ($query) => $query->whereBetween('history.completed_at', [now()->subDay(), now()]))
->when($this->interval === 'week', fn ($query) => $query->whereBetween('history.completed_at', [now()->subWeek(), now()]))
->when($this->interval === 'month', fn ($query) => $query->whereBetween('history.completed_at', [now()->subMonth(), now()]))
->when($this->interval === 'year', fn ($query) => $query->whereBetween('history.completed_at', [now()->subYear(), now()]))
->when($this->interval === 'all', fn ($query) => $query->whereNotNull('history.completed_at'))
->when($this->interval === 'custom', fn ($query) => $query->whereBetween('history.completed_at', [$this->from ?: now(), $this->until ?: now()]))
->whereRelation('category', $this->metaType, '=', true)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy($metaIdColumn)
->orderByRaw('COUNT(*) DESC')
->limit(250)
->get($metaIdColumn)
);
}
}
if (Category::where('tv_meta', '=', true)->exists()) {
$metaTypes[(string) __('mediahub.show')] = 'tv_meta';
/**
* @var Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
final protected Collection $weekly {
get {
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'weekly-charts:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with('movie', 'tv')
->fromSub(
History::query()
->withoutGlobalScopes()
->join('torrents', 'torrents.id', '=', 'history.torrent_id')
->join('categories', fn (JoinClause $join) => $join->on('torrents.category_id', '=', 'categories.id')->where($this->metaType, '=', true))
->select([
DB::raw('FROM_DAYS(TO_DAYS(history.created_at) - MOD(TO_DAYS(history.created_at) - 1, 7)) AS week_start'),
$metaIdColumn,
DB::raw('MIN(categories.id) as category_id'),
DB::raw('COUNT(*) AS download_count'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY FROM_DAYS(TO_DAYS(history.created_at) - MOD(TO_DAYS(history.created_at) - 1, 7)) ORDER BY COUNT(*) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy('week_start', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('week_start')
->orderBy('place')
->withCasts([
'week_start' => 'datetime',
])
->get()
->groupBy('week_start')
);
}
}
return $metaTypes;
/**
* @var Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
final protected Collection $monthly {
get {
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'monthly-charts:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with($this->metaType === 'movie_meta' ? 'movie' : 'tv')
->fromSub(
History::query()
->withoutGlobalScopes()
->join('torrents', 'torrents.id', '=', 'history.torrent_id')
->join('categories', fn (JoinClause $join) => $join->on('torrents.category_id', '=', 'categories.id')->where($this->metaType, '=', true))
->select([
DB::raw('EXTRACT(YEAR_MONTH FROM history.created_at) AS the_year_month'),
$metaIdColumn,
DB::raw('MIN(categories.id) as category_id'),
DB::raw('COUNT(*) AS download_count'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY EXTRACT(YEAR_MONTH FROM history.created_at) ORDER BY COUNT(*) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 1024 * 1024 * 1024)
->groupBy('the_year_month', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('the_year_month')
->orderBy('place')
->get()
->groupBy('the_year_month')
);
}
}
/**
* @var Collection<int|string, Collection<int, Torrent>>
* @phpstan-ignore generics.notSubtype (I can't figure out the correct return type to silence this error)
*/
final protected Collection $releaseYear {
get {
$this->validate();
$metaIdColumn = match ($this->metaType) {
'tv_meta' => 'tmdb_tv_id',
default => 'tmdb_movie_id',
};
return cache()->remember(
'trending-by-release-year:'.$this->metaType,
24 * 3600,
fn () => Torrent::query()
->withoutGlobalScopes()
->with($this->metaType === 'movie_meta' ? 'movie' : 'tv')
->fromSub(
Torrent::query()
->withoutGlobalScopes()
->whereRelation('category', $this->metaType, '=', true)
->leftJoin('tmdb_movies', 'torrents.tmdb_movie_id', '=', 'tmdb_movies.id')
->leftJoin('tmdb_tv', 'torrents.tmdb_tv_id', '=', 'tmdb_tv.id')
->select([
$metaIdColumn,
DB::raw('MIN(category_id) as category_id'),
DB::raw('SUM(times_completed) AS download_count'),
'the_year' => $this->metaType === 'movie_meta'
? TmdbMovie::query()
->selectRaw('EXTRACT(YEAR FROM tmdb_movies.release_date)')
->whereColumn('tmdb_movies.id', '=', 'torrents.tmdb_movie_id')
: TmdbTv::query()
->selectRaw('EXTRACT(YEAR FROM tmdb_tv.first_air_date)')
->whereColumn('tmdb_tv.id', '=', 'torrents.tmdb_tv_id'),
DB::raw('ROW_NUMBER() OVER (PARTITION BY COALESCE(EXTRACT(YEAR FROM MAX(tmdb_movies.release_date)), EXTRACT(YEAR FROM MAX(tmdb_tv.first_air_date))) ORDER BY SUM(times_completed) DESC) AS place'),
])
->where($metaIdColumn, '!=', 0)
// Small torrents screw the stats since users download them only to farm bon.
->where('torrents.size', '>', 2 * 1024 * 1024 * 1024)
->when($this->metaType === 'tv_meta', fn ($query) => $query->where('episode_number', '=', 0))
->havingNotNull('the_year')
->where(fn ($query) => $query->whereNotNull('tmdb_movies.id')->orWhereNotNull('tmdb_tv.id'))
->groupBy('the_year', $metaIdColumn),
'ranked_groups',
)
->where('place', '<=', 10)
->orderByDesc('the_year')
->orderBy('place')
->get()
->groupBy('the_year')
);
}
}
/**
* @var array<string, string>
*/
final protected array $metaTypes {
get {
$metaTypes = [];
if (Category::where('movie_meta', '=', true)->exists()) {
$metaTypes[(string) __('mediahub.movie')] = 'movie_meta';
}
if (Category::where('tv_meta', '=', true)->exists()) {
$metaTypes[(string) __('mediahub.show')] = 'tv_meta';
}
return $metaTypes;
}
}
final public function placeholder(): string

View File

@@ -21,7 +21,6 @@ use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
use Laravel\Fortify\Actions\GenerateNewRecoveryCodes;
use Laravel\Fortify\Features;
use Livewire\Attributes\Computed;
use Livewire\Component;
class TwoFactorAuthForm extends Component
@@ -126,19 +125,15 @@ class TwoFactorAuthForm extends Component
/**
* Get the current user of the application.
*/
#[Computed]
final public function user(): ?\Illuminate\Contracts\Auth\Authenticatable
{
return auth()->user();
final protected ?\Illuminate\Contracts\Auth\Authenticatable $user {
get => auth()->user();
}
/**
* Determine if two-factor authentication is enabled.
*/
#[Computed]
final public function enabled(): bool
{
return !empty($this->user->two_factor_secret);
final protected bool $enabled {
get => !empty($this->user->two_factor_secret);
}
/**

View File

@@ -21,7 +21,6 @@ use App\Models\UnregisteredInfoHash;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -58,12 +57,10 @@ class UnregisteredInfoHashSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, UnregisteredInfoHash>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, UnregisteredInfoHash>
*/
#[Computed]
final public function unregisteredInfoHashes(): \Illuminate\Pagination\LengthAwarePaginator
{
return UnregisteredInfoHash::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $unregisteredInfoHashes {
get => UnregisteredInfoHash::query()
->with('user')
->when($this->username !== '', fn ($query) => $query->whereRelation('user', 'username', 'LIKE', '%'.$this->username.'%'))
->when(

View File

@@ -19,14 +19,10 @@ namespace App\Http\Livewire;
use App\Models\Peer;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Peer> $actives
*/
class UserActive extends Component
{
use LivewireSort;
@@ -80,12 +76,10 @@ class UserActive extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Peer>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Peer>
*/
#[Computed]
final public function actives(): \Illuminate\Pagination\LengthAwarePaginator
{
return Peer::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $actives {
get => Peer::query()
->join('torrents', 'peers.torrent_id', '=', 'torrents.id')
->select(
'peers.port',

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Bookmark;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -45,12 +44,10 @@ class UserBookmarks extends Component
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Bookmark>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Bookmark>
*/
#[Computed]
final public function bookmarks(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return Bookmark::query()
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $bookmarks {
get => Bookmark::query()
->select([
'bookmarks.torrent_id',
'bookmarks.created_at as bookmark_created_at',

View File

@@ -21,14 +21,10 @@ use App\Models\User;
use App\Models\Peer;
use App\Traits\LivewireSort;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, BonEarning> $bonEarnings
*/
class UserEarnings extends Component
{
use LivewireSort;
@@ -64,75 +60,89 @@ class UserEarnings extends Component
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, BonEarning>
* @var \Illuminate\Database\Eloquent\Collection<int, BonEarning>
*/
#[Computed]
final public function bonEarnings(): \Illuminate\Support\Collection
{
$outerQuery = DB::query()->select(DB::raw(1));
$innerQuery = Peer::query()
->select(DB::raw(1))
->join('history', fn ($join) => $join->on('history.torrent_id', '=', 'peers.torrent_id')->on('history.user_id', '=', 'peers.user_id'))
->join('torrents', 'peers.torrent_id', '=', 'torrents.id')
->where('peers.seeder', '=', true)
->where('peers.active', '=', true)
->where('peers.user_id', '=', $this->user->id)
->where('peers.created_at', '<', now()->subMinutes(30))
->where('torrents.name', 'LIKE', '%'.str_replace(' ', '%', $this->torrentName).'%')
->groupBy('peers.torrent_id');
final protected \Illuminate\Support\Collection $bonEarnings {
get {
$outerQuery = DB::query()->select(DB::raw(1));
$innerQuery = Peer::query()
->select(DB::raw(1))
->join('history', fn ($join) => $join->on('history.torrent_id', '=', 'peers.torrent_id')->on('history.user_id', '=', 'peers.user_id'))
->join('torrents', 'peers.torrent_id', '=', 'torrents.id')
->where('peers.seeder', '=', true)
->where('peers.active', '=', true)
->where('peers.user_id', '=', $this->user->id)
->where('peers.created_at', '<', now()->subMinutes(30))
->where('torrents.name', 'LIKE', '%'.str_replace(' ', '%', $this->torrentName).'%')
->groupBy('peers.torrent_id');
foreach (BonEarning::with('conditions')->orderBy('position')->get() as $bonEarning) {
// Raw bindings are fine since all database values are either enums or numeric
$conditionQuery = '1=1';
foreach (BonEarning::with('conditions')->orderBy('position')->get() as $bonEarning) {
// Raw bindings are fine since all database values are either enums or numeric
$conditionQuery = '1=1';
foreach ($bonEarning->conditions as $condition) {
$conditionQuery .= ' AND '.match ($condition->operand1) {
'1' => '1',
'age' => 'TIMESTAMPDIFF(SECOND, torrents.created_at, NOW())',
'size' => 'torrents.size',
'seeders' => 'torrents.seeders',
'leechers' => 'torrents.leechers',
'times_completed' => 'torrents.times_completed',
'internal' => 'torrents.internal',
'personal_release' => 'torrents.personal_release',
'type_id' => 'torrents.type_id',
'seedtime' => 'history.seedtime',
'connectable' => 'peers.connectable',
}.' '.$condition->operator.' '.$condition->operand2;
foreach ($bonEarning->conditions as $condition) {
$conditionQuery .= ' AND '.match ($condition->operand1) {
'1' => '1',
'age' => 'TIMESTAMPDIFF(SECOND, torrents.created_at, NOW())',
'size' => 'torrents.size',
'seeders' => 'torrents.seeders',
'leechers' => 'torrents.leechers',
'times_completed' => 'torrents.times_completed',
'internal' => 'torrents.internal',
'personal_release' => 'torrents.personal_release',
'type_id' => 'torrents.type_id',
'seedtime' => 'history.seedtime',
'connectable' => 'peers.connectable',
}.' '.$condition->operator.' '.$condition->operand2;
}
$innerQuery->selectRaw("MAX({$conditionQuery}) AS bon_earning_{$bonEarning->id}");
$outerQuery->selectRaw("SUM(bon_earning_{$bonEarning->id}) AS bon_earning_{$bonEarning->id}");
}
$innerQuery->selectRaw("MAX({$conditionQuery}) AS bon_earning_{$bonEarning->id}");
$outerQuery->selectRaw("SUM(bon_earning_{$bonEarning->id}) AS bon_earning_{$bonEarning->id}");
$torrentCounts = $outerQuery->fromSub($innerQuery, 'peers_per_torrent')->first();
return BonEarning::query()
->orderBy('position')
->get()
->map(function ($bonEarning) use ($torrentCounts) {
$bonEarning->setAttribute('torrents_count', $torrentCounts->{"bon_earning_{$bonEarning->id}"});
return $bonEarning;
});
}
$torrentCounts = $outerQuery->fromSub($innerQuery, 'peers_per_torrent')->first();
return BonEarning::query()
->orderBy('position')
->get()
->map(function ($bonEarning) use ($torrentCounts) {
$bonEarning->setAttribute('torrents_count', $torrentCounts->{"bon_earning_{$bonEarning->id}"});
return $bonEarning;
});
}
/**
* @return \Illuminate\Database\Query\Builder
* @var \Illuminate\Database\Query\Builder
*/
#[Computed]
final public function query(): \Illuminate\Database\Query\Builder
{
$bonEarnings = BonEarning::with('conditions')->orderBy('position')->get();
final protected \Illuminate\Database\Query\Builder $query {
get {
$bonEarnings = BonEarning::with('conditions')->orderBy('position')->get();
$earningsQuery = str_repeat('(', $bonEarnings->count()).'0';
$earningsQuery = str_repeat('(', $bonEarnings->count()).'0';
foreach ($bonEarnings as $bonEarning) {
// Raw bindings are fine since all database values are either enums or numeric
$conditionQuery = '1=1';
foreach ($bonEarnings as $bonEarning) {
// Raw bindings are fine since all database values are either enums or numeric
$conditionQuery = '1=1';
foreach ($bonEarning->conditions as $condition) {
$conditionQuery .= ' AND '.match ($condition->operand1) {
foreach ($bonEarning->conditions as $condition) {
$conditionQuery .= ' AND '.match ($condition->operand1) {
'1' => '1',
'age' => 'TIMESTAMPDIFF(SECOND, torrents.created_at, NOW())',
'size' => 'torrents.size',
'seeders' => 'torrents.seeders',
'leechers' => 'torrents.leechers',
'times_completed' => 'torrents.times_completed',
'internal' => 'torrents.internal',
'personal_release' => 'torrents.personal_release',
'type_id' => 'torrents.type_id',
'seedtime' => 'history.seedtime',
'connectable' => 'MAX(peers.connectable)',
}.' '.$condition->operator.' '.$condition->operand2;
}
$variable = match ($bonEarning->variable) {
'1' => '1',
'age' => 'TIMESTAMPDIFF(SECOND, torrents.created_at, NOW())',
'size' => 'torrents.size',
@@ -141,68 +151,52 @@ class UserEarnings extends Component
'times_completed' => 'torrents.times_completed',
'internal' => 'torrents.internal',
'personal_release' => 'torrents.personal_release',
'type_id' => 'torrents.type_id',
'seedtime' => 'history.seedtime',
'connectable' => 'MAX(peers.connectable)',
}.' '.$condition->operator.' '.$condition->operand2;
};
$earningsQuery .= match ($bonEarning->operation) {
'append' => " + CASE WHEN ({$conditionQuery}) THEN {$variable} * {$bonEarning->multiplier} ELSE 0 END)",
'multiply' => " * CASE WHEN ({$conditionQuery}) THEN {$variable} * {$bonEarning->multiplier} ELSE 1 END)",
};
}
$variable = match ($bonEarning->variable) {
'1' => '1',
'age' => 'TIMESTAMPDIFF(SECOND, torrents.created_at, NOW())',
'size' => 'torrents.size',
'seeders' => 'torrents.seeders',
'leechers' => 'torrents.leechers',
'times_completed' => 'torrents.times_completed',
'internal' => 'torrents.internal',
'personal_release' => 'torrents.personal_release',
'seedtime' => 'history.seedtime',
'connectable' => 'MAX(peers.connectable)',
};
$query = DB::table('peers')
->select([
DB::raw('1 as "1"'),
'torrents.name',
DB::raw('TIMESTAMPDIFF(SECOND, torrents.created_at, NOW()) as age'),
'torrents.type_id',
'torrents.size',
'torrents.seeders',
'torrents.leechers',
'torrents.times_completed',
'history.seedtime',
'torrents.personal_release',
'torrents.internal',
DB::raw('MAX(peers.connectable) as connectable'),
'peers.torrent_id',
'peers.user_id',
DB::raw("({$earningsQuery}) AS hourly_earnings"),
])
->join('history', fn ($join) => $join->on('history.torrent_id', '=', 'peers.torrent_id')->on('history.user_id', '=', 'peers.user_id'))
->join('torrents', 'peers.torrent_id', '=', 'torrents.id')
->where('peers.seeder', '=', true)
->where('peers.active', '=', true)
->where('peers.user_id', '=', $this->user->id)
->where('peers.created_at', '<', now()->subMinutes(30))
->where('torrents.name', 'LIKE', '%'.str_replace(' ', '%', $this->torrentName).'%')
->groupBy(['peers.torrent_id', 'peers.user_id']);
$earningsQuery .= match ($bonEarning->operation) {
'append' => " + CASE WHEN ({$conditionQuery}) THEN {$variable} * {$bonEarning->multiplier} ELSE 0 END)",
'multiply' => " * CASE WHEN ({$conditionQuery}) THEN {$variable} * {$bonEarning->multiplier} ELSE 1 END)",
};
return $query;
}
$query = DB::table('peers')
->select([
DB::raw('1 as "1"'),
'torrents.name',
DB::raw('TIMESTAMPDIFF(SECOND, torrents.created_at, NOW()) as age'),
'torrents.type_id',
'torrents.size',
'torrents.seeders',
'torrents.leechers',
'torrents.times_completed',
'history.seedtime',
'torrents.personal_release',
'torrents.internal',
DB::raw('MAX(peers.connectable) as connectable'),
'peers.torrent_id',
'peers.user_id',
DB::raw("({$earningsQuery}) AS hourly_earnings"),
])
->join('history', fn ($join) => $join->on('history.torrent_id', '=', 'peers.torrent_id')->on('history.user_id', '=', 'peers.user_id'))
->join('torrents', 'peers.torrent_id', '=', 'torrents.id')
->where('peers.seeder', '=', true)
->where('peers.active', '=', true)
->where('peers.user_id', '=', $this->user->id)
->where('peers.created_at', '<', now()->subMinutes(30))
->where('torrents.name', 'LIKE', '%'.str_replace(' ', '%', $this->torrentName).'%')
->groupBy(['peers.torrent_id', 'peers.user_id']);
return $query;
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Peer>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Peer>
*/
#[Computed]
final public function torrents(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return $this
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $torrents {
get => $this
->query
->orderBy($this->sortField, $this->sortDirection)
->paginate(25);
@@ -211,10 +205,8 @@ class UserEarnings extends Component
/**
* @return float|numeric-string
*/
#[Computed]
final public function total(): float|string
{
return DB::query()->fromSub($this->query, 'earnings_per_torrent')->sum('hourly_earnings');
final protected float|string $total {
get => DB::query()->fromSub($this->query, 'earnings_per_torrent')->sum('hourly_earnings');
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Note;
use App\Models\User;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -70,12 +69,10 @@ class UserNotes extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Note>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Note>
*/
#[Computed]
final public function notes(): \Illuminate\Pagination\LengthAwarePaginator
{
return Note::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $notes {
get => Note::query()
->with('staffuser', 'staffuser.group')
->where('user_id', '=', $this->user->id)
->paginate($this->perPage);

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\Resurrection;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -59,12 +58,10 @@ class UserResurrections extends Component
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Resurrection>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, Resurrection>
*/
#[Computed]
final public function resurrections(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return Resurrection::query()
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $resurrections {
get => Resurrection::query()
->select([
'resurrections.id',
'resurrections.created_at',

View File

@@ -20,7 +20,6 @@ use App\Models\Group;
use App\Models\User;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -75,12 +74,10 @@ class UserSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, User>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, User>
*/
#[Computed]
final public function users(): \Illuminate\Pagination\LengthAwarePaginator
{
return User::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $users {
get => User::query()
->with('group')
->when($this->username !== '', fn ($query) => $query->where('username', 'LIKE', '%'.$this->username.'%'))
->when(
@@ -106,12 +103,10 @@ class UserSearch extends Component
}
/**
* @return \Illuminate\Support\Collection<int, Group>
* @var \Illuminate\Support\Collection<int, Group>
*/
#[Computed]
final public function groups()
{
return Group::orderBy('position')->get();
final protected $groups {
get => Group::orderBy('position')->get();
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application

View File

@@ -19,7 +19,6 @@ namespace App\Http\Livewire;
use App\Models\History;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -89,12 +88,10 @@ class UserTorrents extends Component
}
/**
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, History>
* @var \Illuminate\Contracts\Pagination\LengthAwarePaginator<int, History>
*/
#[Computed]
final public function history(): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
return History::query()
final protected \Illuminate\Contracts\Pagination\LengthAwarePaginator $history {
get => History::query()
->join(
'torrents',
fn ($join) => $join

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Livewire;
use App\Models\UnregisteredInfoHash;
use App\Models\User;
use App\Traits\CastLivewireProperties;
use App\Traits\LivewireSort;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
class UserUnregisteredInfoHashSearch extends Component
{
use CastLivewireProperties;
use LivewireSort;
use WithPagination;
public ?User $user = null;
#TODO: Update URL attributes once Livewire 3 fixes upstream bug. See: https://github.com/livewire/livewire/discussions/7746
#[Url(history: true)]
public int $perPage = 25;
#[Url(history: true)]
public string $sortField = 'deleted_at';
#[Url(history: true)]
public string $sortDirection = 'desc';
final public function mount(int $userId): void
{
$this->user = User::find($userId);
}
final public function updatingSearch(): void
{
$this->resetPage();
}
/**
* @var \Illuminate\Pagination\LengthAwarePaginator<int, UnregisteredInfoHash>
*/
final protected \Illuminate\Pagination\LengthAwarePaginator $unregisteredInfoHashes {
get => UnregisteredInfoHash::query()
->select([
'unregistered_info_hashes.info_hash',
'unregistered_info_hashes.updated_at',
'torrents.id',
'torrents.name',
'torrents.size',
'torrents.deleted_at',
])
->withCasts([
'deleted_at' => 'datetime',
])
->join('torrents', 'unregistered_info_hashes.info_hash', '=', 'torrents.info_hash')
->whereBelongsTo($this->user)
->whereNotNull('torrents.deleted_at')
->where('unregistered_info_hashes.updated_at', '>', now()->subHours(2))
->orderBy($this->sortField, $this->sortDirection)
->paginate($this->perPage);
}
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application
{
return view('livewire.user-unregistered-info-hash-search', [
'unregisteredInfoHashes' => $this->unregisteredInfoHashes,
]);
}
}

View File

@@ -20,7 +20,6 @@ use App\Models\Scopes\ApprovedScope;
use App\Models\Torrent;
use App\Models\User;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -69,12 +68,10 @@ class UserUploads extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Torrent>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Torrent>
*/
#[Computed]
final public function uploads(): \Illuminate\Pagination\LengthAwarePaginator
{
return Torrent::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $uploads {
get => Torrent::query()
->withCount('thanks', 'comments')
->withSum('tips', 'bon')
->withoutGlobalScope(ApprovedScope::class)

View File

@@ -25,18 +25,11 @@ use App\Notifications\WarningsDeleted;
use App\Notifications\WarningTorrentDeleted;
use App\Traits\LivewireSort;
use Illuminate\Support\Carbon;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\WithPagination;
/**
* @property \Illuminate\Pagination\LengthAwarePaginator<int, Warning> $warnings
* @property int $automatedWarningsCount
* @property int $manualWarningsCount
* @property int $deletedWarningsCount
*/
class UserWarnings extends Component
{
use LivewireSort;
@@ -64,12 +57,10 @@ class UserWarnings extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Warning>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Warning>
*/
#[Computed]
final public function warnings(): \Illuminate\Pagination\LengthAwarePaginator
{
return $this->user
final protected \Illuminate\Pagination\LengthAwarePaginator $warnings {
get => $this->user
->userwarning()
->when(
auth()->user()->group->is_modo,
@@ -87,22 +78,16 @@ class UserWarnings extends Component
->paginate($this->perPage);
}
#[Computed]
final public function automatedWarningsCount(): int
{
return $this->user->userwarning()->whereNotNull('torrent')->count();
final protected int $automatedWarningsCount {
get => $this->user->userwarning()->whereNotNull('torrent')->count();
}
#[Computed]
final public function manualWarningsCount(): int
{
return $this->user->userwarning()->whereNull('torrent')->count();
final protected int $manualWarningsCount {
get => $this->user->userwarning()->whereNull('torrent')->count();
}
#[Computed]
final public function deletedWarningsCount(): int
{
return $this->user->userwarning()->onlyTrashed()->count();
final protected int $deletedWarningsCount {
get => $this->user->userwarning()->onlyTrashed()->count();
}
/**

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Warning;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -55,12 +54,10 @@ class WarningLogSearch extends Component
public string $sortDirection = 'desc';
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Warning>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Warning>
*/
#[Computed]
final public function warnings(): \Illuminate\Pagination\LengthAwarePaginator
{
return Warning::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $warnings {
get => Warning::query()
->with(['warneduser.group', 'staffuser.group', 'torrenttitle'])
->when($this->sender, fn ($query) => $query->whereRelation('staffuser', 'username', '=', $this->sender))
->when($this->receiver, fn ($query) => $query->whereRelation('warneduser', 'username', '=', $this->receiver))

View File

@@ -18,7 +18,6 @@ namespace App\Http\Livewire;
use App\Models\Watchlist;
use App\Traits\LivewireSort;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;
@@ -55,12 +54,10 @@ class WatchlistSearch extends Component
}
/**
* @return \Illuminate\Pagination\LengthAwarePaginator<int, Watchlist>
* @var \Illuminate\Pagination\LengthAwarePaginator<int, Watchlist>
*/
#[Computed]
final public function users(): \Illuminate\Pagination\LengthAwarePaginator
{
return Watchlist::query()
final protected \Illuminate\Pagination\LengthAwarePaginator $users {
get => Watchlist::query()
->with(['user.group', 'author.group'])
->when($this->search, fn ($query) => $query->where('message', 'LIKE', '%'.$this->search.'%'))
->orderBy($this->sortField, $this->sortDirection)

View File

@@ -39,6 +39,28 @@ class CheckIfBanned
'message' => __('auth.banned'),
]);
}
if ($request->is('rss/*')) {
$message = __('auth.banned');
$now = now()->toRssString();
$url = config('app.url');
return response(
<<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>{$message}</title>
<link>{$url}</link>
<description>{$message}</description>
<pubDate>{$now}</pubDate>
</channel>
</rss>
XML,
403
)->header('Content-Type', 'text/xml');
}
auth()->logout();
$request->session()->flush();

View File

@@ -88,6 +88,26 @@ class StoreTorrentRequestRequest extends FormRequest
$mustBeNull,
]),
],
'season_number' => [
Rule::when($category->tv_meta, [
'required',
'integer',
'min:0',
]),
Rule::when(!$category->tv_meta, [
$mustBeNull,
]),
],
'episode_number' => [
Rule::when($category->tv_meta, [
'required',
'integer',
'min:0',
]),
Rule::when(!$category->tv_meta, [
$mustBeNull,
]),
],
'tmdb_movie_id' => [
Rule::when($category->movie_meta, [
'required_with:movie_exists_on_tmdb',

View File

@@ -88,6 +88,26 @@ class UpdateTorrentRequestRequest extends FormRequest
$mustBeNull,
]),
],
'season_number' => [
Rule::when($category->tv_meta, [
'required',
'integer',
'min:0',
]),
Rule::when(!$category->tv_meta, [
$mustBeNull,
]),
],
'episode_number' => [
Rule::when($category->tv_meta, [
'required',
'integer',
'min:0',
]),
Rule::when(!$category->tv_meta, [
$mustBeNull,
]),
],
'tmdb_movie_id' => [
Rule::when($category->movie_meta, [
'required_with:movie_exists_on_tmdb',

View File

@@ -16,8 +16,8 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use JsonSerializable;
/**
* @mixin \App\Models\Bot
@@ -26,9 +26,41 @@ class BotResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* id: int,
* position: int,
* name: string,
* command: string,
* color: string|null,
* icon: string|null,
* emoji: string|null,
* help: string|null,
* active: bool,
* is_protected: bool,
* is_nerdbot: bool,
* is_systembot: bool,
* created_at: \Illuminate\Support\Carbon|null,
* updated_at: \Illuminate\Support\Carbon|null,
* }
*/
public function toArray($request): array|\Illuminate\Contracts\Support\Arrayable|JsonSerializable
public function toArray(Request $request): array
{
return parent::toArray($request);
return [
'id' => $this->id,
'position' => $this->position,
'name' => $this->name,
'command' => $this->command,
'color' => $this->color,
'icon' => $this->icon,
'emoji' => $this->emoji,
'help' => $this->help,
'active' => $this->active,
'is_protected' => $this->is_protected,
'is_nerdbot' => $this->is_nerdbot,
'is_systembot' => $this->is_systembot,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View File

@@ -18,6 +18,7 @@ namespace App\Http\Resources;
use App\Helpers\Bbcode;
use hdvinnie\LaravelJoyPixels\LaravelJoyPixels;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -29,8 +30,19 @@ class ChatMessageResource extends JsonResource
* Transform the resource into an array.
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*
* @return array{
* id: int,
* bot: BotResource,
* user: ChatUserResource,
* receiver: ChatUserResource,
* chatroom: ChatRoomResource,
* message: string,
* created_at: string,
* updated_at: string,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
$emojiOne = new LaravelJoyPixels();

View File

@@ -16,6 +16,7 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -25,8 +26,17 @@ class ChatRoomResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* id: int,
* name: string,
* users: \Illuminate\Http\Resources\Json\AnonymousResourceCollection,
* messages: \Illuminate\Http\Resources\Json\AnonymousResourceCollection,
* created_at: string,
* updated_at: string,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'id' => $this->id,

View File

@@ -16,6 +16,7 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -25,8 +26,24 @@ class ChatUserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* id: int,
* username: string,
* chat_status: mixed,
* chat_status_id: int,
* chatroom_id: int,
* group: mixed,
* echoes: mixed,
* group_id: int,
* title: string,
* image: string,
* is_lifetime: bool,
* is_donor: bool,
* icon: string,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'id' => $this->id,

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Request;
/**
* @mixin \App\Models\TorrentRequest
*/
class TorrentRequestResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'category_id' => $this->category_id,
'type_id' => $this->type_id,
'resolution_id' => $this->whenNotNull($this->resolution_id),
'user' => $this->anon ? 'anonymous' : $this->user->username,
'tmdb' => $this->tmdb_movie_id ?: $this->tmdb_tv_id,
'imdb' => $this->imdb,
'tvdb' => $this->tvdb,
'mal' => $this->mal,
'igdb' => $this->igdb,
'season_number' => $this->whenNotNull($this->season_number),
'episode_number' => $this->whenNotNull($this->episode_number),
'bounty' => $this->whenAggregated('bounties', 'seedbonus', 'sum'),
'status' => $this->filled_by !== null ? ($this->approved_by !== null ? 'filled' : 'pending') : ($this->claim ? 'claimed' : 'unfilled'),
'claimed' => $this->claim !== null,
'claimed_by' => $this->when($this->claim !== null, $this->claim?->anon ? 'anonymous' : ($this->claim?->user?->username ?? null)),
'filled_by' => $this->when($this->filled_by !== null, $this->filled_anon ? 'anonymous' : ($this->filler?->username ?? null)),
'created' => $this->created_at->toIso8601String(),
'updated_at' => $this->updated_at->toIso8601String(),
];
}
}

View File

@@ -17,17 +17,76 @@ declare(strict_types=1);
namespace App\Http\Resources;
use App\Enums\AuthGuard;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin \App\Models\Torrent
* @property \App\Models\TmdbMovie|\App\Models\TmdbTv|\App\Models\IgdbGame $meta
*/
class TorrentResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* type: 'torrent',
* id: string,
* attributes: array{
* meta: array{
* poster: string,
* genres: string,
* },
* name: string,
* release_year: string|null,
* category: string,
* type: string,
* resolution: string,
* distributor: string,
* region: string,
* media_info: string,
* bd_info: string,
* description: string,
* size: float,
* folder: string|null,
* num_file: int,
* files: \Illuminate\Support\Collection<
* int,
* array{
* index: int,
* name: string,
* size: int,
* },
* >,
* freeleech: string,
* double_upload: bool,
* refundable: bool,
* internal: int,
* featured: \Illuminate\Http\Resources\MissingValue|mixed,
* personal_release: bool,
* uploader: string,
* seeders: int,
* leechers: int,
* times_completed: int,
* tmdb_id: int|null,
* imdb_id: int,
* tvdb_id: int,
* mal_id: int,
* igdb_id: int,
* category_id: int|null,
* type_id: int,
* resolution_id: \Illuminate\Http\Resources\MissingValue|mixed,
* distributor_id: \Illuminate\Http\Resources\MissingValue|mixed,
* region_id: \Illuminate\Http\Resources\MissingValue|mixed,
* created_at: \Illuminate\Support\Carbon|null,
* download_link: string,
* magnet_link: \Illuminate\Http\Resources\MissingValue|mixed,
* details_link: string,
* }
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'type' => 'torrent',
@@ -47,7 +106,6 @@ class TorrentResource extends JsonResource
'media_info' => $this->mediainfo,
'bd_info' => $this->bdinfo,
'description' => $this->description,
'info_hash' => bin2hex($this->info_hash),
'size' => $this->size,
'folder' => $this->folder,
'num_file' => $this->num_file,
@@ -87,7 +145,7 @@ class TorrentResource extends JsonResource
/**
* Customize the outgoing response for the resource.
*/
public function withResponse($request, $response): void
public function withResponse(Request $request, JsonResponse $response): void
{
$response->setEncodingOptions(JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}

View File

@@ -16,14 +16,20 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TorrentsResource extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @return array{
* data: \Illuminate\Http\Resources\Json\AnonymousResourceCollection,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'data' => TorrentResource::collection($this->collection),
@@ -32,8 +38,14 @@ class TorrentsResource extends ResourceCollection
/**
* Get additional data that should be returned with the resource array.
*
* @return array{
* links: array{
* self: string,
* }
* }
*/
public function with($request): array
public function with(Request $request): array
{
return [
'links' => [
@@ -45,7 +57,7 @@ class TorrentsResource extends ResourceCollection
/**
* Customize the outgoing response for the resource.
*/
public function withResponse($request, $response): void
public function withResponse(Request $request, JsonResponse $response): void
{
$response->setEncodingOptions(JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
}

View File

@@ -16,6 +16,7 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -25,8 +26,18 @@ class UserAudibleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* id: int,
* user_id: int,
* user: ChatUserResource,
* target: ChatUserResource,
* room: \App\Models\Chatroom|null,
* bot: \App\Models\Bot|null,
* status: int,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'id' => $this->id,

View File

@@ -16,6 +16,7 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -25,8 +26,17 @@ class UserEchoResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* id: int,
* user_id: int,
* user: ChatUserResource,
* target: ChatUserResource,
* room: \App\Models\Chatroom,
* bot: \App\Models\Bot,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'id' => $this->id,

View File

@@ -16,6 +16,7 @@ declare(strict_types=1);
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
@@ -25,8 +26,21 @@ class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array{
* username: string,
* group: string,
* uploaded: string,
* downloaded: string,
* ratio: string,
* buffer: string,
* seeding: int,
* leeching: int,
* seedbonus: string,
* hit_and_runs: int,
* }
*/
public function toArray($request): array
public function toArray(Request $request): array
{
return [
'username' => $this->username,

View File

@@ -23,17 +23,17 @@ use Illuminate\Database\Eloquent\Model;
/**
* App\Models\Donation.
*
* @property int $id
* @property int $user_id
* @property int $gifted_user_id
* @property ModerationStatus $status
* @property int $package_id
* @property string $transaction
* @property bool $is_gifted
* @property \Illuminate\Support\Carbon $starts_at
* @property \Illuminate\Support\Carbon $ends_at
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $id
* @property int $user_id
* @property int $gifted_user_id
* @property ModerationStatus $status
* @property int $package_id
* @property string $transaction
* @property bool $is_gifted
* @property \Illuminate\Support\Carbon|null $starts_at
* @property \Illuminate\Support\Carbon|null $ends_at
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*/
class Donation extends Model
{

View File

@@ -46,6 +46,8 @@ use Illuminate\Database\Eloquent\Model;
* @property \Illuminate\Support\Carbon|null $approved_when
* @property int|null $type_id
* @property int|null $resolution_id
* @property int|null $season_number
* @property int|null $episode_number
*/
class TorrentRequest extends Model
{

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Models;
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\Model;
/**
* App\Models\TorrentReseed.
*
* @property int $id
* @property int $torrent_id
* @property int $user_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read Torrent $torrent
* @property-read User $user
*/
class TorrentReseed extends Model
{
use Auditable;
/**
* The attributes that aren't mass assignable.
*
* @var string[]
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Belongs To A Torrent.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<Torrent, $this>
*/
public function torrent(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Torrent::class);
}
/**
* Belongs To A User.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<User, $this>
*/
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class);
}
}

Some files were not shown because too many files have changed in this diff Show More