d08e280ce0
Replace the server-rendered episode upload form with a Livewire component, introducing an interactive admin experience that validates the new episode's stream path against all configured mirrors before saving. The controller-based `store` method and its route are removed, and episode creation now flows through the `AdminEpisodeForm` component with frontend upload handling and dynamic quality checks. Key changes: - Add `AdminEpisodeForm` Livewire component with file uploads, tag handling, and debounced stream/base-url validation. - Extend `CdnPathValidator::validateStream` to accept an episode number so the manifest path can point to the specific episode being added. - Remove the `store` method from `EpisodeController` and its POST route. - Delete the legacy `createEpisode` method from `EpisodeService`. - Replace the Blade upload modal with the Livewire view, including progress indicators and validation badges. - Add feature tests for the Livewire component and update unit tests for the CDN validator's episode-specific manifest check.
116 lines
3.5 KiB
PHP
116 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Http\Client\Response;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
class CdnPathValidator
|
|
{
|
|
public const DOWNLOAD_PREFIX = [
|
|
'FHD' => 'hentai-1080p/',
|
|
'FHDi' => 'hentai-1080p/',
|
|
'UHD' => 'hentai/',
|
|
'UHDi' => 'hentai/',
|
|
];
|
|
|
|
private const STREAM_PREFIX = 'hentai-stream/';
|
|
|
|
private const CACHE_TTL = 300;
|
|
|
|
public function validateDownload(string $type, string $url, bool $fresh = false): array
|
|
{
|
|
$path = self::DOWNLOAD_PREFIX[$type].ltrim($url, '/');
|
|
$domains = config('hstream.download_domain_4k');
|
|
|
|
return $this->checkMirrors($domains, [$path], $fresh);
|
|
}
|
|
|
|
public function validateStream(string $baseurl, int $episodeCount, bool $fresh = false, int $episodeNumber = 1): array
|
|
{
|
|
$domains = config('hstream.download_domain_4k');
|
|
|
|
// Validate the base directory, plus the episode manifest as a sanity check.
|
|
$paths = [self::STREAM_PREFIX.rtrim($baseurl, '/')];
|
|
if ($episodeCount >= 1) {
|
|
$paths[] = self::STREAM_PREFIX.rtrim($baseurl, '/').'/E'.str_pad($episodeNumber, 2, '0', STR_PAD_LEFT).'/720/manifest.mpd';
|
|
}
|
|
|
|
return $this->checkMirrors($domains, $paths, $fresh);
|
|
}
|
|
|
|
private function checkMirrors(array $domains, array $paths, bool $fresh = false): array
|
|
{
|
|
$cacheKey = 'cdncheck:'.sha1(serialize([$domains, $paths]));
|
|
|
|
if ($fresh) {
|
|
Cache::forget($cacheKey);
|
|
}
|
|
|
|
return Cache::remember($cacheKey, self::CACHE_TTL, fn () => $this->runChecks($domains, $paths));
|
|
}
|
|
|
|
private function runChecks(array $domains, array $paths): array
|
|
{
|
|
$entries = [];
|
|
foreach ($domains as $domain) {
|
|
foreach ($paths as $path) {
|
|
$entries[] = ['domain' => $domain, 'path' => $path];
|
|
}
|
|
}
|
|
|
|
try {
|
|
$responses = Http::pool(function ($pool) use ($entries) {
|
|
foreach ($entries as $entry) {
|
|
$pool->timeout(4)
|
|
->withHeaders(['Accept' => 'application/json'])
|
|
->get($entry['domain'].'/check/'.$this->tokenize($entry['path']).'/'.$this->tokenize(now()->addHours(6)));
|
|
}
|
|
});
|
|
} catch (\Throwable $e) {
|
|
$mirrors = [];
|
|
foreach ($entries as $entry) {
|
|
$mirrors[$entry['domain']][$entry['path']] = false;
|
|
}
|
|
|
|
return [
|
|
'valid' => false,
|
|
'mirrors' => $mirrors,
|
|
'size' => null,
|
|
];
|
|
}
|
|
|
|
$mirrors = [];
|
|
$allValid = true;
|
|
$firstSize = null;
|
|
|
|
foreach ($entries as $index => $entry) {
|
|
$response = $responses[$index];
|
|
$ok = $response instanceof Response
|
|
&& $response->ok()
|
|
&& $response->json('valid') === true;
|
|
|
|
$mirrors[$entry['domain']][$entry['path']] = $ok;
|
|
|
|
if (! $ok) {
|
|
$allValid = false;
|
|
} elseif ($firstSize === null) {
|
|
$firstSize = $response->json('size');
|
|
}
|
|
}
|
|
|
|
return [
|
|
'valid' => $allValid,
|
|
'mirrors' => $mirrors,
|
|
'size' => $allValid ? $firstSize : null,
|
|
];
|
|
}
|
|
|
|
private function tokenize(string $value): string
|
|
{
|
|
return urlencode(Crypt::encryptString($value));
|
|
}
|
|
}
|