refactor: wishlist and add movie/tv subscriptions

closes #3826
This commit is contained in:
Roardom
2024-05-24 11:15:00 +00:00
parent af49785734
commit 0df196f077
10 changed files with 340 additions and 75 deletions
+16 -14
View File
@@ -28,13 +28,12 @@ use App\Achievements\UserMadeUpload;
use App\Bots\IRCAnnounceBot;
use App\Models\AutomaticTorrentFreeleech;
use App\Models\Movie;
use App\Models\PrivateMessage;
use App\Models\Scopes\ApprovedScope;
use App\Models\Torrent;
use App\Models\Tv;
use App\Models\User;
use App\Models\Wish;
use App\Notifications\NewUpload;
use App\Notifications\NewWishListNotice;
use App\Services\Unit3dAnnounce;
use Illuminate\Support\Carbon;
@@ -74,20 +73,23 @@ class TorrentHelper
$uploader = $torrent->user;
$wishes = Wish::where('tmdb', '=', $torrent->tmdb)->whereNull('source')->get();
switch (true) {
case $torrent->category->movie_meta:
User::query()
->whereHas('wishes', fn ($query) => $query->where('movie_id', '=', $torrent->tmdb))
->get()
->each
->notify(new NewWishListNotice($torrent));
foreach ($wishes as $wish) {
$wish->source = sprintf('%s/torrents/%s', $appurl, $torrent->id);
$wish->save();
break;
case $torrent->category->tv_meta:
User::query()
->whereHas('wishes', fn ($query) => $query->where('tv_id', '=', $torrent->tmdb))
->get()
->each
->notify(new NewWishListNotice($torrent));
// Send Private Message
$pm = new PrivateMessage();
$pm->sender_id = User::SYSTEM_USER_ID;
$pm->receiver_id = $wish->user_id;
$pm->subject = 'Wish List Notice!';
$pm->message = sprintf('The following item, %s, from your wishlist has been uploaded to %s! You can view it [url=%s/torrents/', $wish->title, $appname, $appurl).$torrent->id.'] HERE [/url]
[color=red][b]THIS IS AN AUTOMATED SYSTEM MESSAGE, PLEASE DO NOT REPLY![/b][/color]';
$pm->save();
break;
}
if ($torrent->anon == 0) {
+41 -35
View File
@@ -14,14 +14,12 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\Category;
use App\Models\Torrent;
use App\Http\Requests\StoreWishRequest;
use App\Models\User;
use App\Models\Wish;
use App\Services\Tmdb\Client\Movie;
use Illuminate\Database\Query\Builder;
use App\Services\Tmdb\Client\TV;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use JsonException;
/**
@@ -38,8 +36,11 @@ class WishController extends Controller
return view('user.wish.index', [
'user' => $user,
'wishes' => $user->wishes()->latest()->paginate(25),
'route' => 'wish',
'wishes' => $user->wishes()
->withCount(['movieTorrents', 'tvTorrents'])
->latest()
->paginate(25),
'route' => 'wish',
]);
}
@@ -48,42 +49,47 @@ class WishController extends Controller
*
* @throws JsonException
*/
public function store(Request $request, User $user): \Illuminate\Http\RedirectResponse
public function store(StoreWishRequest $request, User $user): \Illuminate\Http\RedirectResponse
{
abort_unless($request->user()->is($user), 403);
$request->validate([
'tmdb' => [
'required',
'integer',
'not_in:0',
Rule::unique('wishes')->where(fn (Builder $query) => $query->where('user_id', '=', $user->id)),
],
''
]);
switch ($request->meta) {
case 'movie':
$meta = (new Movie($request->movie_id))->data;
$meta = (new Movie($request->tmdb))->data;
if ($meta === null) {
return to_route('users.wishes.index', ['user' => $user])
->withErrors('TMDB Bad Request!');
}
if ($meta === null) {
return to_route('users.wishes.index', ['user' => $user])
->withErrors('TMDM Bad Request!');
$title = $meta['title'].' ('.$meta['release_date'].')';
Wish::create([
'user_id' => $user->id,
'title' => $title,
'movie_id' => $request->movie_id,
]);
break;
case 'tv':
$meta = (new TV($request->tv_id))->data;
if ($meta === null) {
return to_route('users.wishes.index', ['user' => $user])
->withErrors('TMDB Bad Request!');
}
$title = $meta['name'].' ('.$meta['first_air_date'].')';
Wish::create([
'user_id' => $user->id,
'title' => $title,
'tv_id' => $request->tv_id,
]);
break;
}
$torrent = Torrent::query()
->where('tmdb', '=', $request->tmdb)
->whereIn('category_id', Category::select('id')->where('movie_meta', '=', 1))
->where('seeders', '>', 0)
->where('status', '=', 1)
->first();
Wish::create([
'title' => $meta['title'].' ('.$meta['release_date'].')',
'type' => 'Movie',
'tmdb' => $request->tmdb,
'source' => $torrent === null ? Wish::find($request->integer('tmdb'))?->source : route('torrents.show', $torrent->id),
'user_id' => $user->id,
]);
return to_route('users.wishes.index', ['user' => $user])
->withSuccess('Wish Successfully Added!');
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Http\Requests;
use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class StoreWishRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, array<\Illuminate\Validation\Rules\Unique|string>>
*/
public function rules(Request $request): array
{
$user = auth()->user();
return [
'movie_id' => [
'required_if:meta,movie',
'decimal:0',
'min:1',
Rule::unique('wishes')->where(fn (Builder $query) => $query->where('user_id', '=', $user->id)),
],
'tv_id' => [
'required_if:meta,tv',
'decimal:0',
'min:1',
Rule::unique('wishes')->where(fn (Builder $query) => $query->where('user_id', '=', $user->id)),
],
'meta' => [
'required',
'in:movie,tv',
],
];
}
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
public function messages(): array
{
return [
'movie_id.unique' => 'You are already receiving notifications for this movie.',
'tv_id.unique' => 'You are already receiving notifications for this tv.',
];
}
}
+25 -7
View File
@@ -23,9 +23,8 @@ use Illuminate\Database\Eloquent\Model;
* @property int $id
* @property int $user_id
* @property string $title
* @property string $tmdb
* @property string $type
* @property string|null $source
* @property int $movie_id
* @property int $tv_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
*/
@@ -48,9 +47,28 @@ class Wish extends Model
*/
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'username' => 'System',
'id' => '1',
]);
return $this->belongsTo(User::class);
}
/**
* Has many torrents.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Torrent>
*/
public function movieTorrents(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Torrent::class, 'tmdb', 'movie_id')
->whereHas('category', fn ($query) => $query->where('movie_meta', '=', true));
}
/**
* Has many torrents.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany<Torrent>
*/
public function tvTorrents(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Torrent::class, 'tmdb', 'tv_id')
->whereHas('category', fn ($query) => $query->where('tv_meta', '=', true));
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author Roardom <roardom@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
namespace App\Notifications;
use App\Models\Torrent;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
class NewWishListNotice extends Notification implements ShouldQueue
{
use Queueable;
/**
* NewWishListNotice constructor.
*/
public function __construct(public Torrent $torrent)
{
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return ['database'];
}
/**
* Get the array representation of the notification.
*
* @return array<string, mixed>
*/
public function toArray(object $notifiable): array
{
return [
'title' => 'Wish List Notice!',
'body' => $this->torrent->name.' from your wishlist has been uploaded',
'url' => '/torrents/'.$this->torrent->id,
];
}
}
+2 -2
View File
@@ -22,7 +22,7 @@ use Illuminate\Support\Str;
class TV
{
/**
* @var array{
* @var null|array{
* adult: ?bool,
* backdrop_path: ?string,
* created_by: ?array<
@@ -290,7 +290,7 @@ class TV
* }
* }
*/
public array $data;
public null|array $data;
public TMDB $tmdb;
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
public function up(): void
{
DB::table('wishes')->update([
'tmdb' => DB::raw("REGEXP_REPLACE(tmdb, 'tt', '')"),
]);
DB::table('wishes')
->where('tmdb', 'not regexp', '^[0-9]+$')
->delete();
DB::table('wishes')
->whereNull('tmdb')
->orWhere('tmdb', '<', 0)
->orWhere('tmdb', '>', 2_000_000_000)
->delete();
Schema::table('wishes', function (Blueprint $table): void {
$table->dropColumn(['source', 'type']);
$table->integer('tmdb')->unsigned()->nullable()->change();
$table->integer('tv_id')->unsigned()->nullable()->after('tmdb');
$table->renameColumn('tmdb', 'movie_id');
});
}
};
@@ -61,6 +61,22 @@
</a>
</li>
@if ($meta?->id)
<li>
<form
action="{{ route('users.wishes.store', ['user' => auth()->user()]) }}"
method="post"
>
@csrf
<input type="hidden" name="meta" value="movie" />
<input type="hidden" name="movie_id" value="{{ $meta->id }}" />
<button
style="cursor: pointer"
title="Receive notifications every time a new torrent is uploaded."
>
Notify of New Uploads
</button>
</form>
</li>
<li>
<form
action="{{ route('torrents.similar.update', ['category' => $category, 'tmdbId' => $meta->id]) }}"
@@ -74,6 +90,7 @@
disabled
title="This item was recently updated. Try again tomorrow."
@endif
style="cursor: pointer"
>
Update Metadata
</button>
@@ -61,6 +61,22 @@
</a>
</li>
@if ($meta?->id)
<li>
<form
action="{{ route('users.wishes.store', ['user' => auth()->user()]) }}"
method="post"
>
@csrf
<input type="hidden" name="meta" value="tv" />
<input type="hidden" name="movie_id" value="{{ $meta->id }}" />
<button
style="cursor: pointer"
title="Receive notifications every time a new torrent is uploaded."
>
Notify of New Uploads
</button>
</form>
</li>
<li>
<form
action="{{ route('torrents.similar.update', ['category' => $category, 'tmdbId' => $meta->id]) }}"
@@ -73,6 +89,7 @@
disabled
title="This item was recently updated. Try again tomorrow."
@endif
style="cursor: pointer"
>
Update Metadata
</button>
+64 -17
View File
@@ -23,17 +23,45 @@
<section class="panelV2">
<header class="panel__header">
<h2 class="panel__heading">{{ __('user.wishlist') }}</h2>
<div class="panel__actions">
<div class="panel__actions" x-data="{ meta: 'movie' }">
<div class="panel__action">
<div class="form__group">
<select
id="meta"
class="form__select"
form="wishlistForm"
name="meta"
x-model="meta"
required
>
<option selected value="movie">{{ __('mediahub.movie') }}</option>
<option value="tv">TV</option>
</select>
<label class="form__label form__label--floating" for="model">
{{ __('mediahub.movie') }}/TV
</label>
</div>
</div>
<div class="panel__action">
<div class="form__group">
<input
id="tmdb"
class="form__text"
form="wishlistForm"
x-bind:name="meta === 'movie' ? 'movie_id' : 'tv_id'"
type="text"
required
/>
<label class="form__label form__label--floating" for="tmdb">TMDB ID</label>
</div>
</div>
<form
id="wishlistForm"
class="form form--horizontal panel__action"
action="{{ route('users.wishes.store', ['user' => $user]) }}"
method="POST"
>
@csrf
<div class="form__group">
<input id="tmdb" class="form__text" name="tmdb" required type="text" />
<label class="form__label form__label--floating" for="tmdb">TMDB ID</label>
</div>
<button class="form__button form__button--text">
{{ __('common.add') }}
</button>
@@ -45,7 +73,7 @@
<thead>
<tr>
<th>{{ __('torrent.title') }}</th>
<th>TMDB</th>
<th>{{ __('torrent.torrents') }}</th>
<th>{{ __('common.status') }}</th>
<th>{{ __('common.actions') }}</th>
</tr>
@@ -62,21 +90,40 @@
</td>
<td>
<a
href="{{ route('torrents.index', ['tmdbId' => $wish->tmdb]) }}"
target="_blank"
href="{{ route('torrents.index', ['tmdbId' => $wish->movie_id ?? $wish->tv_id, 'view' => 'group']) }}"
>
Torrents
@if ($wish->movie_id !== null)
Torrents ({{ $wish->movie_torrents_count }})
@elseif ($wish->tv_id !== null)
Torrents ({{ $wish->tv_torrents_count }})
@endif
</a>
</td>
<td>
@if ($wish->source === null)
<i
class="{{ config('other.font-awesome') }} fa-times text-red"
></i>
@else
<i
class="{{ config('other.font-awesome') }} fa-check text-green"
></i>
@if ($wish->movie_id !== null)
@if ($wish->movie_torrents_count === 0)
<i
class="{{ config('other.font-awesome') }} fa-times text-red"
title="Not yet uploaded"
></i>
@else
<i
class="{{ config('other.font-awesome') }} fa-check text-green"
title="Already uploaded"
></i>
@endif
@elseif ($wish->tv_id !== null)
@if ($wish->tv_torrents_count === 0)
<i
class="{{ config('other.font-awesome') }} fa-times text-red"
title="Not yet uploaded"
></i>
@else
<i
class="{{ config('other.font-awesome') }} fa-check text-green"
title="Already uploaded"
></i>
@endif
@endif
</td>
<td>