diff --git a/app/Http/Controllers/ComicController.php b/app/Http/Controllers/ComicController.php index e99d766..c6c7f2b 100644 --- a/app/Http/Controllers/ComicController.php +++ b/app/Http/Controllers/ComicController.php @@ -3,10 +3,11 @@ namespace App\Http\Controllers; use App\Helper\ZhConversion; +use App\Jobs\ImageUpsert; +use App\Jobs\RemotePrefetch; use App\Models\Author; use App\Models\Chapter; use App\Models\Comic; -use App\Models\Image; use App\Remote\CopyManga; use App\Remote\ImageFetcher; use GuzzleHttp\Exception\GuzzleException; @@ -22,13 +23,26 @@ use Illuminate\Support\Facades\Cache; use Inertia\Inertia; use Inertia\Response; +/** + * Comic Controller + */ class ComicController extends Controller { + /** + * @param CopyManga $copyManga + * @param ZhConversion $zhConversion + */ public function __construct(private readonly CopyManga $copyManga, private readonly ZhConversion $zhConversion) { } + /** + * Convert SC to ZH + * + * @param mixed $string + * @return mixed + */ protected function scToZh(mixed $string): mixed { if (gettype($string) !== 'string') { @@ -323,24 +337,8 @@ class ComicController extends Controller $chapterObj = Chapter::where('chapter_uuid', $chapter['chapter']['uuid'])->first(); $comicObj = Comic::where('pathword', $pathword)->first(); - $arrayForUpsert = []; - - foreach ($chapter['sorted'] as $k => $image) { - $metadata = $chapter; - unset($metadata['sorted']); - unset($metadata['chapter']['contents'], $metadata['chapter']['words']); - - $arrayForUpsert[] = [ - 'comic_id' => $comicObj->id, - 'chapter_id' => $chapterObj->id, - 'order' => $k, - 'url' => $image['url'], - 'metadata' => json_encode($metadata), - ]; - } - - // Do an upsert - Image::upsert($arrayForUpsert, uniqueBy: 'url'); + // Image Upsert + ImageUpsert::dispatch($comicObj->id, $chapterObj->id, $chapter); // Update history $request->user()->readingHistories()->attach($chapterObj->id, ['comic_id' => $comicObj->id]); @@ -348,6 +346,15 @@ class ComicController extends Controller // Get chapters from DB $chapters = $comicObj->chapters()->where('metadata->group_path_word', $chapter['chapter']['group_path_word'])->orderBy('order')->get(['name', 'chapter_uuid']); + // Do remote prefetch if needed + if ($chapter['chapter']['next'] !== null) { + try { + Chapter::where('chapter_uuid', $chapter['chapter']['next'])->firstOrFail()->images()->firstOrFail(); + } catch (ModelNotFoundException $e) { + RemotePrefetch::dispatch('chapter', ['pathword' => $pathword, 'uuid' => $chapter['chapter']['next']]); + } + } + return Inertia::render('Comic/Read', [ 'comic' => $this->scToZh($comic), 'chapter' => $this->scToZh($chapter), diff --git a/app/Jobs/ImageUpsert.php b/app/Jobs/ImageUpsert.php new file mode 100644 index 0000000..c708f8f --- /dev/null +++ b/app/Jobs/ImageUpsert.php @@ -0,0 +1,51 @@ +comicId}, chapterId: {$this->chapterId}"); + + $arrayForUpsert = []; + + foreach ($this->chapter['sorted'] as $k => $image) { + $metadata = $this->chapter; + unset($metadata['sorted']); + unset($metadata['chapter']['contents'], $metadata['chapter']['words']); + + $arrayForUpsert[] = [ + 'comic_id' => $this->comicId, + 'chapter_id' => $this->chapterId, + 'order' => $k, + 'url' => $image['url'], + 'metadata' => json_encode($metadata), + ]; + } + + // Do an upsert + Image::upsert($arrayForUpsert, uniqueBy: 'url'); + + Log::info('JOB ImageUpsert END'); + } +} diff --git a/app/Jobs/RemotePrefetch.php b/app/Jobs/RemotePrefetch.php new file mode 100644 index 0000000..70da749 --- /dev/null +++ b/app/Jobs/RemotePrefetch.php @@ -0,0 +1,51 @@ +action) { + case 'chapter': + Log::info("JOB RemotePrefetch START, action '{$this->action}', Pathword: {$this->parameters['pathword']}, UUID: {$this->parameters['uuid']}"); + + $copyManga->chapter($this->parameters['pathword'], $this->parameters['uuid']); + + Log::info("JOB RemotePrefetch END, action '{$this->action}'"); + break; + case 'chapters': + // TODO: + break; + case 'index': + // TODO + break; + case 'tags': + // TODO + break; + default: + Log::info("JOB RemotePrefetch Unknown action '{$this->action}'"); + break; + } + } +} diff --git a/app/Remote/CopyManga.php b/app/Remote/CopyManga.php index 9b626c9..4ee88d6 100644 --- a/app/Remote/CopyManga.php +++ b/app/Remote/CopyManga.php @@ -17,6 +17,7 @@ class CopyManga /** * @var array Caching options + * @deprecated */ protected array $options = [ 'caching' => true, @@ -101,17 +102,17 @@ class CopyManga * * @param string $url * @param array $value + * @param int $ttl * @return void */ - protected function writeToCache(string $url, array $value): void + protected function writeToCache(string $url, array $value, int $ttl = 0): void { if ($this->options['caching']) { Cache::add("URL_{$url}", array_merge($value, ['CACHE' => [ 'CACHE' => true, 'CACHED_AT' => Date::now(), - 'EXPIRE_AT' => Date::now()->addSeconds($this->options['cachingTimeout'])] - ]), - $this->options['cachingTimeout']); + 'EXPIRE_AT' => Date::now()->addSeconds(($ttl !== 0) ? $ttl : $this->options['cachingTimeout'])] + ]), $this->options['cachingTimeout']); } } @@ -121,10 +122,11 @@ class CopyManga * @param string $url * @param string $method * @param string $userAgent + * @param int $ttl * @return mixed|string * @throws GuzzleException */ - protected function execute(string $url, string $method = 'GET', string $userAgent = ""): mixed + protected function execute(string $url, string $method = 'GET', string $userAgent = "", int $ttl = 0): mixed { if ($this->options['caching']) { // Check cache exist @@ -156,7 +158,7 @@ class CopyManga if ($userAgent !== "") { // Directly send html to method to process $html = $response->getBody()->getContents(); - $this->writeToCache($url, ['response' => $html, 'type' => 'HTML']); + $this->writeToCache($url, ['response' => $html, 'type' => 'HTML'], $ttl); return $html; } @@ -166,7 +168,7 @@ class CopyManga if (json_last_error() === JSON_ERROR_NONE) { // Save to cache if needed - $this->writeToCache($url, $json['results']); + $this->writeToCache($url, $json['results'], $ttl); return $json['results']; } else { throw new Exception($json['code']); @@ -209,11 +211,8 @@ class CopyManga $parameters['limit'] = $limit; $parameters['offset'] = $offset; $parameters['top'] = $top; - //OPTIONS - // https://api.mangacopy.com/api/v3/comics?format=json&platform=1&q=x&limit=30&offset=0&top=all" - // https://api.mangacopy.com/api/v3/search/comic?platform=1&q=x&limit=20&offset=0&q_type=&_update=true - return $this->execute($this->buildUrl("comics", $parameters)); + return $this->execute($this->buildUrl("comics", $parameters), ttl: 15 * 60); } /** @@ -233,7 +232,7 @@ class CopyManga $parameters['limit'] = $limit; $parameters['offset'] = $offset; - return $this->execute($this->buildUrl("search/comic", $parameters)); + return $this->execute($this->buildUrl("search/comic", $parameters), ttl: 15 * 60); } /** @@ -246,7 +245,7 @@ class CopyManga */ public function comic(string $comic, array $parameters = []): mixed { - return $this->execute($this->buildUrl("comic2/{$comic}", $parameters)); + return $this->execute($this->buildUrl("comic2/{$comic}", $parameters), ttl: 24 * 60 * 60); } /** @@ -266,7 +265,7 @@ class CopyManga $parameters['offset'] = $offset; $options = $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters, false), 'OPTIONS'); - return $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters)); + return $this->execute($this->buildUrl("comic/{$comic}/group/{$group}/chapters", $parameters), ttl: 24 * 60 * 60); } /** @@ -297,7 +296,7 @@ class CopyManga public function legacyChapter(string $comic, string $chapter): array { $userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"; - $responses = $this->execute($this->legacyBuildUrl("comic/{$comic}/chapter/{$chapter}"), "GET", $userAgent); + $responses = $this->execute($this->legacyBuildUrl("comic/{$comic}/chapter/{$chapter}"), "GET", $userAgent, ttl: 24 * 60 * 60); // Get Content Key $dom = new DOMDocument(); @@ -347,11 +346,12 @@ class CopyManga */ public function chapter(string $comic, string $chapter, array $parameters = []): array { - $responses = $this->execute($this->buildUrl("comic/{$comic}/chapter2/{$chapter}", $parameters)); - $responses['sorted'] = $this->sort($responses['chapter']['contents'], $responses['chapter']['words']); + $responses = $this->execute($this->buildUrl("comic/{$comic}/chapter2/{$chapter}", $parameters), ttl: 24 * 60 * 60); if ($this->legacyImagesFetch) { $responses['sorted'] = $this->legacyChapter($comic, $chapter); + } else { + $responses['sorted'] = $this->sort($responses['chapter']['contents'], $responses['chapter']['words']); } return $responses; diff --git a/bun.lockb b/bun.lockb index 4c59ecf..d95ba2f 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/composer.json b/composer.json index 0bafdf0..2f82cfa 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "laravel/framework": "^11.31", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", + "plesk/ext-laravel-integration": "^7.0", "predis/predis": "^2.0", "sentry/sentry-laravel": "^4.10", "tightenco/ziggy": "^2.0" diff --git a/composer.lock b/composer.lock index e39bfe4..ba3b1e5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b4f2ee211728714c2fa05fbad3914d2e", + "content-hash": "641b7b21cd3231c89ab305cd615eeb6f", "packages": [ { "name": "brick/math", @@ -2310,12 +2310,12 @@ "version": "3.8.4", "source": { "type": "git", - "url": "https://github.com/briannesbitt/Carbon.git", + "url": "https://github.com/CarbonPHP/carbon.git", "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "shasum": "" }, @@ -2857,6 +2857,53 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "plesk/ext-laravel-integration", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/plesk/ext-laravel-integration.git", + "reference": "73dc4ea3f99e033396497f147aa033eb0602f943" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/plesk/ext-laravel-integration/zipball/73dc4ea3f99e033396497f147aa033eb0602f943", + "reference": "73dc4ea3f99e033396497f147aa033eb0602f943", + "shasum": "" + }, + "require": { + "laravel/framework": ">=7.0.0", + "php": ">=7.4" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "PleskExtLaravel\\Providers\\ConsoleServiceProvider" + ] + }, + "component": "package" + }, + "autoload": { + "psr-4": { + "PleskExtLaravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Plesk Laravel Toolkit integration with Laravel applications.", + "keywords": [ + "laravel", + "plesk" + ], + "support": { + "issues": "https://github.com/plesk/ext-laravel-integration/issues", + "source": "https://github.com/plesk/ext-laravel-integration/tree/7.0.0" + }, + "time": "2023-01-20T09:40:57+00:00" + }, { "name": "predis/predis", "version": "v2.3.0", diff --git a/package.json b/package.json index b0fad8b..632efc7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@headlessui/react": "^2.2.0", "@inertiajs/react": "^2.0.0", - "@tailwindcss/forms": "^0.5.9", + "@tailwindcss/forms": "^0.5.10", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "axios": "^1.7.9", @@ -21,7 +21,7 @@ "vite": "^6.0.7" }, "dependencies": { - "@hookform/resolvers": "^3.9.1", + "@hookform/resolvers": "^3.10.0", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.2", @@ -35,8 +35,8 @@ "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", - "@sentry/react": "^8.47.0", - "@sentry/vite-plugin": "^2.22.7", + "@sentry/react": "^8.48.0", + "@sentry/vite-plugin": "^2.23.0", "@tanstack/react-table": "^8.20.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/resources/js/Layouts/AppLayout.jsx b/resources/js/Layouts/AppLayout.jsx index 64359fb..e6184ce 100644 --- a/resources/js/Layouts/AppLayout.jsx +++ b/resources/js/Layouts/AppLayout.jsx @@ -35,8 +35,7 @@ export default function AppLayout({ auth, header, children, toolbar }) { const [theme, setTheme] = useState(getTheme()); useEffect(() => { - getTheme(); - setTheme(theme); + setTheme(getTheme()); }, []); return ( diff --git a/resources/js/Pages/Comic/Chapters.jsx b/resources/js/Pages/Comic/Chapters.jsx index 88cf7b6..4b732c8 100644 --- a/resources/js/Pages/Comic/Chapters.jsx +++ b/resources/js/Pages/Comic/Chapters.jsx @@ -215,7 +215,7 @@ export default function Chapters({ auth, comic, chapters, histories, offset }) { { chapters.list.sort((a, b) => ascending ? (a.index - b.index) : (b.index - a.index)).map(c => ( ) ) } - { (chapters.total > chapters.limit && chapters.offset === 0) && ( + { (chapters.total > chapters.limit && chapters.total > chapters.offset) && (