Merge branch '8.x.x' into add-torrent-immune-button-to-ui

This commit is contained in:
Jay
2024-10-10 14:06:12 +00:00
committed by GitHub
67 changed files with 871 additions and 371 deletions
+4 -3
View File
@@ -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}");
}
/**
+1 -1
View File
@@ -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!');
}
+1 -1
View File
@@ -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!');
}
+96 -8
View File
@@ -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');
+2 -7
View File
@@ -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(),
]);
}
}
+4 -32
View File
@@ -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.');
}
}
+12 -9
View File
@@ -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,
]);
}
/**
+12
View File
@@ -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'));
}
}
+3
View File
@@ -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'])
)
+5 -7
View File
@@ -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);
}
+1 -1
View File
@@ -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);
+4
View File
@@ -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.
*
+4
View File
@@ -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.
*
+6
View File
@@ -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,
+10
View File
@@ -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.
*
-9
View File
@@ -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));
+8 -1
View File
@@ -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), '@');
+48 -10
View File
@@ -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
BIN
View File
Binary file not shown.
+1
View File
@@ -30,6 +30,7 @@ return [
'username' => 'UNIT3D',
'password' => 'UNIT3D',
'channel' => '#announce',
'channel_key' => 'UNIT3D',
'nickservpass' => false,
'joinchannel' => false,
];
+11 -1
View File
@@ -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
View File
@@ -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']);
});
}
};
+2
View File
@@ -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',
+2
View File
@@ -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",
-5
View File
@@ -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
+6
View File
@@ -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/**']);
+5 -66
View File
@@ -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.
*/
+3 -1
View File
@@ -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 {
+1
View File
@@ -77,6 +77,7 @@
display: inline-block;
box-sizing: border-box;
text-align: center;
height: 130px;
}
.playlists__playlist-image--none::before {
+12
View File
@@ -46,3 +46,15 @@
height: 40px;
border-radius: 10%;
}
/* Donation Charts */
.chart-wrapper {
display: flex;
justify-content: space-around;
gap: 20px;
& > div {
flex: 1;
}
}
+4
View File
@@ -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;
+5
View File
@@ -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;
+5
View File
@@ -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;
+5
View File
@@ -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;
+5 -5
View File
@@ -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>
+86 -3
View File
@@ -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({
+2 -2
View File
@@ -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>
+6 -3
View File
@@ -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 &copy;
{{ 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>
+35 -35
View File
@@ -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>
+9
View File
@@ -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
+3 -1
View File
@@ -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>
+55 -3
View File
@@ -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
View File
@@ -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 {