Add ability for mods to edit episode & Refactor code

This commit is contained in:
2026-05-26 13:45:13 +02:00
parent 2c016274ab
commit a71b2976af
7 changed files with 123 additions and 47 deletions
@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Enums\UserRole;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Jobs\DiscordReleaseNotification; use App\Jobs\DiscordReleaseNotification;
use App\Models\Episode; use App\Models\Episode;
@@ -63,6 +64,17 @@ class EpisodeController extends Controller
public function update(Request $request): RedirectResponse public function update(Request $request): RedirectResponse
{ {
$episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail(); $episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
if ($request->user()->hasRole(UserRole::MODERATOR)) {
$this->episodeService->updateEpisodeModerator($request, $episode->id);
cache()->flush();
return to_route('hentai.index', [
'title' => $episode->slug,
]);
}
$studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value); $studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value);
$oldinterpolated = $episode->interpolated; $oldinterpolated = $episode->interpolated;
+48 -7
View File
@@ -5,6 +5,7 @@ namespace App\Services;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\Studios; use App\Models\Studios;
use App\Models\ModLog;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@@ -62,6 +63,34 @@ class EpisodeService
return $episode; return $episode;
} }
private function applyTags(Request $request, Episode $episode): void
{
$tags = json_decode($request->input('tags'));
$newtags = [];
foreach ($tags as $t) {
$newtags[] = $t->value;
}
$newTagsTemp = $newtags;
$oldTagsTemp = $episode->tagNames();
sort($newTagsTemp);
sort($oldTagsTemp);
if ($newTagsTemp !== $oldTagsTemp) {
ModLog::create([
'moderator' => $request->user()->name,
'data' => sprintf(
'Updated Episode tags from %s to %s',
implode(', ', $oldTagsTemp),
implode(', ', $newTagsTemp),
),
]);
}
$episode->retag($newtags);
}
public function updateEpisode(Request $request, Studios $studio, int $episodeId): Episode public function updateEpisode(Request $request, Studios $studio, int $episodeId): Episode
{ {
$episode = Episode::where('id', $episodeId)->firstOrFail(); $episode = Episode::where('id', $episodeId)->firstOrFail();
@@ -75,17 +104,29 @@ class EpisodeService
$episode->dmca_takedown = $request->input('dmca_takedown') == 'true'; $episode->dmca_takedown = $request->input('dmca_takedown') == 'true';
$episode->save(); $episode->save();
// Tagging $this->applyTags($request, $episode);
$tags = json_decode($request->input('tags'));
$newtags = [];
foreach ($tags as $t) {
$newtags[] = $t->value;
}
$episode->retag($newtags);
return $episode; return $episode;
} }
public function updateEpisodeModerator(Request $request, int $episodeId): void
{
$episode = Episode::where('id', $episodeId)->firstOrFail();
$oldDescription = $episode->description;
$episode->description = $request->input('description');
$episode->save();
if ($episode->description !== $oldDescription) {
// Log to ModLog
ModLog::create([
'moderator' => $request->user()->name,
'data' => "Updated Episode description from {$oldDescription} to {$episode->description}",
]);
}
$this->applyTags($request, $episode);
}
public function getOrCreateStudio(string $studioName): Studios public function getOrCreateStudio(string $studioName): Studios
{ {
return Studios::firstOrCreate( return Studios::firstOrCreate(
@@ -5,17 +5,18 @@
<!--Modal body--> <!--Modal body-->
<div class="relative p-4 pt-0"> <div class="relative p-4 pt-0">
<form method="POST" action="{{ route('admin.edit') }}" enctype="multipart/form-data"> <form method="POST" action="{{ route('admin.episode.edit') }}" enctype="multipart/form-data">
@csrf @csrf
<div class="grid grid-cols-3"> <div class="grid grid-cols-3">
<div class="col-span-2"> <div class="col-span-2">
<!-- Tags --> <!-- Tags -->
<div class="row-span-2 p-0"> <div class="row-span-2 p-0">
<label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="tags">Tags:</label> <label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="tags">Tags:</label>
<x-text-input id="tags" class="block w-full" type="text" name="tags" required /> <x-text-input id="tags" class="block w-full" type="text" name="tags" required />
</div> </div>
</div> </div>
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<div class="grid grid-rows-2"> <div class="grid grid-rows-2">
<!-- Studio --> <!-- Studio -->
<div class="p-2 pt-0"> <div class="p-2 pt-0">
@@ -47,13 +48,16 @@
</div> </div>
</div> </div>
</div> </div>
@endif
</div> </div>
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<!-- Stream URL --> <!-- Stream URL -->
<div class="p-2 pt-0"> <div class="p-2 pt-0">
<label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="baseurl">Stream:</label> <label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="baseurl">Stream:</label>
<x-text-input id="baseurl" class="block w-full" type="text" name="baseurl" value="{{ $episode->url }}" required /> <x-text-input id="baseurl" class="block w-full" type="text" name="baseurl" value="{{ $episode->url }}" required />
</div> </div>
@endif
<input name="episode_id" id="episode_id" type="hidden" value="{{ $episode->id }}" /> <input name="episode_id" id="episode_id" type="hidden" value="{{ $episode->id }}" />
@@ -62,6 +66,7 @@
<textarea rows="4" cols="50" id="description" name="description" class="block mt-1 w-full rounded-md border-gray-300 shadow-sm dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600" required>{{ $episode->description }}</textarea> <textarea rows="4" cols="50" id="description" name="description" class="block mt-1 w-full rounded-md border-gray-300 shadow-sm dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600" required>{{ $episode->description }}</textarea>
</div> </div>
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<!-- Episodes --> <!-- Episodes -->
<div class="grid grid-cols-2"> <div class="grid grid-cols-2">
<!-- Cover --> <!-- Cover -->
@@ -95,8 +100,10 @@
<label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="downloadUHDi1">Download 4k Interpolated:</label> <label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="downloadUHDi1">Download 4k Interpolated:</label>
<x-text-input id="downloadUHDi1" class="block w-full" type="text" name="downloadUHDi1" value="{{ $episode->getDownloadByType('UHDi')->url ?? '' }}" /> <x-text-input id="downloadUHDi1" class="block w-full" type="text" name="downloadUHDi1" value="{{ $episode->getDownloadByType('UHDi')->url ?? '' }}" />
</div> </div>
@endif
<div class="flex flex-wrap flex-shrink-0 justify-end items-center p-4 rounded-b-md"> <div class="sticky bottom-0 flex items-center justify-end gap-3 border-t border-neutral-200 bg-white/90 px-6 py-4 backdrop-blur dark:border-neutral-700 dark:bg-neutral-900/90">
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<div class="inline-block mr-2"> <div class="inline-block mr-2">
<input class="w-4 h-4 text-rose-600 bg-gray-100 border-gray-300 rounded focus:ring-rose-500 dark:focus:ring-rose-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" <input class="w-4 h-4 text-rose-600 bg-gray-100 border-gray-300 rounded focus:ring-rose-500 dark:focus:ring-rose-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
type="checkbox" value="true" id="v2" name="v2" /> type="checkbox" value="true" id="v2" name="v2" />
@@ -111,10 +118,17 @@
DMCA Takedown DMCA Takedown
</label> </label>
</div> </div>
<button type="button" class="inline-block px-6 pt-2.5 pb-2 text-xs font-medium leading-normal uppercase rounded transition duration-150 ease-in-out bg-primary-100 text-primary-700 hover:bg-primary-accent-100 focus:bg-primary-accent-100 focus:outline-none focus:ring-0 active:bg-primary-accent-200" data-te-modal-dismiss data-te-ripple-init data-te-ripple-color="light"> @endif
<button
type="button"
data-te-modal-dismiss
class="rounded-xl border border-neutral-300 px-5 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-100 dark:border-neutral-600 dark:text-neutral-200 dark:hover:bg-neutral-800">
Cancel Cancel
</button> </button>
<button type="submit" class="inline-block px-6 pt-2.5 pb-2 ml-1 text-xs font-medium leading-normal text-white uppercase bg-rose-600 rounded transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600" data-te-ripple-init data-te-ripple-color="light"> <button
type="submit"
data-te-ripple-init
class="rounded-xl bg-rose-600 px-5 py-2.5 text-sm font-semibold text-white shadow-lg shadow-rose-600/20 transition hover:bg-rose-700">
Edit Edit
</button> </button>
</div> </div>
-23
View File
@@ -1,23 +0,0 @@
@auth
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || Auth::user()->hasRole(\App\Enums\UserRole::MODERATOR))
<div class="relative p-5 bg-white dark:bg-neutral-700/40 rounded-lg overflow-hidden z-10">
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<div class="float-left">
<a data-te-toggle="modal" data-te-target="#modalUploadEpisode" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
<i class="fa-solid fa-plus pr-[6px]"></i> Add Episode
</a>
</div>
@endif
<div class="float-right">
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<a data-te-toggle="modal" data-te-target="#modalAddSubtitles" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
<i class="fa-solid fa-plus pr-[6px]"></i> Add Subtitles
</a>
@endif
<a data-te-toggle="modal" data-te-target="#modalEditEpisode" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
<i class="fa-solid fa-pen pr-[6px]"></i> Edit Episode
</a>
</div>
</div>
@endif
@endauth
+6 -7
View File
@@ -15,7 +15,6 @@
</div> </div>
@endif @endif
@include('admin.stream')
<!-- Infos --> <!-- Infos -->
@include('stream.partials.info') @include('stream.partials.info')
<!-- Comments --> <!-- Comments -->
@@ -38,18 +37,18 @@
@include('modals.add-to-playlist') @include('modals.add-to-playlist')
@include('modals.share') @include('modals.share')
@auth @auth
@if (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || Auth::user()->hasRole(\App\Enums\UserRole::MODERATOR))
@include('admin.modals.edit-episode')
@endif
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
@include('admin.modals.upload-episode') @include('admin.modals.upload-episode')
@include('admin.modals.add-subtitles') @include('admin.modals.add-subtitles')
@endif @endif
@if (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || auth()->user()->hasRole(\App\Enums\UserRole::MODERATOR))
@include('admin.modals.edit-episode')
@endif
@endauth @endauth
<!-- Player Script --> <!-- Player Script -->
@vite(['resources/js/player.js']) @vite(['resources/js/player.js'])
</x-app-layout> </x-app-layout>
@@ -148,6 +148,36 @@
</a> </a>
@endauth @endauth
</div> </div>
@if(auth()->check() && (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || auth()->user()->hasRole(\App\Enums\UserRole::MODERATOR)))
<div class="flex flex-wrap flex-row-reverse gap-2">
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
<a
data-te-toggle="modal"
data-te-target="#modalAddSubtitles"
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
<i class="fa-solid fa-plus"></i>
Add Subtitles
</a>
<a
data-te-toggle="modal"
data-te-target="#modalUploadEpisode"
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
<i class="fa-solid fa-plus"></i>
Add Episode
</a>
@endif
<a
data-te-toggle="modal"
data-te-target="#modalEditEpisode"
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
<i class="fa-solid fa-pen"></i>
Edit Episode
</a>
</div>
@endif
</div> </div>
@endif @endif
</div> </div>
+4 -1
View File
@@ -45,7 +45,7 @@ Route::group(['middleware' => ['auth', 'auth.admin']], function () {
// Episode // Episode
Route::post('/admin/episode/upload', [EpisodeController::class, 'store'])->name('admin.upload.episode'); Route::post('/admin/episode/upload', [EpisodeController::class, 'store'])->name('admin.upload.episode');
Route::post('/admin/episode/edit', [EpisodeController::class, 'update'])->name('admin.edit');
// Get Tags used for Upload Form // Get Tags used for Upload Form
Route::get('/admin/tags', [AdminApiController::class, 'getTags'])->name('admin.tags'); Route::get('/admin/tags', [AdminApiController::class, 'getTags'])->name('admin.tags');
@@ -66,4 +66,7 @@ Route::group(['middleware' => ['auth', 'auth.moderator']], function () {
// Get Tags for editing Episode // Get Tags for editing Episode
Route::get('/admin/tags/{episode_id}', [AdminApiController::class, 'getEpisodeTags'])->name('admin.tags.episode'); Route::get('/admin/tags/{episode_id}', [AdminApiController::class, 'getEpisodeTags'])->name('admin.tags.episode');
Route::get('/admin/studio/{episode_id}', [AdminApiController::class, 'getEpisodeStudio'])->name('admin.studio.episode'); Route::get('/admin/studio/{episode_id}', [AdminApiController::class, 'getEpisodeStudio'])->name('admin.studio.episode');
// Edit Episode
Route::post('/admin/episode/edit', [EpisodeController::class, 'update'])->name('admin.episode.edit');
}); });