mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-05-08 04:00:14 -05:00
Merge branch '8.x.x' into add-torrent-immune-button-to-ui
This commit is contained in:
@@ -117,10 +117,11 @@ class IRCAnnounceBot
|
||||
public function to(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
$channelKey = config('irc-bot.channel_key', '');
|
||||
|
||||
if (config('irc-bot.joinchannel')) {
|
||||
if ($this->isValidChannelName($recipient)) {
|
||||
$this->join($recipient);
|
||||
$this->join($recipient, $channelKey);
|
||||
$this->isInChannel = true;
|
||||
} else {
|
||||
Log::error('Tried to channel with invalid name.', [
|
||||
@@ -192,7 +193,7 @@ class IRCAnnounceBot
|
||||
/**
|
||||
* @see https://www.rfc-editor.org/rfc/rfc1459#section-4.2.1
|
||||
*/
|
||||
private function join(string $channel, string $key = ''): void
|
||||
private function join(string $channel, string $channelKey = ''): void
|
||||
{
|
||||
if (!$this->isValidChannelName($channel)) {
|
||||
Log::error('Tried to join a channel with invalid name.', ['name' => $channel]);
|
||||
@@ -200,7 +201,7 @@ class IRCAnnounceBot
|
||||
return;
|
||||
}
|
||||
|
||||
$this->send("JOIN {$channel} {$key}");
|
||||
$this->send("JOIN {$channel} {$channelKey}");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -304,7 +304,7 @@ class ChatController extends Controller
|
||||
|
||||
public function deleteMessage(Request $request, int $id): \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
|
||||
{
|
||||
$message = Message::find($id);
|
||||
$message = Message::findOrFail($id);
|
||||
|
||||
abort_unless($request->user()->id === $message->user_id || $request->user()->group->is_modo, 403);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class DislikeController extends Controller
|
||||
$user = auth()->user();
|
||||
$post = Post::findOrFail($postId);
|
||||
|
||||
if ($user->id === $post->id) {
|
||||
if ($user->id === $post->user_id) {
|
||||
abort(400, 'You cannot dislike your own post!');
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class LikeController extends Controller
|
||||
$user = auth()->user();
|
||||
$post = Post::findOrFail($postId);
|
||||
|
||||
if ($user->id === $post->id) {
|
||||
if ($user->id === $post->user_id) {
|
||||
abort(400, 'You cannot like your own post!');
|
||||
}
|
||||
|
||||
|
||||
@@ -519,6 +519,7 @@ class TorrentController extends BaseController
|
||||
$queryString = http_build_query($queryParams);
|
||||
$cacheKey = $url.'?'.$queryString;
|
||||
|
||||
/** @phpstan-ignore argument.templateType (phpstan is unable to resolve type because it's returning a phpstan-ignored line) */
|
||||
$torrents = cache()->remember($cacheKey, 300, function () use ($request, $isSqlAllowed) {
|
||||
$eagerLoads = fn (Builder $query) => $query
|
||||
->with(['user:id,username', 'category', 'type', 'resolution', 'distributor', 'region', 'files'])
|
||||
@@ -576,8 +577,11 @@ class TorrentController extends BaseController
|
||||
->latest('sticky')
|
||||
->orderBy($request->input('sortField') ?? $this->sortField, $request->input('sortDirection') ?? $this->sortDirection)
|
||||
->cursorPaginate(min($request->input('perPage') ?? $this->perPage, 100));
|
||||
|
||||
// See app/Traits/TorrentMeta.php
|
||||
$this->scopeMeta($torrents);
|
||||
} else {
|
||||
$torrents = Torrent::search(
|
||||
$paginator = Torrent::search(
|
||||
$request->filled('name') ? $request->string('name')->toString() : '',
|
||||
function (Indexes $meilisearch, string $query, array $options) use ($request, $filters) {
|
||||
$options['sort'] = [
|
||||
@@ -586,21 +590,105 @@ class TorrentController extends BaseController
|
||||
$options['filter'] = $filters->toMeilisearchFilter();
|
||||
$options['matchingStrategy'] = 'all';
|
||||
|
||||
return $meilisearch->search($query, $options);
|
||||
$results = $meilisearch->search($query, $options);
|
||||
|
||||
return $results;
|
||||
}
|
||||
)
|
||||
->query($eagerLoads)
|
||||
->paginate(min($request->input('perPage') ?? $this->perPage, 100));
|
||||
}
|
||||
->simplePaginateRaw(min($request->input('perPage') ?? $this->perPage, 100));
|
||||
|
||||
// See app/Traits/TorrentMeta.php
|
||||
$this->scopeMeta($torrents);
|
||||
/** @phpstan-ignore method.notFound (this method exists at time of writing) */
|
||||
$results = $paginator->getCollection();
|
||||
$torrents = collect();
|
||||
|
||||
foreach ($results['hits'] ?? [] as $hit) {
|
||||
$meta = $hit['movie'] ?? $hit['tv'] ?? [];
|
||||
|
||||
/** @see TorrentResource */
|
||||
$torrents->push([
|
||||
'type' => 'torrent',
|
||||
'id' => (string) $hit['id'],
|
||||
'attributes' => [
|
||||
'meta' => [
|
||||
'poster' => \array_key_exists('poster', $meta) ? tmdb_image('poster_small', $meta['poster']) : null,
|
||||
'genres' => \array_key_exists('genres', $meta) ? implode(', ', array_column($meta['genres'], 'name')) : '',
|
||||
],
|
||||
'name' => $hit['name'],
|
||||
'release_year' => $meta['year'] ?? null,
|
||||
'category' => $hit['category']['name'] ?? null,
|
||||
'type' => $hit['type']['name'] ?? null,
|
||||
'resolution' => $hit['resolution']['name'] ?? null,
|
||||
'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'],
|
||||
'freeleech' => $hit['free'].'%',
|
||||
'double_upload' => $hit['doubleup'],
|
||||
'refundable' => $hit['refundable'],
|
||||
'internal' => $hit['internal'],
|
||||
'featured' => $hit['featured'],
|
||||
'personal_release' => $hit['personal_release'],
|
||||
'uploader' => $hit['anon'] ? 'Anonymous' : $hit['user']['username'],
|
||||
'seeders' => $hit['seeders'],
|
||||
'leechers' => $hit['leechers'],
|
||||
'times_completed' => $hit['times_completed'],
|
||||
'tmdb_id' => $hit['tmdb'],
|
||||
'imdb_id' => $hit['imdb'],
|
||||
'tvdb_id' => $hit['tvdb'],
|
||||
'mal_id' => $hit['mal'],
|
||||
'igdb_id' => $hit['igdb'],
|
||||
'category_id' => $hit['category']['id'] ?? null,
|
||||
'type_id' => $hit['type']['id'] ?? null,
|
||||
'resolution_id' => $hit['resolution']['id'] ?? null,
|
||||
'created_at' => date('Y-m-d\TH:i:s', $hit['created_at']).'.000000Z',
|
||||
'details_link' => route('torrents.show', ['id' => $hit['id']]),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/** @phpstan-ignore method.notFound (this method exists at time of writing) */
|
||||
$torrents = $paginator->setCollection(collect($torrents));
|
||||
}
|
||||
|
||||
return $torrents;
|
||||
});
|
||||
|
||||
if ($torrents !== null) {
|
||||
return new TorrentsResource($torrents);
|
||||
if ($isSqlAllowed) {
|
||||
if ($torrents !== null) {
|
||||
return new TorrentsResource($torrents);
|
||||
}
|
||||
} elseif ($torrents->isNotEmpty()) {
|
||||
$page = $request->integer('page') ?: 1;
|
||||
$perPage = min(100, $request->integer('perPage') ?: 25);
|
||||
|
||||
// Auth keys must not be cached
|
||||
$torrents->through(function ($torrent) {
|
||||
$torrent['attributes']['download_link'] = route('torrent.download.rsskey', ['id' => $torrent['id'], 'rsskey' => auth('api')->user()->rsskey]);
|
||||
$torrent['attributes']['magnet_link'] = config('torrent.magnet') ? 'magnet:?dn='.$torrent['attributes']['name'].'&xt=urn:btih:'.$torrent['attributes']['info_hash'].'&as='.route('torrent.download.rsskey', ['id' => $torrent['id'], 'rsskey' => auth('api')->user()->rsskey]).'&tr='.route('announce', ['passkey' => auth('api')->user()->passkey]).'&xl='.$torrent['attributes']['size'] : null;
|
||||
|
||||
return $torrent;
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => $torrents->items(),
|
||||
'links' => [
|
||||
'first' => $request->fullUrlWithoutQuery(['page' => 1]),
|
||||
'last' => null,
|
||||
'prev' => $page === 1 ? null : $request->fullUrlWithQuery(['page' => $page - 1]),
|
||||
'next' => $request->fullUrlWithQuery(['page' => $page + 1]),
|
||||
'self' => $request->fullUrl(),
|
||||
],
|
||||
'meta' => [
|
||||
'current_page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'from' => ($page - 1) * $perPage + 1,
|
||||
'to' => ($page - 1) * $perPage + \count($torrents->items()),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->sendResponse('404', 'No Torrents Found');
|
||||
|
||||
@@ -17,9 +17,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use App\Models\Bookmark;
|
||||
use App\Models\FeaturedTorrent;
|
||||
use App\Models\FreeleechToken;
|
||||
use App\Models\Group;
|
||||
use App\Models\Poll;
|
||||
use App\Models\Post;
|
||||
@@ -55,9 +53,8 @@ class HomeController extends Controller
|
||||
}
|
||||
|
||||
return view('home.index', [
|
||||
'user' => $user,
|
||||
'personal_freeleech' => cache()->get('personal_freeleech:'.$user->id),
|
||||
'users' => cache()->remember(
|
||||
'user' => $user,
|
||||
'users' => cache()->remember(
|
||||
'online_users:by-group:'.auth()->user()->group_id,
|
||||
$expiresAt,
|
||||
fn () => User::with('group', 'privacy')
|
||||
@@ -121,8 +118,6 @@ class HomeController extends Controller
|
||||
->orWhereNull('expires_at');
|
||||
})->latest()->first();
|
||||
}),
|
||||
'freeleech_tokens' => FreeleechToken::where('user_id', $user->id)->get(),
|
||||
'bookmarks' => Bookmark::where('user_id', $user->id)->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ use App\Models\Type;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Meilisearch\Endpoints\Indexes;
|
||||
|
||||
/**
|
||||
@@ -182,9 +181,10 @@ class RssController extends Controller
|
||||
dead: (bool) ($search->dead ?? false),
|
||||
);
|
||||
|
||||
$torrents = Torrent::search(
|
||||
$results = Torrent::search(
|
||||
$search->search ?? '',
|
||||
function (Indexes $meilisearch, string $query, array $options) use ($filters) {
|
||||
$options['limit'] = 50;
|
||||
$options['sort'] = [
|
||||
'bumped_at:desc',
|
||||
];
|
||||
@@ -196,37 +196,9 @@ class RssController extends Controller
|
||||
return $results;
|
||||
}
|
||||
)
|
||||
->query(
|
||||
fn (Builder $query) => $query->
|
||||
select([
|
||||
'name',
|
||||
'id',
|
||||
'category_id',
|
||||
'type_id',
|
||||
'resolution_id',
|
||||
'size',
|
||||
'created_at',
|
||||
'seeders',
|
||||
'leechers',
|
||||
'times_completed',
|
||||
'user_id',
|
||||
'anon',
|
||||
'imdb',
|
||||
'tmdb',
|
||||
'tvdb',
|
||||
'mal',
|
||||
'internal',
|
||||
])
|
||||
->with([
|
||||
'user:id,username,rsskey',
|
||||
'category:id,name,movie_meta,tv_meta',
|
||||
'type:id,name',
|
||||
'resolution:id,name'
|
||||
])
|
||||
)
|
||||
->paginate(50);
|
||||
->raw();
|
||||
|
||||
return $torrents;
|
||||
return $results['hits'] ?? [];
|
||||
});
|
||||
|
||||
return response()->view('rss.show', [
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Category;
|
||||
use App\Models\Movie;
|
||||
use App\Models\Torrent;
|
||||
use App\Models\TorrentRequest;
|
||||
use App\Models\Tv;
|
||||
use App\Services\Tmdb\TMDBScraper;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -103,7 +104,13 @@ class SimilarTorrentController extends Controller
|
||||
|
||||
public function update(Request $request, Category $category, int $tmdbId): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if ($tmdbId === 0 || Torrent::where('category_id', '=', $category->id)->where('tmdb', '=', $tmdbId)->doesntExist()) {
|
||||
if (
|
||||
$tmdbId === 0
|
||||
|| (
|
||||
Torrent::where('category_id', '=', $category->id)->where('tmdb', '=', $tmdbId)->doesntExist()
|
||||
&& TorrentRequest::where('category_id', '=', $category->id)->where('tmdb', '=', $tmdbId)->doesntExist()
|
||||
)
|
||||
) {
|
||||
return to_route('torrents.similar', ['category_id' => $category->id, 'tmdb' => $tmdbId])
|
||||
->withErrors('There exists no torrent with this tmdb.');
|
||||
}
|
||||
@@ -149,7 +156,6 @@ class SimilarTorrentController extends Controller
|
||||
break;
|
||||
}
|
||||
|
||||
return to_route('torrents.similar', ['category_id' => $category->id, 'tmdb' => $tmdbId])
|
||||
->withSuccess('Metadata update queued successfully.');
|
||||
return back()->withSuccess('Metadata update queued successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace App\Http\Controllers\Staff;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Audit;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
@@ -30,15 +31,17 @@ class AuditController extends Controller
|
||||
*/
|
||||
public function index(): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
{
|
||||
return view('Staff.audit.index', ['staffActivities' => Audit::with(['user', 'user.group'])
|
||||
->whereRelation('user.group', 'is_modo', '=', true)
|
||||
->where('action', '!=', 'create') // Exclude audits with action 'create'
|
||||
->select('user_id')
|
||||
->selectRaw('COUNT(*) as total_actions')
|
||||
->selectRaw('SUM(CASE WHEN created_at > NOW() - INTERVAL 60 DAY THEN 1 ELSE 0 END) as last_60_days')
|
||||
->selectRaw('SUM(CASE WHEN created_at > NOW() - INTERVAL 30 DAY THEN 1 ELSE 0 END) as last_30_days')
|
||||
->groupBy('user_id')
|
||||
->get()]);
|
||||
return view('Staff.audit.index', [
|
||||
'staffUsers' => User::query()
|
||||
->with(['group'])
|
||||
->whereRelation('group', 'is_modo', '=', true)
|
||||
->withCount([
|
||||
'audits as total_actions' => fn ($query) => $query->where('action', '!=', 'create'),
|
||||
'audits as last_60_days' => fn ($query) => $query->where('action', '!=', 'create')->whereBetween('created_at', [now()->subDays(60), now()]),
|
||||
'audits as last_30_days' => fn ($query) => $query->where('action', '!=', 'create')->whereBetween('created_at', [now()->subDays(30), now()]),
|
||||
])
|
||||
->get()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,28 @@ class DonationController extends Controller
|
||||
{
|
||||
abort_unless($request->user()->group->is_owner, 403);
|
||||
|
||||
return view('Staff.donation.index', ['donations' => Donation::with('package')->latest()->paginate(25)]);
|
||||
$donations = Donation::with('package')->latest()->paginate(25);
|
||||
|
||||
$dailyDonations = Donation::selectRaw('DATE(donations.created_at) as date, SUM(donation_packages.cost) as total')
|
||||
->join('donation_packages', 'donations.package_id', '=', 'donation_packages.id')
|
||||
->where('donations.status', '=', Donation::APPROVED)
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->get();
|
||||
|
||||
$monthlyDonations = Donation::selectRaw('YEAR(donations.created_at) as year, MONTH(donations.created_at) as month, SUM(donation_packages.cost) as total')
|
||||
->join('donation_packages', 'donations.package_id', '=', 'donation_packages.id')
|
||||
->where('donations.status', '=', Donation::APPROVED)
|
||||
->groupBy('year', 'month')
|
||||
->orderBy('year')
|
||||
->orderBy('month')
|
||||
->get();
|
||||
|
||||
return view('Staff.donation.index', [
|
||||
'donations' => $donations,
|
||||
'dailyDonations' => $dailyDonations,
|
||||
'monthlyDonations' => $monthlyDonations,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -112,4 +112,16 @@ class TicketController extends Controller
|
||||
return to_route('tickets.index')
|
||||
->withSuccess(trans('ticket.closed-success'));
|
||||
}
|
||||
|
||||
final public function reopen(Request $request, Ticket $ticket): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->group->is_modo || $request->user()->id === $ticket->user_id, 403);
|
||||
|
||||
$ticket->update([
|
||||
'closed_at' => null,
|
||||
]);
|
||||
|
||||
return to_route('tickets.show', ['ticket' => $ticket])
|
||||
->withSuccess(trans('ticket.reopened-success'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ class PeerSearch extends Component
|
||||
->selectRaw('SUM(peers.connectable = 0) as unconnectable_count')
|
||||
->selectRaw('SUM(peers.active = 1) as active_count')
|
||||
->selectRaw('SUM(peers.active = 0) as inactive_count')
|
||||
->selectRaw('ROUND(COALESCE(SUM(peers.active = 0) / SUM(peers.active = 1), 0), 2) as inactive_ratio')
|
||||
->groupBy(['peers.user_id', 'peers.agent', 'peers.ip', 'peers.port'])
|
||||
->with(['user', 'user.group'])
|
||||
)
|
||||
@@ -161,6 +162,7 @@ class PeerSearch extends Component
|
||||
->selectRaw('SUM(peers.connectable = 0) as unconnectable_count')
|
||||
->selectRaw('SUM(peers.active = 1) as active_count')
|
||||
->selectRaw('SUM(peers.active = 0) as inactive_count')
|
||||
->selectRaw('ROUND(COALESCE(SUM(peers.active = 0) / SUM(peers.active = 1), 0), 2) as inactive_ratio')
|
||||
->groupBy(['peers.user_id', 'peers.ip'])
|
||||
->with(['user', 'user.group'])
|
||||
)
|
||||
@@ -182,6 +184,7 @@ class PeerSearch extends Component
|
||||
->selectRaw('SUM(peers.connectable = 0) as unconnectable_count')
|
||||
->selectRaw('SUM(peers.active = 1) as active_count')
|
||||
->selectRaw('SUM(peers.active = 0) as inactive_count')
|
||||
->selectRaw('ROUND(COALESCE(SUM(peers.active = 0) / SUM(peers.active = 1), 0), 2) as inactive_ratio')
|
||||
->groupBy(['peers.user_id'])
|
||||
->with(['user', 'user.group'])
|
||||
)
|
||||
|
||||
@@ -55,11 +55,8 @@ class ReportSearch extends Component
|
||||
#[Url(history: true)]
|
||||
public ?string $type = null;
|
||||
|
||||
#[Url(history: true, except: 'exclude')]
|
||||
public ?string $solved = 'exclude';
|
||||
|
||||
#[Url(history: true)]
|
||||
public bool $hideSnoozed = true;
|
||||
public ?string $status = 'open';
|
||||
|
||||
#[Url(history: true)]
|
||||
public string $sortField = 'created_at';
|
||||
@@ -85,9 +82,10 @@ class ReportSearch extends Component
|
||||
->when($this->title !== null, fn ($query) => $query->where('title', 'LIKE', '%'.str_replace(' ', '%', '%'.$this->title.'%')))
|
||||
->when($this->message !== null, fn ($query) => $query->where('message', 'LIKE', '%'.str_replace(' ', '%', '%'.$this->message.'%')))
|
||||
->when($this->verdict !== null, fn ($query) => $query->where('verdict', 'LIKE', '%'.str_replace(' ', '%', '%'.$this->verdict.'%')))
|
||||
->when($this->solved === 'include', fn ($query) => $query->where('solved', '=', true))
|
||||
->when($this->solved === 'exclude', fn ($query) => $query->where('solved', '=', false))
|
||||
->when($this->hideSnoozed, fn ($query) => $query->where(fn ($query) => $query->whereNull('snoozed_until')->orWhere('snoozed_until', '<', now())))
|
||||
->when($this->status === 'open', fn ($query) => $query->where('solved', '=', false)->where(fn ($query) => $query->whereNull('snoozed_until')->orWhere('snoozed_until', '<', now())))
|
||||
->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($this->sortField, $this->sortDirection)
|
||||
->paginate($this->perPage);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ class TorrentRequestSearch extends Component
|
||||
$query->where('user_id', '=', $user->id);
|
||||
})
|
||||
->when($this->myClaims, function ($query) use ($user): void {
|
||||
$query->whereRelation('claim', 'user_id', '=', $user->id)->whereNull('approved_by');
|
||||
$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);
|
||||
|
||||
@@ -16,6 +16,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
@@ -31,6 +32,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
*/
|
||||
class DonationGateway extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\DonationGatewayFactory> */
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,9 @@ use Illuminate\Database\Eloquent\Model;
|
||||
*/
|
||||
class DonationPackage extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\DonationPackagefactory> */
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
|
||||
@@ -794,6 +794,9 @@ class Torrent extends Model
|
||||
$missingRequiredAttributes = array_diff([
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'mediainfo',
|
||||
'bdinfo',
|
||||
'num_file',
|
||||
'folder',
|
||||
'size',
|
||||
@@ -861,6 +864,9 @@ class Torrent extends Model
|
||||
return [
|
||||
'id' => $torrent->id,
|
||||
'name' => $torrent->name,
|
||||
'description' => $torrent->description,
|
||||
'mediainfo' => $torrent->mediainfo,
|
||||
'bdinfo' => $torrent->bdinfo,
|
||||
'num_file' => $torrent->num_file,
|
||||
'folder' => $torrent->folder,
|
||||
'size' => $torrent->size,
|
||||
|
||||
@@ -947,6 +947,16 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
return $this->hasMany(TorrentTrump::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has Many Audits.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Audit, $this>
|
||||
*/
|
||||
public function audits(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Audit::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has many donations.
|
||||
*
|
||||
|
||||
@@ -19,13 +19,11 @@ namespace App\Providers;
|
||||
use App\Helpers\ByteUnits;
|
||||
use App\Helpers\HiddenCaptcha;
|
||||
use App\Interfaces\ByteUnitsInterface;
|
||||
use App\Models\Page;
|
||||
use App\Models\User;
|
||||
use App\Observers\UserObserver;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -53,13 +51,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
// User Observer For Cache
|
||||
User::observe(UserObserver::class);
|
||||
|
||||
// Share $footer_pages across all views
|
||||
view()->composer('*', function (View $view): void {
|
||||
$footerPages = cache()->remember('cached-pages', 3_600, fn () => Page::select(['id', 'name', 'created_at'])->take(6)->get());
|
||||
|
||||
$view->with(['footer_pages' => $footerPages]);
|
||||
});
|
||||
|
||||
// Hidden Captcha
|
||||
Blade::directive('hiddencaptcha', fn ($mustBeEmptyField = '_username') => \sprintf('<?= App\Helpers\HiddenCaptcha::render(%s); ?>', $mustBeEmptyField));
|
||||
|
||||
|
||||
@@ -35,9 +35,16 @@ class EmailBlacklist implements ValidationRule
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
// Load blacklisted domains
|
||||
$this->domains = cache()->get(config('email-blacklist.cache-key'));
|
||||
$this->domains = cache()->get(config('email-blacklist.cache-key'), []);
|
||||
$this->appendCustomDomains();
|
||||
|
||||
// Fail if the domain blacklist cache is empty
|
||||
if (empty($this->domains)) {
|
||||
$fail('The email blacklist cache is currently empty. Please try again later or contact staff.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract domain from supplied email address
|
||||
$domain = Str::after(strtolower((string) $value), '@');
|
||||
|
||||
|
||||
@@ -214,6 +214,22 @@ class Unit3dAnnounce
|
||||
* is_lifetime: bool,
|
||||
* num_seeding: int,
|
||||
* num_leeching: int,
|
||||
* receive_seed_list_rates: array{
|
||||
* rates: array{
|
||||
* count: int,
|
||||
* max_count: int,
|
||||
* window: int,
|
||||
* updated_at: double,
|
||||
* }
|
||||
* },
|
||||
* receive_leech_list_rates: array{
|
||||
* rates: array{
|
||||
* count: int,
|
||||
* max_count: int,
|
||||
* window: int,
|
||||
* updated_at: double,
|
||||
* }
|
||||
* },
|
||||
* }
|
||||
*/
|
||||
public static function getUser(int $userId): bool|array
|
||||
@@ -225,19 +241,41 @@ class Unit3dAnnounce
|
||||
}
|
||||
|
||||
if (
|
||||
\array_key_exists('id', $user) && \is_int($user['id'])
|
||||
&& \array_key_exists('group_id', $user) && \is_int($user['group_id'])
|
||||
&& \array_key_exists('passkey', $user) && \is_string($user['passkey'])
|
||||
&& \array_key_exists('can_download', $user) && \is_bool($user['can_download'])
|
||||
&& \array_key_exists('is_donor', $user) && \is_bool($user['is_donor'])
|
||||
&& \array_key_exists('is_lifetime', $user) && \is_bool($user['is_lifetime'])
|
||||
&& \array_key_exists('num_seeding', $user) && \is_int($user['num_seeding'])
|
||||
&& \array_key_exists('num_leeching', $user) && \is_int($user['num_leeching'])
|
||||
!\array_key_exists('id', $user) || !\is_int($user['id'])
|
||||
|| !\array_key_exists('group_id', $user) || !\is_int($user['group_id'])
|
||||
|| !\array_key_exists('passkey', $user) || !\is_string($user['passkey'])
|
||||
|| !\array_key_exists('can_download', $user) || !\is_bool($user['can_download'])
|
||||
|| !\array_key_exists('is_donor', $user) || !\is_bool($user['is_donor'])
|
||||
|| !\array_key_exists('is_lifetime', $user) || !\is_bool($user['is_lifetime'])
|
||||
|| !\array_key_exists('num_seeding', $user) || !\is_int($user['num_seeding'])
|
||||
|| !\array_key_exists('num_leeching', $user) || !\is_int($user['num_leeching'])
|
||||
) {
|
||||
return $user;
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
foreach ($user['receive_seed_list_rates']['rates'] as $rate) {
|
||||
if (
|
||||
!\array_key_exists('count', $rate) || !\is_float($rate['count'])
|
||||
|| !\array_key_exists('max_count', $rate) || !\is_float($rate['max_count'])
|
||||
|| !\array_key_exists('window', $rate) || !\is_float($rate['window'])
|
||||
|| !\array_key_exists('updated_at', $rate) || !\is_float($rate['updated_at'])
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($user['receive_leech_list_rates']['rates'] as $rate) {
|
||||
if (
|
||||
!\array_key_exists('count', $rate) || !\is_float($rate['count'])
|
||||
|| !\array_key_exists('max_count', $rate) || !\is_float($rate['max_count'])
|
||||
|| !\array_key_exists('window', $rate) || !\is_float($rate['window'])
|
||||
|| !\array_key_exists('updated_at', $rate) || !\is_float($rate['updated_at'])
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function addGroup(Group $group): bool
|
||||
|
||||
@@ -30,6 +30,7 @@ return [
|
||||
'username' => 'UNIT3D',
|
||||
'password' => 'UNIT3D',
|
||||
'channel' => '#announce',
|
||||
'channel_key' => 'UNIT3D',
|
||||
'nickservpass' => false,
|
||||
'joinchannel' => false,
|
||||
];
|
||||
|
||||
@@ -91,7 +91,7 @@ return [
|
||||
* 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url'
|
||||
*/
|
||||
|
||||
'referrer-policy' => 'no-referrer-when-downgrade',
|
||||
'referrer-policy' => 'same-origin',
|
||||
|
||||
/*
|
||||
* Clear-Site-Data
|
||||
@@ -547,6 +547,16 @@ return [
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src
|
||||
'media-src' => [
|
||||
'self' => true,
|
||||
|
||||
'schemes' => [
|
||||
'data:',
|
||||
'https:',
|
||||
],
|
||||
|
||||
'allow' => [
|
||||
'https://www.youtube-nocookie.com/embed/',
|
||||
],
|
||||
],
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/navigate-to
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'powered-by' => 'Powered By UNIT3D Community Edition v8.2.0',
|
||||
'powered-by' => 'Powered By UNIT3D Community Edition v8.3.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -45,7 +45,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'version' => 'v8.2.0',
|
||||
'version' => 'v8.3.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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 Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use App\Models\DonationGateway;
|
||||
|
||||
/** @extends Factory<DonationGateway> */
|
||||
class DonationGatewayFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*/
|
||||
protected $model = DonationGateway::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'position' => $this->faker->randomDigitNotNull(),
|
||||
'name' => $this->faker->name(),
|
||||
'address' => $this->faker->word(),
|
||||
'is_active' => $this->faker->boolean(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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 Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use App\Models\DonationPackage;
|
||||
|
||||
/** @extends Factory<DonationPackage> */
|
||||
class DonationPackageFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*/
|
||||
protected $model = DonationPackage::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'position' => $this->faker->randomDigitNotNull(),
|
||||
'name' => $this->faker->name(),
|
||||
'description' => $this->faker->sentence(3),
|
||||
'cost' => $this->faker->randomFloat(2, 10, 100), // Generates a random float with 2 decimal places between 10 and 100
|
||||
'upload_value' => $this->faker->randomNumber(),
|
||||
'invite_value' => $this->faker->randomNumber(),
|
||||
'bonus_value' => $this->faker->randomNumber(),
|
||||
'donor_value' => $this->faker->numberBetween(30, 365),
|
||||
'is_active' => $this->faker->boolean(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('audits', function (Blueprint $table): void {
|
||||
$table->index(['user_id', 'action', 'created_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('torrents', function (Blueprint $table): void {
|
||||
$table->index(['category_id', 'status', 'deleted_at', 'tmdb', 'size']);
|
||||
});
|
||||
|
||||
Schema::table('history', function (Blueprint $table): void {
|
||||
$table->index(['torrent_id', 'completed_at', 'created_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -40,6 +40,8 @@ return [
|
||||
'note-destroy-success' => 'Staff Note Removed Successfully!',
|
||||
'opened-by' => 'Opened By:',
|
||||
'priority' => 'Priority',
|
||||
'reopen' => 'Reopen',
|
||||
'reopen-success' => 'Your helpdesk ticket was reopened successfully',
|
||||
'reset' => 'Reset',
|
||||
'staff-notes' => 'Staff Notes',
|
||||
'subject' => 'Subject',
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"ajv": "^8.10.0",
|
||||
"alpinejs": "^3.9.1",
|
||||
"axios": "^1.7.2",
|
||||
"chart.js": "^4.4.4",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.0",
|
||||
"laravel-echo": "^1.11.4",
|
||||
|
||||
@@ -265,11 +265,6 @@ parameters:
|
||||
count: 1
|
||||
path: app/Http/Controllers/API/TorrentController.php
|
||||
|
||||
-
|
||||
message: "#^Unreachable statement \\- code above always terminates\\.$#"
|
||||
count: 1
|
||||
path: app/Http/Controllers/API/TorrentController.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property object\\:\\:\\$banned_id\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -32,6 +32,12 @@ if (token) {
|
||||
import Swal from 'sweetalert2';
|
||||
window.Swal = Swal;
|
||||
|
||||
// ChartJS
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
Chart.register(...registerables);
|
||||
window.Chart = Chart;
|
||||
|
||||
// Vite Import
|
||||
import.meta.glob(['/public/img/pipes/**', '/resources/sass/vendor/webfonts/font-awesome/**']);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ The above copyright notice and this permission notice shall be included in all c
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*! modern-normalize v2.0.0 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||
/*! modern-normalize v3.0.1 | MIT License | https://github.com/sindresorhus/modern-normalize */
|
||||
|
||||
/*
|
||||
Document
|
||||
@@ -35,8 +35,7 @@ html {
|
||||
'Apple Color Emoji', 'Segoe UI Emoji';
|
||||
line-height: 1.15; /* 1. Correct the line height in all browsers. */
|
||||
-webkit-text-size-adjust: 100%; /* 2. Prevent adjustments of font size after orientation changes in iOS. */
|
||||
-moz-tab-size: 4; /* 3. Use a more readable tab size (opinionated). */
|
||||
tab-size: 4; /* 3 */
|
||||
tab-size: 4; /* 3. Use a more readable tab size (opinionated). */
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -48,36 +47,13 @@ body {
|
||||
margin: 0; /* Remove the margin in all browsers. */
|
||||
}
|
||||
|
||||
/*
|
||||
Grouping content
|
||||
================
|
||||
*/
|
||||
|
||||
/**
|
||||
1. Add the correct height in Firefox.
|
||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
Text-level semantics
|
||||
====================
|
||||
*/
|
||||
|
||||
/**
|
||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
Add the correct font weight in Edge and Safari.
|
||||
Add the correct font weight in Chrome and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
@@ -133,13 +109,11 @@ Tabular data
|
||||
*/
|
||||
|
||||
/**
|
||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||
2. Correct table border color inheritance in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
Correct table border color inheritance in Chrome and Safari. (https://issues.chromium.org/issues/40615503, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||
*/
|
||||
|
||||
table {
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-color: currentcolor;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -163,15 +137,6 @@ textarea {
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the inheritance of text transform in Edge and Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
@@ -183,32 +148,6 @@ button,
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the additional ':invalid' styles in Firefox.
|
||||
See: https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/**
|
||||
Remove the padding so developers are not caught out when they zero out 'fieldset' elements in all browsers.
|
||||
*/
|
||||
|
||||
@@ -263,13 +263,14 @@
|
||||
overflow-x: auto;
|
||||
grid-area: chips;
|
||||
gap: 24px;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.meta__chip-container {
|
||||
position: relative;
|
||||
padding-top: 16px;
|
||||
height: calc(100%);
|
||||
overflow-y: auto;
|
||||
overflow-y: scroll;
|
||||
flex: 1 0 200px;
|
||||
}
|
||||
|
||||
@@ -314,6 +315,7 @@
|
||||
gap: 3px 20px;
|
||||
border-radius: 26px;
|
||||
padding: 6px 26px 6px 6px;
|
||||
will-change: backdrop-filter, background;
|
||||
}
|
||||
|
||||
.meta-chip:hover {
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
height: 130px;
|
||||
}
|
||||
|
||||
.playlists__playlist-image--none::before {
|
||||
|
||||
@@ -46,3 +46,15 @@
|
||||
height: 40px;
|
||||
border-radius: 10%;
|
||||
}
|
||||
|
||||
/* Donation Charts */
|
||||
|
||||
.chart-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 20px;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
--donation-secondary-text-color: #aaaaaa;
|
||||
--donation-background-color: #1c1c1c;
|
||||
--donation-price-days-color: orange;
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #bbb;
|
||||
|
||||
@@ -93,6 +93,11 @@
|
||||
--dialog-head-bg: #373d43;
|
||||
--dialog-head-fg: #fff;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #444;
|
||||
--fieldset-border-radius: 5px;
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
--dialog-head-bg: #2a292e;
|
||||
--dialog-head-fg: #050505;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #aaa;
|
||||
--fieldset-border-radius: 20px;
|
||||
|
||||
@@ -92,6 +92,11 @@
|
||||
--dialog-head-bg: #2a292e;
|
||||
--dialog-head-fg: #050505;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #aaa;
|
||||
--fieldset-border-radius: 20px;
|
||||
|
||||
@@ -91,6 +91,11 @@
|
||||
--dialog-head-bg: #fff;
|
||||
--dialog-head-fg: #050505;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #333;
|
||||
--fieldset-border-radius: 20px;
|
||||
|
||||
@@ -111,6 +111,11 @@
|
||||
--dialog-head-bg: #3b4252;
|
||||
--dialog-head-fg: #d8dee9;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #5e81ac;
|
||||
--fieldset-border-radius: 10px;
|
||||
|
||||
@@ -117,6 +117,11 @@
|
||||
--dialog-head-bg: #232323;
|
||||
--dialog-head-fg: #fff;
|
||||
|
||||
--donation-chart-daily-bg: rgba(75, 192, 192, 0.2);
|
||||
--donation-chart-daily-border: rgba(75, 192, 192, 1);
|
||||
--donation-chart-monthly-bg: rgba(153, 102, 255, 0.2);
|
||||
--donation-chart-monthly-border: rgba(153, 102, 255, 1);
|
||||
|
||||
--fieldset-bg: inherit;
|
||||
--fieldset-fg: #bbb;
|
||||
--fieldset-border-radius: 0;
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($staffActivities as $activity)
|
||||
@foreach ($staffUsers as $staffUser)
|
||||
<tr>
|
||||
<td>
|
||||
<x-user_tag :anon="false" :user="$activity->user" />
|
||||
<x-user_tag :anon="false" :user="$staffUser" />
|
||||
</td>
|
||||
<td>{{ $activity->last_30_days }}</td>
|
||||
<td>{{ $activity->last_60_days }}</td>
|
||||
<td>{{ $activity->total_actions }}</td>
|
||||
<td>{{ $staffUser->last_30_days }}</td>
|
||||
<td>{{ $staffUser->last_60_days }}</td>
|
||||
<td>{{ $staffUser->total_actions }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<section class="panelV2">
|
||||
<header class="panel__header">
|
||||
<h2 class="panel__heading">Donation Statistics</h2>
|
||||
</header>
|
||||
<div class="chart-wrapper">
|
||||
<div>
|
||||
<canvas id="dailyDonationsChart"></canvas>
|
||||
</div>
|
||||
<div>
|
||||
<canvas id="monthlyDonationsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panelV2">
|
||||
<header class="panel__header">
|
||||
<h2 class="panel__heading">Donations</h2>
|
||||
@@ -40,10 +54,10 @@
|
||||
<td>{{ $donation->transaction }}</td>
|
||||
<td>$ {{ $donation->package->cost }}</td>
|
||||
<td>
|
||||
{{ App\Helpers\StringHelper::formatBytes($donation->package->upload_value) }}
|
||||
{{ App\Helpers\StringHelper::formatBytes($donation->package->upload_value ?? 0) }}
|
||||
</td>
|
||||
<td>{{ $donation->package->invite_value }}</td>
|
||||
<td>{{ $donation->package->bonus_value }}</td>
|
||||
<td>{{ $donation->package->invite_value ?? 0 }}</td>
|
||||
<td>{{ $donation->package->bonus_value ?? 0 }}</td>
|
||||
<td>
|
||||
@if ($donation->package->donor_value === null)
|
||||
Lifetime
|
||||
@@ -108,3 +122,72 @@
|
||||
{{ $donations->links('partials.pagination') }}
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script nonce="{{ HDVinnie\SecureHeaders\SecureHeaders::nonce('script') }}">
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const dailyDonations = {!! Js::encode($dailyDonations) !!};
|
||||
const monthlyDonations = {!! Js::encode($monthlyDonations) !!};
|
||||
|
||||
// Daily Donations Chart
|
||||
const dailyCtx = document.getElementById('dailyDonationsChart').getContext('2d');
|
||||
new Chart(dailyCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: dailyDonations.map((donation) => donation.date),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Daily Donations',
|
||||
data: dailyDonations.map((donation) => donation.total),
|
||||
backgroundColor: getComputedStyle(
|
||||
document.documentElement,
|
||||
).getPropertyValue('--donation-chart-daily-bg'),
|
||||
borderColor: getComputedStyle(
|
||||
document.documentElement,
|
||||
).getPropertyValue('--donation-chart-daily-border'),
|
||||
borderWidth: 1,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: { type: 'time', time: { unit: 'day' } },
|
||||
y: { beginAtZero: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Monthly Donations Chart
|
||||
const monthlyCtx = document.getElementById('monthlyDonationsChart').getContext('2d');
|
||||
new Chart(monthlyCtx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: monthlyDonations.map(
|
||||
(donation) => `${donation.year}-${donation.month}`,
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Monthly Donations',
|
||||
data: monthlyDonations.map((donation) => donation.total),
|
||||
backgroundColor: getComputedStyle(
|
||||
document.documentElement,
|
||||
).getPropertyValue('--donation-chart-monthly-bg'),
|
||||
borderColor: getComputedStyle(
|
||||
document.documentElement,
|
||||
).getPropertyValue('--donation-chart-monthly-border'),
|
||||
borderWidth: 1,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: { type: 'category' },
|
||||
y: { beginAtZero: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
$releaseYear = $media->release_date instanceof \Illuminate\Support\Carbon ? $media->release_date->year : (int) $media->release_date;
|
||||
@endphp
|
||||
|
||||
{{ \preg_replace('/^.*( ' . implode(' | ', range($releaseYear - 1, $releaseYear + 1)) . ' )/i', '', $torrent->name) }}
|
||||
{{ str_contains($torrent->name, ' / ') ? $torrent->name : \preg_replace('/^.*( ' . implode(' | ', range($releaseYear - 1, $releaseYear + 1)) . ' )/i', '', $torrent->name) }}
|
||||
|
||||
@break
|
||||
@case('tv')
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
@endphp
|
||||
|
||||
{{ \preg_replace('/^.*( ' . implode(' | ', $firstAirDateRange) . ' | (?=S\d{2,4}(?:-S\d{2,4})?(?:-?E\d{2,4})*? |' . implode('-|', $fullRange) . '-))/i', '', $torrent->name) }}
|
||||
{{ str_contains($torrent->name, ' / ') ? $torrent->name : \preg_replace('/^.*( ' . implode(' | ', $firstAirDateRange) . ' | (?=S\d{2,4}(?:-S\d{2,4})?(?:-?E\d{2,4})*? |' . implode('-|', $fullRange) . '-))/i', '', $torrent->name) }}
|
||||
|
||||
@break
|
||||
@endswitch
|
||||
|
||||
@@ -17,15 +17,7 @@
|
||||
</div>
|
||||
<div class="panel__action">
|
||||
<div class="form__group">
|
||||
<select
|
||||
id="model"
|
||||
x-data="{ selected: '' }"
|
||||
x-model="selected"
|
||||
x-bind:class="selected === '' ? 'form__select--default' : ''"
|
||||
class="form__select"
|
||||
wire:model.live="modelName"
|
||||
required
|
||||
>
|
||||
<select id="model" class="form__select" wire:model.live="modelName">
|
||||
<option selected value="">All</option>
|
||||
@foreach ($modelNames as $modelName)
|
||||
<option value="{{ $modelName }}">{{ $modelName }}</option>
|
||||
|
||||
@@ -269,6 +269,15 @@
|
||||
: 'border-color 600ms cubic-bezier(0.25, 0.8, 0.25, 1), height 600ms cubic-bezier(0.25, 0.8, 0.25, 1)',
|
||||
};
|
||||
},
|
||||
['x-on:keydown.self.ctrl.b.prevent']() {
|
||||
this.insertBold();
|
||||
},
|
||||
['x-on:keydown.self.ctrl.i.prevent']() {
|
||||
this.insertItalic();
|
||||
},
|
||||
['x-on:keydown.self.ctrl.u.prevent']() {
|
||||
this.insertUnderline();
|
||||
},
|
||||
},
|
||||
insertBold() {
|
||||
this.insert('[b]', '[/b]');
|
||||
@@ -300,7 +309,7 @@
|
||||
insertColor() {
|
||||
this.insert('[color=]', '[/color]');
|
||||
},
|
||||
insertsize() {
|
||||
insertSize() {
|
||||
this.insert('[size=]', '[/size]');
|
||||
},
|
||||
insertFont() {
|
||||
@@ -345,12 +354,22 @@
|
||||
input = this.$refs.bbcode;
|
||||
start = input.selectionStart;
|
||||
end = input.selectionEnd;
|
||||
input.value =
|
||||
input.value.substring(0, start) +
|
||||
openTag +
|
||||
input.value.substring(start, end) +
|
||||
closeTag +
|
||||
input.value.substring(end);
|
||||
alreadyNested =
|
||||
input.value.substring(start, start + openTag.length) === openTag &&
|
||||
input.value.substring(end - closeTag.length, end) === closeTag;
|
||||
if (alreadyNested) {
|
||||
input.value =
|
||||
input.value.substring(0, start) +
|
||||
input.value.substring(start + openTag.length, end - closeTag.length) +
|
||||
input.value.substring(end);
|
||||
} else {
|
||||
input.value =
|
||||
input.value.substring(0, start) +
|
||||
openTag +
|
||||
input.value.substring(start, end) +
|
||||
closeTag +
|
||||
input.value.substring(end);
|
||||
}
|
||||
input.dispatchEvent(new Event('input'));
|
||||
input.focus();
|
||||
if (openTag.charAt(openTag.length - 2) === '=') {
|
||||
@@ -361,7 +380,11 @@
|
||||
} else if (start == end) {
|
||||
input.setSelectionRange(start + openTag.length, end + openTag.length);
|
||||
} else {
|
||||
input.setSelectionRange(start, end + openTag.length + closeTag.length);
|
||||
if (alreadyNested) {
|
||||
input.setSelectionRange(start, end - openTag.length - closeTag.length);
|
||||
} else {
|
||||
input.setSelectionRange(start, end + openTag.length + closeTag.length);
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -103,80 +103,71 @@
|
||||
{{ __('user.user') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.user_id'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.torrent_count')"
|
||||
role="columnheader button"
|
||||
>
|
||||
<th wire:click="sortBy('torrent_count')" role="columnheader button">
|
||||
{{ __('torrent.torrents') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'torrent_count'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.uploaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
<th wire:click="sortBy('uploaded_sum')" role="columnheader button">
|
||||
{{ __('user.credited-upload') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.uploaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.actual_uploaded_sum')"
|
||||
wire:click="sortBy('actual_uploaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
{{ __('user.upload-true') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.actual_uploaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.client_uploaded_sum')"
|
||||
wire:click="sortBy('client_uploaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
Client Upload
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.client_uploaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.downloaded_sum')"
|
||||
wire:click="sortBy('downloaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
{{ __('user.credited-download') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.downloaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.actual_downloaded_sum')"
|
||||
wire:click="sortBy('actual_downloaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
{{ __('user.download-true') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.actual_downloaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.client_downloaded_sum')"
|
||||
wire:click="sortBy('client_downloaded_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
Client Download
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.client_downloaded_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.refunded_download_sum')"
|
||||
wire:click="sortBy('refunded_download_sum')"
|
||||
role="columnheader button"
|
||||
>
|
||||
{{ __('torrent.refunded') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.refunded_download_sum'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.created_at_min')"
|
||||
wire:click="sortBy('created_at_min')"
|
||||
role="columnheader button"
|
||||
>
|
||||
{{ __('torrent.started') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.created_at_min'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.updated_at_max')"
|
||||
wire:click="sortBy('updated_at_max')"
|
||||
role="columnheader button"
|
||||
>
|
||||
Announced
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.updated_at_max'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('history.seedtime_avg')"
|
||||
role="columnheader button"
|
||||
>
|
||||
<th wire:click="sortBy('seedtime_avg')" role="columnheader button">
|
||||
{{ __('user.avg-seedtime') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'history.seedtime_avg'])
|
||||
</th>
|
||||
@@ -261,7 +252,7 @@
|
||||
</time>
|
||||
</td>
|
||||
|
||||
@if ($history->seedtime < config('hitrun.seedtime'))
|
||||
@if ($history->seedtime_avg < config('hitrun.seedtime'))
|
||||
<td
|
||||
class="text-red"
|
||||
title="{{ App\Helpers\StringHelper::timeElapsed($history->seedtime_avg) }}"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{{ $notification->data['title'] }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<td style="word-break: break-all">
|
||||
{{ $notification->data['body'] }}
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -309,6 +309,14 @@
|
||||
Inactive {{ __('torrent.peers') }}
|
||||
@include('livewire.includes._sort-icon', ['field' => 'inactive_count'])
|
||||
</th>
|
||||
<th
|
||||
wire:click="sortBy('inactive_ratio')"
|
||||
role="columnheader button"
|
||||
style="text-align: right"
|
||||
>
|
||||
Inactive/active ratio
|
||||
@include('livewire.includes._sort-icon', ['field' => 'inactive_ratio'])
|
||||
</th>
|
||||
@endif
|
||||
<th
|
||||
wire:click="sortBy('created_at')"
|
||||
@@ -437,6 +445,7 @@
|
||||
@else
|
||||
<td style="text-align: right">{{ $peer->active_count }}</td>
|
||||
<td style="text-align: right">{{ $peer->inactive_count }}</td>
|
||||
<td style="text-align: right">{{ $peer->inactive_ratio }}</td>
|
||||
@endif
|
||||
<td style="text-align: right">
|
||||
<time
|
||||
|
||||
@@ -102,17 +102,19 @@
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<select
|
||||
id="solved"
|
||||
wire:model.live="solved"
|
||||
id="status"
|
||||
wire:model.live="status"
|
||||
class="form__select"
|
||||
placeholder=" "
|
||||
>
|
||||
<option value="any">Any</option>
|
||||
<option value="include">Include</option>
|
||||
<option value="exclude">Exclude</option>
|
||||
<option value="open">Open</option>
|
||||
<option value="snoozed">Snoozed</option>
|
||||
<option value="closed">Closed</option>
|
||||
<option value="all">All</option>
|
||||
<option value="all_open">All open</option>
|
||||
</select>
|
||||
<label class="form__label form__label--floating" for="solved">
|
||||
{{ __('forum.solved') }}
|
||||
<label class="form__label form__label--floating" for="status">
|
||||
{{ __('common.status') }}
|
||||
</label>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
@@ -130,15 +132,6 @@
|
||||
{{ __('common.quantity') }}
|
||||
</label>
|
||||
</p>
|
||||
<p class="form__group">
|
||||
<input
|
||||
id="hideSnoozed"
|
||||
class="form__checkbox"
|
||||
type="checkbox"
|
||||
wire:model.live="hideSnoozed"
|
||||
/>
|
||||
<label class="form__label" for="hideSnoozed">Hide Snoozed</label>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -309,12 +309,12 @@
|
||||
@else
|
||||
@if ($connectable)
|
||||
<i
|
||||
class="{{ config('other.font-awesome') }} text-green fa-check"
|
||||
class="{{ config('other.font-awesome') }} text-green fa-wifi"
|
||||
title="Connectable"
|
||||
></i>
|
||||
@else
|
||||
<i
|
||||
class="{{ config('other.font-awesome') }} text-red fa-times"
|
||||
class="{{ config('other.font-awesome') }} text-red fa-wifi-slash"
|
||||
title="Not Connectable"
|
||||
></i>
|
||||
@endif
|
||||
@@ -326,12 +326,12 @@
|
||||
@if ($active->active)
|
||||
@if ($active->seeder)
|
||||
<i
|
||||
class="{{ config('other.font-awesome') }} text-green fa-check"
|
||||
class="{{ config('other.font-awesome') }} text-green fa-arrow-up"
|
||||
title="{{ __('torrent.seeding') }}"
|
||||
></i>
|
||||
@else
|
||||
<i
|
||||
class="{{ config('other.font-awesome') }} text-red fa-times"
|
||||
class="{{ config('other.font-awesome') }} text-red fa-arrow-down"
|
||||
title="Not {{ __('torrent.seeding') }}"
|
||||
></i>
|
||||
@endif
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
Soft Deleted ({{ $deletedWarningsCount ?? 0 }})
|
||||
</li>
|
||||
</menu>
|
||||
<div class="data-table-wrapper" x-data="userWarnings">
|
||||
<div class="data-table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -127,7 +127,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($warnings as $warning)
|
||||
<tr x-ref="warning" data-warning-id="{{ $warning->id }}">
|
||||
<tr x-data="userWarnings" data-warning-id="{{ $warning->id }}">
|
||||
<td>
|
||||
<x-user_tag :user="$warning->staffuser" :anon="false" />
|
||||
</td>
|
||||
@@ -261,24 +261,16 @@
|
||||
this.confirmAction(() => this.$wire.massDeactivate());
|
||||
},
|
||||
destroyWarning() {
|
||||
this.confirmAction(() =>
|
||||
this.$wire.destroy(this.$refs.warning.dataset.warningId),
|
||||
);
|
||||
this.confirmAction(() => this.$wire.destroy(this.$root.dataset.warningId));
|
||||
},
|
||||
reactivateWarning() {
|
||||
this.confirmAction(() =>
|
||||
this.$wire.reactivate(this.$refs.warning.dataset.warningId),
|
||||
);
|
||||
this.confirmAction(() => this.$wire.reactivate(this.$root.dataset.warningId));
|
||||
},
|
||||
deactivateWarning() {
|
||||
this.confirmAction(() =>
|
||||
this.$wire.deactivate(this.$refs.warning.dataset.warningId),
|
||||
);
|
||||
this.confirmAction(() => this.$wire.deactivate(this.$root.dataset.warningId));
|
||||
},
|
||||
restoreWarning() {
|
||||
this.confirmAction(() =>
|
||||
this.$wire.restore(this.$refs.warning.dataset.warningId),
|
||||
);
|
||||
this.confirmAction(() => this.$wire.restore(this.$root.dataset.warningId));
|
||||
},
|
||||
confirmAction(onConfirm) {
|
||||
Swal.fire({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@if (config('other.freeleech') == true || config('other.invite-only') == false || config('other.doubleup') == true)
|
||||
<section class="alert special-event-alert" x-data="timer()" x-init="start()">
|
||||
<div class="alert__content">
|
||||
<span>
|
||||
<strong>
|
||||
@if (config('other.freeleech') == true)
|
||||
🌐 {{ __('common.freeleech_activated') }} 🌐
|
||||
@endif
|
||||
@@ -13,7 +13,7 @@
|
||||
@if (config('other.doubleup') == true)
|
||||
🌐 {{ __('common.doubleup_activated') }} 🌐
|
||||
@endif
|
||||
</span>
|
||||
</strong>
|
||||
<div>
|
||||
<span x-text="days">00</span>
|
||||
<span>{{ __('common.day') }}</span>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div class="footer__wrapper">
|
||||
<section class="footer__section">
|
||||
<h2 class="footer__section-title">
|
||||
<b>{{ config('other.title') }}</b>
|
||||
<img src="{{ url('/favicon.ico') }}" style="height: 30px; vertical-align: sub" />
|
||||
<span class="top-nav__site-logo">{{ \config('other.title') }}</span>
|
||||
</h2>
|
||||
<p>{{ config('other.meta_description') }}</p>
|
||||
<p class="footer__icons">
|
||||
@@ -44,7 +45,7 @@
|
||||
<li><a href="{{ route('wikis.index') }}">Wikis</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
@if ($footer_pages)
|
||||
@if ($footer_pages = cache()->remember('cached-pages',3600,fn () => \App\Models\Page::select(['id', 'name', 'created_at'])->take(6)->get()))
|
||||
<section class="footer__section">
|
||||
<h2 class="footer__section-title">{{ __('common.pages') }}</h2>
|
||||
<ul class="footer__section-list">
|
||||
@@ -255,7 +256,9 @@
|
||||
Site and design ©
|
||||
{{ date('Y', strtotime(config('other.birthdate'))) }}-{{ date('Y') }}
|
||||
{{ config('other.title') }} |
|
||||
<a href="https://github.com/HDInnovations/UNIT3D-Community-Edition">UNIT3D</a>
|
||||
<a href="https://github.com/HDInnovations/UNIT3D-Community-Edition">
|
||||
UNIT3D {{ config('unit3d.version') }}
|
||||
</a>
|
||||
@if (config('announce.external_tracker.is_enabled'))
|
||||
+
|
||||
<a href="https://github.com/HDInnovations/UNIT3D-Announce">UNIT3D-Announce</a>
|
||||
|
||||
@@ -20,60 +20,60 @@
|
||||
@if($torrents)
|
||||
@foreach($torrents as $torrent)
|
||||
<item>
|
||||
<title>{{ $torrent->name }}</title>
|
||||
<category>{{ $torrent->category->name }}</category>
|
||||
<contentlength>{{ $torrent->size }}</contentlength>
|
||||
<link>{{ route('torrent.download.rsskey', ['id' => $torrent->id, 'rsskey' => $user->rsskey ]) }}</link>
|
||||
<guid>{{ $torrent->id }}</guid>
|
||||
<title>{{ $torrent['name'] }}</title>
|
||||
<category>{{ $torrent['category']['name'] }}</category>
|
||||
<contentlength>{{ $torrent['size'] }}</contentlength>
|
||||
<link>{{ route('torrent.download.rsskey', ['id' => $torrent['id'], 'rsskey' => $user->rsskey ]) }}</link>
|
||||
<guid>{{ $torrent['id'] }}</guid>
|
||||
<description>
|
||||
<![CDATA[<p>
|
||||
<strong>Name</strong>: {{ $torrent->name }}<br>
|
||||
<strong>Category</strong>: {{ $torrent->category->name }}<br>
|
||||
<strong>Type</strong>: {{ $torrent->type->name }}<br>
|
||||
<strong>Resolution</strong>: {{ $torrent->resolution->name ?? 'No Res' }}<br>
|
||||
<strong>Size</strong>: {{ $torrent->getSize() }}<br>
|
||||
<strong>Uploaded</strong>: {{ $torrent->created_at->diffForHumans() }}<br>
|
||||
<strong>Seeders</strong>: {{ $torrent->seeders }} |
|
||||
<strong>Leechers</strong>: {{ $torrent->leechers }} |
|
||||
<strong>Completed</strong>: {{ $torrent->times_completed }}<br>
|
||||
<strong>Name</strong>: {{ $torrent['name'] }}<br>
|
||||
<strong>Category</strong>: {{ $torrent['category']['name'] }}<br>
|
||||
<strong>Type</strong>: {{ $torrent['type']['name'] }}<br>
|
||||
<strong>Resolution</strong>: {{ $torrent['resolution']['name'] ?? 'No Res' }}<br>
|
||||
<strong>Size</strong>: {{ App\Helpers\StringHelper::formatBytes($torrent['size'], 2) }}<br>
|
||||
<strong>Uploaded</strong>: {{ \Illuminate\Support\Carbon::createFromTimestampUTC($torrent['created_at'])->diffForHumans() }}<br>
|
||||
<strong>Seeders</strong>: {{ $torrent['seeders'] }} |
|
||||
<strong>Leechers</strong>: {{ $torrent['leechers'] }} |
|
||||
<strong>Completed</strong>: {{ $torrent['times_completed'] }}<br>
|
||||
<strong>Uploader</strong>:
|
||||
@if(!$torrent->anon && $torrent->user)
|
||||
{{ __('torrent.uploaded-by') }} {{ $torrent->user->username }}
|
||||
@if(!$torrent['anon'] && $torrent['user'])
|
||||
{{ __('torrent.uploaded-by') }} {{ $torrent['user']['username'] }}
|
||||
@else
|
||||
{{ __('common.anonymous') }} {{ __('torrent.uploader') }}
|
||||
@endif<br>
|
||||
@if (($torrent->category->movie_meta || $torrent->category->tv_meta) && $torrent->imdb != 0)
|
||||
IMDB Link:<a href="https://anon.to?http://www.imdb.com/title/tt{{ $torrent->imdb }}"
|
||||
target="_blank">tt{{ $torrent->imdb }}</a><br>
|
||||
@if (($torrent['category']['movie_meta'] || $torrent['category']['tv_meta']) && $torrent['imdb'] != 0)
|
||||
IMDB Link:<a href="https://anon.to?http://www.imdb.com/title/tt{{ $torrent['imdb'] }}"
|
||||
target="_blank">tt{{ $torrent['imdb'] }}</a><br>
|
||||
@endif
|
||||
@if ($torrent->category->movie_meta && $torrent->tmdb != 0)
|
||||
TMDB Link: <a href="https://anon.to?https://www.themoviedb.org/movie/{{ $torrent->tmdb }}"
|
||||
target="_blank">{{ $torrent->tmdb }}</a><br>
|
||||
@elseif ($torrent->category->tv_meta && $torrent->tmdb != 0)
|
||||
TMDB Link: <a href="https://anon.to?https://www.themoviedb.org/tv/{{ $torrent->tmdb }}"
|
||||
target="_blank">{{ $torrent->tmdb }}</a><br>
|
||||
@if ($torrent['category']['movie_meta'] && $torrent['tmdb'] != 0)
|
||||
TMDB Link: <a href="https://anon.to?https://www.themoviedb.org/movie/{{ $torrent['tmdb'] }}"
|
||||
target="_blank">{{ $torrent['tmdb'] }}</a><br>
|
||||
@elseif ($torrent['category']['tv_meta'] && $torrent['tmdb'] != 0)
|
||||
TMDB Link: <a href="https://anon.to?https://www.themoviedb.org/tv/{{ $torrent['tmdb'] }}"
|
||||
target="_blank">{{ $torrent['tmdb'] }}</a><br>
|
||||
@endif
|
||||
@if (($torrent->category->tv_meta) && $torrent->tvdb != 0)
|
||||
TVDB Link:<a href="https://anon.to?https://www.thetvdb.com/?tab=series&id={{ $torrent->tvdb }}"
|
||||
target="_blank">{{ $torrent->tvdb }}</a><br>
|
||||
@if (($torrent['category']['tv_meta']) && $torrent['tvdb'] != 0)
|
||||
TVDB Link:<a href="https://anon.to?https://www.thetvdb.com/?tab=series&id={{ $torrent['tvdb'] }}"
|
||||
target="_blank">{{ $torrent['tvdb'] }}</a><br>
|
||||
@endif
|
||||
@if (($torrent->category->movie_meta || $torrent->category->tv_meta) && $torrent->mal != 0)
|
||||
MAL Link:<a href="https://anon.to?https://myanimelist.net/anime/{{ $torrent->mal }}"
|
||||
target="_blank">{{ $torrent->mal }}</a><br>
|
||||
@if (($torrent['category']['movie_meta'] || $torrent['category']['tv_meta']) && $torrent['mal'] != 0)
|
||||
MAL Link:<a href="https://anon.to?https://myanimelist.net/anime/{{ $torrent['mal'] }}"
|
||||
target="_blank">{{ $torrent['mal'] }}</a><br>
|
||||
@endif
|
||||
@if ($torrent->internal == 1)
|
||||
@if ($torrent['internal'] == 1)
|
||||
<comments>This is a high quality internal release!</comments>
|
||||
@endif
|
||||
</p>]]>
|
||||
</description>
|
||||
<dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
@if(!$torrent->anon && $torrent->user)
|
||||
{{ __('torrent.uploaded-by') }} {{ $torrent->user->username }}
|
||||
@if(!$torrent['anon'] && $torrent['user'])
|
||||
{{ __('torrent.uploaded-by') }} {{ $torrent['user']['username'] }}
|
||||
@else
|
||||
{{ __('common.anonymous') }} {{ __('torrent.uploader') }}
|
||||
@endif
|
||||
</dc:creator>
|
||||
<pubDate>{{ $torrent->created_at->toRssString() }}</pubDate>
|
||||
<pubDate>{{ \Illuminate\Support\Carbon::createFromTimestampUTC($torrent['created_at'])->toRssString() }}</pubDate>
|
||||
</item>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<tr x-cloak x-show="isToggledOn">
|
||||
<td style="padding: 0 0 0 24px">
|
||||
<a
|
||||
href="{{ route('staff.peers.index', ['agent' => $prefix]) }}"
|
||||
href="{{ route('staff.peers.index', ['agent' => $client['agent']]) }}"
|
||||
>
|
||||
{{ $client['agent'] }}
|
||||
</a>
|
||||
|
||||
@@ -256,6 +256,15 @@
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
@else
|
||||
<form action="{{ route('tickets.reopen', ['ticket' => $ticket]) }}" method="POST">
|
||||
<p class="form__group form__group--horizontal">
|
||||
@csrf
|
||||
<button class="form__button form__button--filled form__button--centered">
|
||||
{{ __('ticket.reopen') }}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
route('torrents.create', [
|
||||
'category_id' => $category->id,
|
||||
'title' => rawurlencode(($meta?->title ?? '') . ' ' . substr($meta->release_date ?? '', 0, 4) ?? ''),
|
||||
'imdb' => $torrent->imdb ?? '',
|
||||
'imdb' => $torrent->imdb ?? '' ?: $meta->imdb_id ?? '' ?: '',
|
||||
'tmdb' => $meta?->id ?? '',
|
||||
'mal' => $torrent->mal ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '',
|
||||
@@ -49,7 +49,7 @@
|
||||
route('requests.create', [
|
||||
'category_id' => $category->id,
|
||||
'title' => rawurlencode(($meta?->title ?? '') . ' ' . substr($meta->release_date ?? '', 0, 4) ?? ''),
|
||||
'imdb' => $torrent->imdb ?? '',
|
||||
'imdb' => $torrent->imdb ?? '' ?: $meta->imdb_id ?? '' ?: '',
|
||||
'tmdb' => $meta?->id ?? '',
|
||||
'mal' => $torrent->mal ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '',
|
||||
@@ -201,6 +201,7 @@
|
||||
class="meta-chip__image"
|
||||
src="{{ tmdb_image('cast_face', $credit->person->still) }}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
@else
|
||||
<i
|
||||
@@ -226,6 +227,7 @@
|
||||
class="meta-chip__image"
|
||||
src="{{ tmdb_image('cast_face', $credit->person->still) }}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
@else
|
||||
<i
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
route('torrents.create', [
|
||||
'category_id' => $category->id,
|
||||
'title' => rawurlencode(($meta?->name ?? '') . ' ' . substr($meta->first_air_date ?? '', 0, 4) ?? ''),
|
||||
'imdb' => $torrent->imdb ?? '',
|
||||
'imdb' => $torrent->imdb ?? '' ?: $meta->imdb_id ?? '' ?: '',
|
||||
'tmdb' => $meta?->id ?? '',
|
||||
'mal' => $torrent->mal ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '' ?: $meta->tvdb_id ?? '' ?: '',
|
||||
'igdb' => $torrent->igdb ?? '',
|
||||
])
|
||||
}}"
|
||||
@@ -49,10 +49,10 @@
|
||||
route('requests.create', [
|
||||
'category_id' => $category->id,
|
||||
'title' => rawurlencode(($meta?->name ?? '') . ' ' . substr($meta->first_air_date ?? '', 0, 4) ?? ''),
|
||||
'imdb' => $torrent->imdb ?? '',
|
||||
'imdb' => $torrent->imdb ?? '' ?: $meta->imdb_id ?? '' ?: '',
|
||||
'tmdb' => $meta?->id ?? '',
|
||||
'mal' => $torrent->mal ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '',
|
||||
'tvdb' => $torrent->tvdb ?? '' ?: $meta->tvdb_id ?? '' ?: '',
|
||||
'igdb' => $torrent->igdb ?? '',
|
||||
])
|
||||
}}"
|
||||
@@ -138,18 +138,18 @@
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if (($torrent->tvdb ?? 0) > 0)
|
||||
@foreach (array_unique(array_filter([(int) ($meta->tvdb_id ?? 0), $torrent->tvdb ?? 0])) as $tvdbId)
|
||||
<li class="meta__tvdb">
|
||||
<a
|
||||
class="meta-id-tag"
|
||||
href="https://www.thetvdb.com/?tab=series&id={{ $torrent->tvdb }}"
|
||||
title="The TV Database: {{ $torrent->tvdb }}"
|
||||
href="https://www.thetvdb.com/?tab=series&id={{ $tvdbId }}"
|
||||
title="The TV Database: {{ $tvdbId }}"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="{{ url('/img/meta/tvdb.svg') }}" />
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
@if (($meta->id ?? 0) > 0)
|
||||
<li class="meta__rotten">
|
||||
@@ -200,6 +200,7 @@
|
||||
class="meta-chip__image"
|
||||
src="{{ tmdb_image('cast_face', $credit->person->still) }}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
@else
|
||||
<i
|
||||
@@ -225,6 +226,7 @@
|
||||
class="meta-chip__image"
|
||||
src="{{ tmdb_image('cast_face', $credit->person->still) }}"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
@else
|
||||
<i
|
||||
|
||||
@@ -112,7 +112,9 @@
|
||||
}
|
||||
@endphp
|
||||
|
||||
<td>@choice('user.client-connectable-state', $connectable)</td>
|
||||
<td class="{{ $connectable ? 'text-green' : 'text-red' }}">
|
||||
@choice('user.client-connectable-state', $connectable)
|
||||
</td>
|
||||
@endif
|
||||
|
||||
<td>
|
||||
|
||||
@@ -872,17 +872,17 @@
|
||||
@if (config('announce.external_tracker.is_enabled') && auth()->user()->group->is_modo)
|
||||
@if ($externalUser === true)
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">{{ __('torrent.torrent') }}</h2>
|
||||
<h2 class="panel__heading">External Tracker</h2>
|
||||
<div class="panel__body">External tracker not enabled.</div>
|
||||
</section>
|
||||
@elseif ($externalUser === false)
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">{{ __('torrent.torrent') }}</h2>
|
||||
<h2 class="panel__heading">External Tracker</h2>
|
||||
<div class="panel__body">User not found.</div>
|
||||
</section>
|
||||
@elseif ($externalUser === [])
|
||||
<section class="panelV2">
|
||||
<h2 class="panel__heading">{{ __('torrent.torrent') }}</h2>
|
||||
<h2 class="panel__heading">External Tracker</h2>
|
||||
<div class="panel__body">Tracker returned an error.</div>
|
||||
</section>
|
||||
@else
|
||||
@@ -934,6 +934,58 @@
|
||||
<dt>{{ __('user.total-leeching') }}</dt>
|
||||
<dd>{{ $externalUser['num_leeching'] }}</dd>
|
||||
</div>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Seed lists</th>
|
||||
<th>Window</th>
|
||||
<th>Max</th>
|
||||
<th>Lists/h</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($externalUser['receive_seed_list_rates']['rates'] as $rate)
|
||||
<tr>
|
||||
<td
|
||||
title="Updated at: {{ $lastUpdatedAt = \Illuminate\Support\Carbon::createFromTimestampUTC($rate['updated_at']) }} ({{ $lastUpdatedAt->diffForHumans() }})"
|
||||
>
|
||||
{{ \number_format($rate['count'], 2, null, "\u{202F}") }}
|
||||
</td>
|
||||
<td>{{ $rate['window'] }}</td>
|
||||
<td>{{ $rate['max_count'] }}</td>
|
||||
<td>
|
||||
{{ \number_format((3600 * $rate['count']) / $rate['window'], 1, null, "\u{202F}") }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Leech lists</th>
|
||||
<th>Window</th>
|
||||
<th>Max</th>
|
||||
<th>Lists/h</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($externalUser['receive_leech_list_rates']['rates'] as $rate)
|
||||
<tr>
|
||||
<td
|
||||
title="Updated at: {{ $lastUpdatedAt = \Illuminate\Support\Carbon::createFromTimestampUTC($rate['updated_at']) }} ({{ $lastUpdatedAt->diffForHumans() }})"
|
||||
>
|
||||
{{ \number_format($rate['count'], 2, null, "\u{202F}") }}
|
||||
</td>
|
||||
<td>{{ $rate['window'] }}</td>
|
||||
<td>{{ $rate['max_count'] }}</td>
|
||||
<td>
|
||||
{{ \number_format((3600 * $rate['count']) / $rate['window'], 1, null, "\u{202F}") }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</dl>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
+47
-46
@@ -119,16 +119,16 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\RssController::class, 'index'])->name('index');
|
||||
Route::get('/create', [App\Http\Controllers\RssController::class, 'create'])->name('create');
|
||||
Route::post('/store', [App\Http\Controllers\RssController::class, 'store'])->name('store');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\RssController::class, 'edit'])->name('edit');
|
||||
Route::patch('/{id}/update', [App\Http\Controllers\RssController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}/destroy', [App\Http\Controllers\RssController::class, 'destroy'])->name('destroy');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\RssController::class, 'edit'])->name('edit')->whereNumber('id');
|
||||
Route::patch('/{id}/update', [App\Http\Controllers\RssController::class, 'update'])->name('update')->whereNumber('id');
|
||||
Route::delete('/{id}/destroy', [App\Http\Controllers\RssController::class, 'destroy'])->name('destroy')->whereNumber('id');
|
||||
});
|
||||
});
|
||||
|
||||
// Reports System
|
||||
Route::prefix('reports')->group(function (): void {
|
||||
Route::post('/torrent/{id}', [App\Http\Controllers\ReportController::class, 'torrent'])->name('report_torrent');
|
||||
Route::post('/request/{id}', [App\Http\Controllers\ReportController::class, 'request'])->name('report_request');
|
||||
Route::post('/torrent/{id}', [App\Http\Controllers\ReportController::class, 'torrent'])->name('report_torrent')->whereNumber('id');
|
||||
Route::post('/request/{id}', [App\Http\Controllers\ReportController::class, 'request'])->name('report_request')->whereNumber('id');
|
||||
Route::post('/user/{username}', [App\Http\Controllers\ReportController::class, 'user'])->name('report_user');
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::get('/internal', [App\Http\Controllers\PageController::class, 'internal'])->name('internal');
|
||||
Route::get('/blacklist/clients', [App\Http\Controllers\PageController::class, 'clientblacklist'])->name('client_blacklist');
|
||||
Route::get('/aboutus', [App\Http\Controllers\PageController::class, 'about'])->name('about');
|
||||
Route::get('/{page}', [App\Http\Controllers\PageController::class, 'show'])->where('id', '[0-9]+')->name('pages.show');
|
||||
Route::get('/{page}', [App\Http\Controllers\PageController::class, 'show'])->name('pages.show');
|
||||
});
|
||||
|
||||
// Wiki System
|
||||
@@ -176,7 +176,7 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::get('/torrent/dead', [App\Http\Controllers\StatsController::class, 'dead'])->name('dead');
|
||||
Route::get('/request/bountied', [App\Http\Controllers\StatsController::class, 'bountied'])->name('bountied');
|
||||
Route::get('/groups', [App\Http\Controllers\StatsController::class, 'groups'])->name('groups');
|
||||
Route::get('/groups/group/{id}', [App\Http\Controllers\StatsController::class, 'group'])->name('group');
|
||||
Route::get('/groups/group/{id}', [App\Http\Controllers\StatsController::class, 'group'])->name('group')->whereNumber('id');
|
||||
Route::get('/groups/requirements', [App\Http\Controllers\StatsController::class, 'groupsRequirements'])->name('groups_requirements');
|
||||
Route::get('/languages', [App\Http\Controllers\StatsController::class, 'languages'])->name('languages');
|
||||
Route::get('/themes', [App\Http\Controllers\StatsController::class, 'themes'])->name('themes');
|
||||
@@ -225,32 +225,32 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\TorrentController::class, 'index'])->name('index');
|
||||
Route::get('/create', [App\Http\Controllers\TorrentController::class, 'create'])->name('create');
|
||||
Route::post('/', [App\Http\Controllers\TorrentController::class, 'store'])->name('store');
|
||||
Route::get('/{id}{hash?}', [App\Http\Controllers\TorrentController::class, 'show'])->name('show');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\TorrentController::class, 'edit'])->name('edit');
|
||||
Route::patch('/{id}', [App\Http\Controllers\TorrentController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}', [App\Http\Controllers\TorrentController::class, 'destroy'])->name('destroy');
|
||||
Route::get('/{id}{hash?}', [App\Http\Controllers\TorrentController::class, 'show'])->name('show')->whereNumber('id');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\TorrentController::class, 'edit'])->name('edit')->whereNumber('id');
|
||||
Route::patch('/{id}', [App\Http\Controllers\TorrentController::class, 'update'])->name('update')->whereNumber('id');
|
||||
Route::delete('/{id}', [App\Http\Controllers\TorrentController::class, 'destroy'])->name('destroy')->whereNumber('id');
|
||||
});
|
||||
|
||||
Route::prefix('torrents')->group(function (): void {
|
||||
Route::get('/{id}/peers', [App\Http\Controllers\TorrentPeerController::class, 'index'])->name('peers');
|
||||
Route::get('/{id}/history', [App\Http\Controllers\TorrentHistoryController::class, 'index'])->name('history');
|
||||
Route::get('/{id}/external-tracker', [App\Http\Controllers\ExternalTorrentController::class, 'show'])->name('torrents.external_tracker')->middleware('modo');
|
||||
Route::get('/download_check/{id}', [App\Http\Controllers\TorrentDownloadController::class, 'show'])->name('download_check');
|
||||
Route::get('/download/{id}', [App\Http\Controllers\TorrentDownloadController::class, 'store'])->name('download');
|
||||
Route::post('/{id}/reseed', [App\Http\Controllers\ReseedController::class, 'store'])->name('reseed');
|
||||
Route::get('/similar/{category_id}.{tmdb}', [App\Http\Controllers\SimilarTorrentController::class, 'show'])->name('torrents.similar');
|
||||
Route::get('/{id}/peers', [App\Http\Controllers\TorrentPeerController::class, 'index'])->name('peers')->whereNumber('id');
|
||||
Route::get('/{id}/history', [App\Http\Controllers\TorrentHistoryController::class, 'index'])->name('history')->whereNumber('id');
|
||||
Route::get('/{id}/external-tracker', [App\Http\Controllers\ExternalTorrentController::class, 'show'])->name('torrents.external_tracker')->whereNumber('id')->middleware('modo');
|
||||
Route::get('/download_check/{id}', [App\Http\Controllers\TorrentDownloadController::class, 'show'])->name('download_check')->whereNumber('id');
|
||||
Route::get('/download/{id}', [App\Http\Controllers\TorrentDownloadController::class, 'store'])->name('download')->whereNumber('id');
|
||||
Route::post('/{id}/reseed', [App\Http\Controllers\ReseedController::class, 'store'])->name('reseed')->whereNumber('id');
|
||||
Route::get('/similar/{category_id}.{tmdb}', [App\Http\Controllers\SimilarTorrentController::class, 'show'])->name('torrents.similar')->whereNumber('category_id');
|
||||
Route::patch('/similar/{category}.{tmdbId}', [App\Http\Controllers\SimilarTorrentController::class, 'update'])->name('torrents.similar.update');
|
||||
});
|
||||
|
||||
Route::prefix('torrent')->group(function (): void {
|
||||
Route::post('/{id}/torrent_fl', [App\Http\Controllers\TorrentBuffController::class, 'grantFL'])->name('torrent_fl');
|
||||
Route::post('/{id}/torrent_doubleup', [App\Http\Controllers\TorrentBuffController::class, 'grantDoubleUp'])->name('torrent_doubleup');
|
||||
Route::post('/{id}/bumpTorrent', [App\Http\Controllers\TorrentBuffController::class, 'bumpTorrent'])->name('bumpTorrent');
|
||||
Route::post('/{id}/torrent_sticky', [App\Http\Controllers\TorrentBuffController::class, 'sticky'])->name('torrent_sticky');
|
||||
Route::post('/{id}/torrent_feature', [App\Http\Controllers\TorrentBuffController::class, 'grantFeatured'])->name('torrent_feature');
|
||||
Route::post('/{id}/torrent_revokefeature', [App\Http\Controllers\TorrentBuffController::class, 'revokeFeatured'])->name('torrent_revokefeature');
|
||||
Route::post('/{id}/freeleech_token', [App\Http\Controllers\TorrentBuffController::class, 'freeleechToken'])->name('freeleech_token');
|
||||
Route::post('/{id}/refundable', [App\Http\Controllers\TorrentBuffController::class, 'setRefundable'])->name('refundable');
|
||||
Route::post('/{id}/torrent_fl', [App\Http\Controllers\TorrentBuffController::class, 'grantFL'])->name('torrent_fl')->whereNumber('id');
|
||||
Route::post('/{id}/torrent_doubleup', [App\Http\Controllers\TorrentBuffController::class, 'grantDoubleUp'])->name('torrent_doubleup')->whereNumber('id');
|
||||
Route::post('/{id}/bumpTorrent', [App\Http\Controllers\TorrentBuffController::class, 'bumpTorrent'])->name('bumpTorrent')->whereNumber('id');
|
||||
Route::post('/{id}/torrent_sticky', [App\Http\Controllers\TorrentBuffController::class, 'sticky'])->name('torrent_sticky')->whereNumber('id');
|
||||
Route::post('/{id}/torrent_feature', [App\Http\Controllers\TorrentBuffController::class, 'grantFeatured'])->name('torrent_feature')->whereNumber('id');
|
||||
Route::post('/{id}/torrent_revokefeature', [App\Http\Controllers\TorrentBuffController::class, 'revokeFeatured'])->name('torrent_revokefeature')->whereNumber('id');
|
||||
Route::post('/{id}/freeleech_token', [App\Http\Controllers\TorrentBuffController::class, 'freeleechToken'])->name('freeleech_token')->whereNumber('id');
|
||||
Route::post('/{id}/refundable', [App\Http\Controllers\TorrentBuffController::class, 'setRefundable'])->name('refundable')->whereNumber('id');
|
||||
});
|
||||
|
||||
Route::prefix('torrent')->name('torrent.trump.')->group(function (): void {
|
||||
@@ -328,6 +328,7 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::post('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'store'])->name('assignee.store');
|
||||
Route::delete('/{ticket}/assignee', [App\Http\Controllers\TicketAssigneeController::class, 'destroy'])->name('assignee.destroy');
|
||||
Route::post('/{ticket}/close', [App\Http\Controllers\TicketController::class, 'close'])->name('close');
|
||||
Route::post('/{ticket}/reopen', [App\Http\Controllers\TicketController::class, 'reopen'])->name('reopen');
|
||||
Route::post('/{ticket}/attachments/{attachment}/download', [App\Http\Controllers\TicketAttachmentController::class, 'download'])->name('attachment.download');
|
||||
})->scopeBindings();
|
||||
});
|
||||
@@ -351,9 +352,9 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::get('/networks', [App\Http\Controllers\MediaHub\NetworkController::class, 'index'])->name('mediahub.networks.index');
|
||||
Route::get('/companies', [App\Http\Controllers\MediaHub\CompanyController::class, 'index'])->name('mediahub.companies.index');
|
||||
Route::get('/persons', [App\Http\Controllers\MediaHub\PersonController::class, 'index'])->name('mediahub.persons.index');
|
||||
Route::get('/persons/{id}', [App\Http\Controllers\MediaHub\PersonController::class, 'show'])->name('mediahub.persons.show');
|
||||
Route::get('/persons/{id}', [App\Http\Controllers\MediaHub\PersonController::class, 'show'])->name('mediahub.persons.show')->whereNumber('id');
|
||||
Route::get('/collections', [App\Http\Controllers\MediaHub\CollectionController::class, 'index'])->name('mediahub.collections.index');
|
||||
Route::get('/collections/{id}', [App\Http\Controllers\MediaHub\CollectionController::class, 'show'])->name('mediahub.collections.show');
|
||||
Route::get('/collections/{id}', [App\Http\Controllers\MediaHub\CollectionController::class, 'show'])->name('mediahub.collections.show')->whereNumber('id');
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -365,12 +366,12 @@ Route::middleware('language')->group(function (): void {
|
||||
// Forum System
|
||||
Route::name('forums.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\ForumController::class, 'index'])->name('index');
|
||||
Route::get('/{id}', [App\Http\Controllers\ForumController::class, 'show'])->where('id', '[0-9]+')->name('show');
|
||||
Route::get('/{id}', [App\Http\Controllers\ForumController::class, 'show'])->name('show')->whereNumber('id');
|
||||
});
|
||||
|
||||
// Forum Category System
|
||||
Route::prefix('categories')->name('forums.categories.')->group(function (): void {
|
||||
Route::get('/{id}', [App\Http\Controllers\ForumCategoryController::class, 'show'])->where('id', '[0-9]+')->name('show');
|
||||
Route::get('/{id}', [App\Http\Controllers\ForumCategoryController::class, 'show'])->name('show')->whereNumber('id');
|
||||
});
|
||||
|
||||
// Posts System
|
||||
@@ -385,18 +386,18 @@ Route::middleware('language')->group(function (): void {
|
||||
//Topics System
|
||||
Route::prefix('topics')->name('topics.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\TopicController::class, 'index'])->name('index');
|
||||
Route::get('/forum/{id}/create', [App\Http\Controllers\TopicController::class, 'create'])->name('create');
|
||||
Route::post('/forum/{id}', [App\Http\Controllers\TopicController::class, 'store'])->name('store');
|
||||
Route::get('/{topicId}/posts/{postId}', [App\Http\Controllers\TopicController::class, 'permalink'])->name('permalink');
|
||||
Route::get('/{id}/latest', [App\Http\Controllers\TopicController::class, 'latestPermalink'])->name('latestPermalink');
|
||||
Route::get('/{id}', [App\Http\Controllers\TopicController::class, 'show'])->name('show');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\TopicController::class, 'edit'])->name('edit');
|
||||
Route::patch('/{id}', [App\Http\Controllers\TopicController::class, 'update'])->name('update');
|
||||
Route::delete('/{id}', [App\Http\Controllers\TopicController::class, 'destroy'])->name('destroy')->middleware('modo');
|
||||
Route::post('/{id}/close', [App\Http\Controllers\TopicController::class, 'close'])->name('close')->middleware('modo');
|
||||
Route::post('/{id}/open', [App\Http\Controllers\TopicController::class, 'open'])->name('open')->middleware('modo');
|
||||
Route::post('/{id}/pin', [App\Http\Controllers\TopicController::class, 'pin'])->name('pin')->middleware('modo');
|
||||
Route::post('/{id}/unpin', [App\Http\Controllers\TopicController::class, 'unpin'])->name('unpin')->middleware('modo');
|
||||
Route::get('/forum/{id}/create', [App\Http\Controllers\TopicController::class, 'create'])->name('create')->whereNumber('id');
|
||||
Route::post('/forum/{id}', [App\Http\Controllers\TopicController::class, 'store'])->name('store')->whereNumber('id');
|
||||
Route::get('/{topicId}/posts/{postId}', [App\Http\Controllers\TopicController::class, 'permalink'])->name('permalink')->whereNumber(['topicId', 'postId']);
|
||||
Route::get('/{id}/latest', [App\Http\Controllers\TopicController::class, 'latestPermalink'])->name('latestPermalink')->whereNumber('id');
|
||||
Route::get('/{id}', [App\Http\Controllers\TopicController::class, 'show'])->name('show')->whereNumber('id');
|
||||
Route::get('/{id}/edit', [App\Http\Controllers\TopicController::class, 'edit'])->name('edit')->whereNumber('id');
|
||||
Route::patch('/{id}', [App\Http\Controllers\TopicController::class, 'update'])->name('update')->whereNumber('id');
|
||||
Route::delete('/{id}', [App\Http\Controllers\TopicController::class, 'destroy'])->name('destroy')->whereNumber('id')->middleware('modo');
|
||||
Route::post('/{id}/close', [App\Http\Controllers\TopicController::class, 'close'])->name('close')->whereNumber('id')->middleware('modo');
|
||||
Route::post('/{id}/open', [App\Http\Controllers\TopicController::class, 'open'])->name('open')->whereNumber('id')->middleware('modo');
|
||||
Route::post('/{id}/pin', [App\Http\Controllers\TopicController::class, 'pin'])->name('pin')->whereNumber('id')->middleware('modo');
|
||||
Route::post('/{id}/unpin', [App\Http\Controllers\TopicController::class, 'unpin'])->name('unpin')->whereNumber('id')->middleware('modo');
|
||||
});
|
||||
|
||||
// Topic Label System
|
||||
@@ -666,9 +667,9 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::prefix('applications')->group(function (): void {
|
||||
Route::name('applications.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\Staff\ApplicationController::class, 'index'])->name('index');
|
||||
Route::get('/{id}', [App\Http\Controllers\Staff\ApplicationController::class, 'show'])->where('id', '[0-9]+')->name('show');
|
||||
Route::post('/{id}/approve', [App\Http\Controllers\Staff\ApplicationController::class, 'approve'])->name('approve');
|
||||
Route::post('/{id}/reject', [App\Http\Controllers\Staff\ApplicationController::class, 'reject'])->name('reject');
|
||||
Route::get('/{id}', [App\Http\Controllers\Staff\ApplicationController::class, 'show'])->name('show')->whereNumber('id');
|
||||
Route::post('/{id}/approve', [App\Http\Controllers\Staff\ApplicationController::class, 'approve'])->name('approve')->whereNumber('id');
|
||||
Route::post('/{id}/reject', [App\Http\Controllers\Staff\ApplicationController::class, 'reject'])->name('reject')->whereNumber('id');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -959,7 +960,7 @@ Route::middleware('language')->group(function (): void {
|
||||
Route::prefix('moderation')->group(function (): void {
|
||||
Route::name('moderation.')->group(function (): void {
|
||||
Route::get('/', [App\Http\Controllers\Staff\ModerationController::class, 'index'])->name('index');
|
||||
Route::post('/{id}/update', [App\Http\Controllers\Staff\ModerationController::class, 'update'])->name('update');
|
||||
Route::post('/{id}/update', [App\Http\Controllers\Staff\ModerationController::class, 'update'])->name('update')->whereNumber('id');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ test('index returns an ok response', function (): void {
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('home.index');
|
||||
$response->assertViewHas('user');
|
||||
$response->assertViewHas('personal_freeleech');
|
||||
$response->assertViewHas('users');
|
||||
$response->assertViewHas('groups');
|
||||
$response->assertViewHas('articles');
|
||||
@@ -31,6 +30,4 @@ test('index returns an ok response', function (): void {
|
||||
$response->assertViewHas('posts');
|
||||
$response->assertViewHas('featured');
|
||||
$response->assertViewHas('poll');
|
||||
$response->assertViewHas('freeleech_tokens');
|
||||
$response->assertViewHas('bookmarks');
|
||||
});
|
||||
|
||||
@@ -14,8 +14,10 @@ declare(strict_types=1);
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
use App\Models\Group;
|
||||
use App\Models\Invite;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
test('create returns an ok response', function (): void {
|
||||
$this->markTestIncomplete('This test case was generated by Shift. When you are ready, remove this line and complete this test case.');
|
||||
@@ -33,16 +35,25 @@ test('create returns an ok response', function (): void {
|
||||
});
|
||||
|
||||
test('create aborts with a 403', function (): void {
|
||||
$this->markTestIncomplete('This test case was generated by Shift. When you are ready, remove this line and complete this test case.');
|
||||
$group = Group::factory()->create([]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$authUser = User::factory()->create();
|
||||
$user = User::factory()->create([
|
||||
'group_id' => $group->id,
|
||||
'can_invite' => 0,
|
||||
'invites' => 1,
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
// TODO: perform additional setup to trigger `abort_unless(403)`...
|
||||
$response = $this->actingAs($user)->get(route('users.invites.create', [$user]), []);
|
||||
|
||||
$response = $this->actingAs($authUser)->get(route('users.invites.create', [$user]));
|
||||
|
||||
$response->assertForbidden();
|
||||
$response->assertRedirect(route('home.index'));
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'invites' => 1,
|
||||
]);
|
||||
$this->assertDatabaseMissing('invites', [
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
});
|
||||
|
||||
test('destroy returns an ok response', function (): void {
|
||||
@@ -136,18 +147,38 @@ test('send aborts with a 403', function (): void {
|
||||
});
|
||||
|
||||
test('store returns an ok response', function (): void {
|
||||
$this->markTestIncomplete('This test case was generated by Shift. When you are ready, remove this line and complete this test case.');
|
||||
$group = Group::factory()->create([]);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$authUser = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($authUser)->post(route('users.invites.store', [$user]), [
|
||||
// TODO: send request data
|
||||
$user = User::factory()->create([
|
||||
'group_id' => $group->id,
|
||||
'can_invite' => 1,
|
||||
'invites' => 1,
|
||||
'two_factor_confirmed_at' => now(),
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$inviteEmail = 'test@unit3d.dev';
|
||||
|
||||
// TODO: perform additional assertions
|
||||
config(['other.invites_restriced' => true]);
|
||||
config(['other.invite_groups' => [$group->name]]);
|
||||
config(['other.invite_groups' => [$group->name]]);
|
||||
config(['email-blacklist.enabled' => false]);
|
||||
|
||||
Mail::fake();
|
||||
|
||||
$response = $this->actingAs($user)->post(route('users.invites.store', [$user]), [
|
||||
'email' => $inviteEmail,
|
||||
'message' => 'Test Invite',
|
||||
]);
|
||||
|
||||
$response->assertRedirect();
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'invites' => 0,
|
||||
]);
|
||||
$this->assertDatabaseHas('invites', [
|
||||
'user_id' => $user->id,
|
||||
'email' => $inviteEmail,
|
||||
]);
|
||||
});
|
||||
|
||||
test('store aborts with a 403', function (): void {
|
||||
|
||||
Reference in New Issue
Block a user