Files
hstream/app/Services/GalleryService.php
T
w33b 30a6b080eb feat(admin): rebuild release upload form with Livewire and CDN validation
Replace the server-rendered release upload form with a Livewire component,
introducing an interactive admin release form that supports dynamic episode
management, real‑time CDN path validation, and direct download file checks.

Key changes:
- New `AdminReleaseForm` Livewire component with dynamic episode fields,
  Tagify integration, and upload handling via Livewire's file uploads.
- Added `CdnPathValidator` service to verify download and stream paths across
  all configured mirrors, caching results and capturing file sizes.
- Extended `EpisodeService` and `GalleryService` to support array‑based data
  and temporary uploaded files from Livewire.
- Persist validated download sizes and store the validation timestamp on the
  `downloads` table via a new migration; also add a unique constraint on
  `hentais.slug` to prevent duplicates.
- Remove the old POST `/admin/release/upload` route and controller logic,
  along with the legacy `upload.js` script.
- Add comprehensive feature and unit tests covering the new component and the
  CDN validation service.
2026-08-08 13:18:57 +02:00

88 lines
3.1 KiB
PHP

<?php
namespace App\Services;
use App\Models\Episode;
use App\Models\Gallery;
use App\Models\Hentai;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Encoders\WebpEncoder;
use Intervention\Image\Laravel\Facades\Image;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
class GalleryService
{
public function createOrUpdateGallery(Request $request, Hentai $hentai, Episode $episode, int $episodeNumber, bool $override = false): void
{
$galleryInputNumber = $override ? 1 : $episodeNumber;
if ($request->hasFile('episodegallery'.$galleryInputNumber)) {
$this->saveGalleryFiles($hentai, $episode, $episodeNumber, $request->file('episodegallery'.$galleryInputNumber));
}
}
/**
* Save a gallery for the given episode from a list of uploaded files.
*
* @param array<int, UploadedFile|TemporaryUploadedFile> $files
*/
public function saveGalleryFiles(Hentai $hentai, Episode $episode, int $episodeNumber, array $files): void
{
$this->deleteOldGallery($episode);
$this->createGalleryFolder($hentai);
$counter = 0;
foreach ($files as $file) {
$gallery = $this->createGallery($hentai, $episode, $episodeNumber, $counter);
$this->saveGalleryImage($gallery, $file);
$counter += 1;
}
}
private function createGalleryFolder(Hentai $hentai): void
{
// Create Folder for Image Upload
if (! Storage::disk('public')->exists("/images/hentai/{$hentai->slug}")) {
Storage::disk('public')->makeDirectory("/images/hentai/{$hentai->slug}");
}
}
private function createGallery(Hentai $hentai, Episode $episode, int $episodeNumber, int $counter): Gallery
{
$gallery = new Gallery;
$gallery->hentai_id = $hentai->id;
$gallery->episode_id = $episode->id;
$gallery->image_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}.webp";
$gallery->thumbnail_url = "/images/hentai/{$hentai->slug}/gallery-ep-{$episodeNumber}-{$counter}-thumbnail.webp";
$gallery->save();
return $gallery;
}
private function saveGalleryImage(Gallery $gallery, UploadedFile|TemporaryUploadedFile $sourceImage): void
{
Image::read($sourceImage->getRealPath())
->cover(1920, 1080)
->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->image_url));
Image::read($sourceImage->getRealPath())
->cover(960, 540)
->encode(new WebpEncoder)
->save(Storage::disk('public')->path($gallery->thumbnail_url));
}
private function deleteOldGallery(Episode $episode): void
{
$oldGallery = Gallery::where('episode_id', $episode->id)->get();
foreach ($oldGallery as $oldImage) {
Storage::disk('public')->delete($oldImage->image_url);
Storage::disk('public')->delete($oldImage->thumbnail_url);
$oldImage->delete();
}
}
}