From 83ab144734d78f8e3567ed4d97b2bd4bffd73dd6 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sat, 28 Dec 2024 23:48:10 +0100 Subject: [PATCH 1/7] WIP cache --- app/Http/Kernel.php | 2 + app/Http/Middleware/ResponseCache.php | 94 +++++++++ app/Listeners/CacheListener.php | 33 +++ app/Metadata/Cache/RouteCacheConfig.php | 22 ++ app/Metadata/Cache/RouteCacheManager.php | 189 ++++++++++++++++++ app/Providers/AppServiceProvider.php | 7 + .../2024_12_28_190150_caching_config.php | 31 +++ 7 files changed, 378 insertions(+) create mode 100644 app/Http/Middleware/ResponseCache.php create mode 100644 app/Listeners/CacheListener.php create mode 100644 app/Metadata/Cache/RouteCacheConfig.php create mode 100644 app/Metadata/Cache/RouteCacheManager.php create mode 100644 database/migrations/2024_12_28_190150_caching_config.php diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a4007b209ee..eebbcc8e4bc 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -72,6 +72,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'response_cache', ], ]; @@ -95,5 +96,6 @@ class Kernel extends HttpKernel 'support' => \LycheeVerify\Http\Middleware\VerifySupporterStatus::class, 'config_integrity' => \App\Http\Middleware\ConfigIntegrity::class, 'unlock_with_password' => \App\Http\Middleware\UnlockWithPassword::class, + 'response_cache' => \App\Http\Middleware\ResponseCache::class, ]; } diff --git a/app/Http/Middleware/ResponseCache.php b/app/Http/Middleware/ResponseCache.php new file mode 100644 index 00000000000..b60565099c3 --- /dev/null +++ b/app/Http/Middleware/ResponseCache.php @@ -0,0 +1,94 @@ +route_cache_manager = $route_cache_manager; + } + + /** + * Handle an incoming request. + * + * @param Request $request + * @param \Closure $next + * + * @return Response + * + * @throws \InvalidArgumentException + */ + public function handle(Request $request, \Closure $next): mixed + { + // We only cache get requests. + if ($request->method() !== 'GET') { + return $next($request); + } + + if (Configs::getValueAsBool('cache_enabled') === false) { + return $next($request); + } + + $config = $this->route_cache_manager->getConfig($request->route()->uri); + + // Check with the route manager if we can cache this route. + if ($config === false) { + return $next($request); + } + + if (Cache::supportsTags()) { + return $this->cacheWithTags($request, $next, $config); + } + + return $this->chacheWithoutTags($request, $next, $config); + } + + /** + * This is the light version of caching: we cache only if the user is not logged in. + * + * @param Request $request + * @param \Closure $next + * + * @return mixed + */ + private function chacheWithoutTags(Request $request, \Closure $next, RouteCacheConfig $config): mixed + { + // We do not cache this. + if ($config->user_dependant && Auth::user() !== null) { + return $next($request); + } + + $key = $this->route_cache_manager->getKey($request, $config); + + return Cache::remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); + } + + /** + * This is the stronger version of caching. + * + * @param Request $request + * @param \Closure $next + * + * @return mixed + */ + private function cacheWithTags(Request $request, \Closure $next, RouteCacheConfig $config): mixed + { + $key = $this->route_cache_manager->getKey($request, $config); + + return Cache::tags([$config->tag])->remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); + } +} diff --git a/app/Listeners/CacheListener.php b/app/Listeners/CacheListener.php new file mode 100644 index 00000000000..b00447ee949 --- /dev/null +++ b/app/Listeners/CacheListener.php @@ -0,0 +1,33 @@ +key, 'lv:dev-lycheeOrg')) { + return; + } + + match (get_class($event)) { + CacheMissed::class => Log::info('CacheListener: Miss for ' . $event->key), + CacheHit::class => Log::info('CacheListener: Hit for ' . $event->key), + default => '', + }; + } +} diff --git a/app/Metadata/Cache/RouteCacheConfig.php b/app/Metadata/Cache/RouteCacheConfig.php new file mode 100644 index 00000000000..6ff16813a01 --- /dev/null +++ b/app/Metadata/Cache/RouteCacheConfig.php @@ -0,0 +1,22 @@ + */ + private array $cache_list; + + /** + * Initalize the cache list. + * + * @return void + */ + public function __construct() + { + $this->cache_list = [ + 'api/v2/Album' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), + 'api/v2/Album::getTargetListAlbums' => false, // TODO: cache me later. + 'api/v2/Albums' => new RouteCacheConfig(tag: 'gallery', user_dependant: true), + 'api/v2/Auth::config' => new RouteCacheConfig(tag: 'auth', user_dependant: true), + 'api/v2/Auth::rights' => new RouteCacheConfig(tag: 'auth', user_dependant: true), + 'api/v2/Auth::user' => new RouteCacheConfig(tag: 'user', user_dependant: true), + 'api/v2/Diagnostics' => false, + 'api/v2/Diagnostics::config' => false, + 'api/v2/Diagnostics::info' => new RouteCacheConfig(tag: 'settings', user_dependant: true), + 'api/v2/Diagnostics::permissions' => new RouteCacheConfig(tag: 'settings', user_dependant: true), + 'api/v2/Diagnostics::space' => new RouteCacheConfig(tag: 'settings', user_dependant: true), + + // Response must be different for each call. + 'api/v2/Frame' => false, + + 'api/v2/Gallery::Footer' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Gallery::Init' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Gallery::getLayout' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Gallery::getUploadLimits' => new RouteCacheConfig(tag: 'settings'), + + 'api/v2/Jobs' => false, // TODO: fix me later + 'api/v2/LandingPage' => new RouteCacheConfig(tag: 'settings'), + + // We do not need to cache those. + 'api/v2/Maintenance::cleaning' => false, + 'api/v2/Maintenance::fullTree' => false, + 'api/v2/Maintenance::genSizeVariants' => false, + 'api/v2/Maintenance::jobs' => false, + 'api/v2/Maintenance::missingFileSize' => false, + 'api/v2/Maintenance::tree' => false, + 'api/v2/Maintenance::update' => false, + + 'api/v2/Map' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), + 'api/v2/Map::provider' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Oauth' => new RouteCacheConfig(tag: 'user', user_dependant: true), + + // Response must be different for each call. + 'api/v2/Photo::random' => false, + + 'api/v2/Search' => false, // TODO: how to support pagination ?? new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id', 'terms']), + 'api/v2/Search::init' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Settings' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Settings::getLanguages' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Sharing' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), + 'api/v2/Sharing::all' => new RouteCacheConfig(tag: 'gallery', user_dependant: true), + 'api/v2/Statistics::albumSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), + 'api/v2/Statistics::sizeVariantSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), + 'api/v2/Statistics::totalAlbumSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), + 'api/v2/Statistics::userSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), + 'api/v2/UserManagement' => new RouteCacheConfig(tag: 'users', user_dependant: true), + 'api/v2/Users' => new RouteCacheConfig(tag: 'users', user_dependant: true), + 'api/v2/Users::count' => new RouteCacheConfig(tag: 'users', user_dependant: true), + 'api/v2/Version' => false, + 'api/v2/WebAuthn' => false, + + // This is returning a stream, we do not cache it. + 'api/v2/Zip' => false, + ]; + } + + public function getConfig(string $uri): RouteCacheConfig|false + { + if (!array_key_exists($uri, $this->cache_list)) { + Log::warning('ResponseCache: No cache config for ' . $uri); + + return false; + } + + Log::info('ResponseCache: Cache config for ' . $uri); + + return $this->cache_list[$uri]; + } + + public function getKey(Request $request, RouteCacheConfig $config): string + { + $key = self::REQUEST . $request->url(); + + // If the request is user dependant, we add the user id to the key. + // That way we ensure that this does not contaminate between logged in and looged out users. + if ($config->user_dependant) { + $key .= self::USER . $request->user?->getId(); + } + + if (count($config->extra) > 0) { + $key .= self::EXTRA; + foreach ($config->extra as $extra) { + /** @var string $vals */ + $vals = $request->query($extra) ?? ''; + $key .= ':' . $vals; + } + } + + return $key; + } + + /** + * Generate a key for the cache. + * + * @param RouteCacheConfig $config + * @param string $uri + * @param int|null $userId + * @param array $extras + * + * @return string + */ + public function genKey(RouteCacheConfig $config, string $uri, ?int $userId, array $extras): string + { + $key = self::REQUEST . $uri; + + // If the request is user dependant, we add the user id to the key. + // That way we ensure that this does not contaminate between logged in and looged out users. + if ($config->user_dependant) { + $key .= self::USER . $userId; + } + + if (count($config->extra) > 0) { + $key .= self::EXTRA; + foreach ($config->extra as $extra) { + $key .= ':' . ($extras[$extra] ?? ''); + } + } + + return $key; + } + + /** + * Return the tag associated to the route if there is one. + * Return false if there is no tag for this route or if the route is not cached (just safe precaution). + * + * @param string $uri + * + * @return string|false + */ + public function getTag(string $uri): string|false + { + if (!array_key_exists($uri, $this->cache_list)) { + return false; + } + + if ($this->cache_list[$uri] === false) { + return false; + } + + return $this->cache_list[$uri]->tag; + } + + /** + * Given a tag, return all the routes associated to this tag. + * + * @param string $tag + * + * @return string[] + */ + public function retrieveKeysForTag(string $tag): array + { + $keys = []; + foreach ($this->cache_list as $uri => $value) { + if (is_array($value) && array_key_exists('tag', $value) && $value['tag'] === $tag) { + $keys[] = $uri; + } + } + + return $keys; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 16e396d2d8b..bfb44eb1ff0 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -11,6 +11,7 @@ use App\Factories\AlbumFactory; use App\Image\SizeVariantDefaultFactory; use App\Image\StreamStatFilter; +use App\Listeners\CacheListener; use App\Metadata\Json\CommitsRequest; use App\Metadata\Json\UpdateRequest; use App\Metadata\Versions\FileVersion; @@ -23,11 +24,14 @@ use App\Policies\AlbumQueryPolicy; use App\Policies\PhotoQueryPolicy; use App\Policies\SettingsPolicy; +use Illuminate\Cache\Events\CacheHit; +use Illuminate\Cache\Events\CacheMissed; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\URL; @@ -87,6 +91,9 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { + Event::listen(CacheHit::class, CacheListener::class . '@handle'); + Event::listen(CacheMissed::class, CacheListener::class . '@handle'); + // Prohibits: db:wipe, migrate:fresh, migrate:refresh, and migrate:reset DB::prohibitDestructiveCommands(config('app.env', 'production') !== 'dev'); diff --git a/database/migrations/2024_12_28_190150_caching_config.php b/database/migrations/2024_12_28_190150_caching_config.php new file mode 100644 index 00000000000..2c99c740f9c --- /dev/null +++ b/database/migrations/2024_12_28_190150_caching_config.php @@ -0,0 +1,31 @@ + 'cache_enabled', + 'value' => '1', + 'cat' => 'Mod Cache', + 'type_range' => self::BOOL, + 'description' => 'Enable caching of responses given requests.', + 'details' => 'This will significantly speed up the response time of Lychee.', + 'is_secret' => false, + 'level' => 0, + ], + [ + 'key' => 'cache_ttl', + 'value' => '60', + 'cat' => 'Mod Cache', + 'type_range' => self::POSITIVE, + 'description' => 'Number of seconds responses should be cached.', + 'details' => '', + 'is_secret' => false, + 'level' => 1, + ], + ]; + } +}; From 1f61b6afd878da6be9c5fd34c0f12be53d0a5b38 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sat, 28 Dec 2024 23:56:04 +0100 Subject: [PATCH 2/7] fix setting cache --- app/Metadata/Cache/RouteCacheManager.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Metadata/Cache/RouteCacheManager.php b/app/Metadata/Cache/RouteCacheManager.php index b5e1ef61287..e783dbbb2ae 100644 --- a/app/Metadata/Cache/RouteCacheManager.php +++ b/app/Metadata/Cache/RouteCacheManager.php @@ -63,7 +63,7 @@ public function __construct() 'api/v2/Search' => false, // TODO: how to support pagination ?? new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id', 'terms']), 'api/v2/Search::init' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Settings' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Settings' => new RouteCacheConfig(tag: 'settings', user_dependant: true), 'api/v2/Settings::getLanguages' => new RouteCacheConfig(tag: 'settings'), 'api/v2/Sharing' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), 'api/v2/Sharing::all' => new RouteCacheConfig(tag: 'gallery', user_dependant: true), @@ -90,14 +90,12 @@ public function getConfig(string $uri): RouteCacheConfig|false return false; } - Log::info('ResponseCache: Cache config for ' . $uri); - return $this->cache_list[$uri]; } public function getKey(Request $request, RouteCacheConfig $config): string { - $key = self::REQUEST . $request->url(); + $key = self::REQUEST . $request->route()->uri; // If the request is user dependant, we add the user id to the key. // That way we ensure that this does not contaminate between logged in and looged out users. From b57ddcb8d3fecc955a0eb1aee9d9ce6ce8682549 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 29 Dec 2024 00:08:33 +0100 Subject: [PATCH 3/7] more work --- .../Admin/Maintenance/FlushCache.php | 29 +++++++++++ app/Metadata/Cache/RouteCacheManager.php | 11 ++++ .../maintenance/MaintenanceFlushCache.vue | 52 +++++++++++++++++++ resources/js/services/maintenance-service.ts | 4 ++ resources/js/views/Maintenance.vue | 2 + routes/api_v2.php | 1 + tests/Feature_v2/Maintenance/FlushTest.php | 36 +++++++++++++ 7 files changed, 135 insertions(+) create mode 100644 app/Http/Controllers/Admin/Maintenance/FlushCache.php create mode 100644 resources/js/components/maintenance/MaintenanceFlushCache.vue create mode 100644 tests/Feature_v2/Maintenance/FlushTest.php diff --git a/app/Http/Controllers/Admin/Maintenance/FlushCache.php b/app/Http/Controllers/Admin/Maintenance/FlushCache.php new file mode 100644 index 00000000000..9215290a2a5 --- /dev/null +++ b/app/Http/Controllers/Admin/Maintenance/FlushCache.php @@ -0,0 +1,29 @@ +retrieveKeysForTag($tag); + // TODO: refine. + + foreach ($keys as $key) { + Cache::forget($key); + } + } } diff --git a/resources/js/components/maintenance/MaintenanceFlushCache.vue b/resources/js/components/maintenance/MaintenanceFlushCache.vue new file mode 100644 index 00000000000..d471ba6f0db --- /dev/null +++ b/resources/js/components/maintenance/MaintenanceFlushCache.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/resources/js/services/maintenance-service.ts b/resources/js/services/maintenance-service.ts index b4b3ddd66f6..0b174bba9a8 100644 --- a/resources/js/services/maintenance-service.ts +++ b/resources/js/services/maintenance-service.ts @@ -54,6 +54,10 @@ const MaintenanceService = { return axios.post(`${Constants.getApiUrl()}Maintenance::optimize`, {}); }, + flushDo(): Promise> { + return axios.post(`${Constants.getApiUrl()}Maintenance::flushCache`, {}); + }, + register(key: string): Promise> { return axios.post(`${Constants.getApiUrl()}Maintenance::register`, { key: key }); }, diff --git a/resources/js/views/Maintenance.vue b/resources/js/views/Maintenance.vue index 33d65d49140..4ac32223962 100644 --- a/resources/js/views/Maintenance.vue +++ b/resources/js/views/Maintenance.vue @@ -18,6 +18,7 @@ > + @@ -40,5 +41,6 @@ import MaintenanceFixTree from "@/components/maintenance/MaintenanceFixTree.vue" import MaintenanceGenSizevariants from "@/components/maintenance/MaintenanceGenSizevariants.vue"; import MaintenanceOptimize from "@/components/maintenance/MaintenanceOptimize.vue"; import MaintenanceUpdate from "@/components/maintenance/MaintenanceUpdate.vue"; +import MaintenanceFlushCache from "@/components/maintenance/MaintenanceFlushCache.vue"; import OpenLeftMenu from "@/components/headers/OpenLeftMenu.vue"; diff --git a/routes/api_v2.php b/routes/api_v2.php index 24daa636145..f379d9fe2df 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -206,6 +206,7 @@ Route::get('/Maintenance::missingFileSize', [Admin\Maintenance\MissingFileSizes::class, 'check']); Route::post('/Maintenance::missingFileSize', [Admin\Maintenance\MissingFileSizes::class, 'do']); Route::post('/Maintenance::optimize', [Admin\Maintenance\Optimize::class, 'do']); +Route::post('/Maintenance::flushCache', [Admin\Maintenance\FlushCache::class, 'do']); Route::post('/Maintenance::register', Admin\Maintenance\RegisterController::class); Route::get('/Maintenance::fullTree', [Admin\Maintenance\FullTree::class, 'check']); Route::post('/Maintenance::fullTree', [Admin\Maintenance\FullTree::class, 'do']); diff --git a/tests/Feature_v2/Maintenance/FlushTest.php b/tests/Feature_v2/Maintenance/FlushTest.php new file mode 100644 index 00000000000..874cc4d8559 --- /dev/null +++ b/tests/Feature_v2/Maintenance/FlushTest.php @@ -0,0 +1,36 @@ +postJson('Maintenance::flushCache'); + $this->assertUnauthorized($response); + } + + public function testUser(): void + { + $response = $this->actingAs($this->userLocked)->postJson('Maintenance::flushCache'); + $this->assertForbidden($response); + } + + public function testAdmin(): void + { + $response = $this->actingAs($this->admin)->postJson('Maintenance::flushCache'); + $this->assertNoContent($response); + } +} \ No newline at end of file From c63d7c2081766bc40c1d46c8e88a0dba1cf46855 Mon Sep 17 00:00:00 2001 From: ildyria Date: Sun, 29 Dec 2024 23:04:32 +0100 Subject: [PATCH 4/7] more work --- app/Actions/Photo/Create.php | 5 +- app/Enum/CacheTag.php | 18 +++ app/Events/AlbumRouteCacheUpdated.php | 21 +++ app/Events/TaggedRouteCacheUpdated.php | 22 ++++ .../Admin/Maintenance/FullTree.php | 3 + .../Admin/Maintenance/GenSizeVariants.php | 3 + .../Admin/Maintenance/MissingFileSizes.php | 3 + .../Admin/Maintenance/RegisterController.php | 4 + .../Controllers/Admin/SettingsController.php | 3 + .../Admin/UserManagementController.php | 8 ++ .../Controllers/Gallery/AlbumController.php | 59 +++++++-- app/Http/Controllers/OauthController.php | 6 + app/Http/Controllers/ProfileController.php | 8 ++ app/Http/Kernel.php | 6 +- .../Caching/AlbumRouteCacheRefresher.php | 124 ++++++++++++++++++ .../Middleware/{ => Caching}/CacheControl.php | 4 +- .../{ => Caching}/ResponseCache.php | 12 +- app/Listeners/AlbumCacheCleaner.php | 120 +++++++++++++++++ app/Listeners/CacheListener.php | 16 +-- app/Listeners/TaggedRouteCacheCleaner.php | 35 +++++ app/Metadata/Cache/RouteCacheConfig.php | 10 +- app/Metadata/Cache/RouteCacheManager.php | 109 ++++++++------- app/Providers/AppServiceProvider.php | 9 ++ .../2024_12_28_190150_caching_config.php | 10 ++ phpstan.neon | 4 +- .../Metadata/Cache/RouteCacheManagerTest.php | 78 +++++++++++ 26 files changed, 610 insertions(+), 90 deletions(-) create mode 100644 app/Enum/CacheTag.php create mode 100644 app/Events/AlbumRouteCacheUpdated.php create mode 100644 app/Events/TaggedRouteCacheUpdated.php create mode 100644 app/Http/Middleware/Caching/AlbumRouteCacheRefresher.php rename app/Http/Middleware/{ => Caching}/CacheControl.php (90%) rename app/Http/Middleware/{ => Caching}/ResponseCache.php (81%) create mode 100644 app/Listeners/AlbumCacheCleaner.php create mode 100644 app/Listeners/TaggedRouteCacheCleaner.php create mode 100644 tests/Unit/Metadata/Cache/RouteCacheManagerTest.php diff --git a/app/Actions/Photo/Create.php b/app/Actions/Photo/Create.php index 2578fec5ad6..44ab6bbc92b 100644 --- a/app/Actions/Photo/Create.php +++ b/app/Actions/Photo/Create.php @@ -26,10 +26,10 @@ use App\Image\Files\NativeLocalFile; use App\Legacy\Actions\Photo\Create as LegacyPhotoCreate; use App\Models\Photo; +use App\Models\User; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Pipeline\Pipeline; use LycheeVerify\Verify; -use User; class Create { @@ -212,6 +212,7 @@ private function executePipeOnDTO(array $pipes, VideoPartnerDTO|StandaloneDTO|Ph // If source file could not be put into final destination, remove // freshly created photo from DB to avoid having "zombie" entries. try { + /** @disregard */ $dto->getPhoto()->delete(); } catch (\Throwable) { // Sic! If anything goes wrong here, we still throw the original exception @@ -311,7 +312,7 @@ private function checkQuota(NativeLocalFile $sourceFile): void return; } - $user = \User::find($this->strategyParameters->intendedOwnerId) ?? throw new ModelNotFoundException(); + $user = User::find($this->strategyParameters->intendedOwnerId) ?? throw new ModelNotFoundException(); // User does not have quota if ($user->quota_kb === null) { diff --git a/app/Enum/CacheTag.php b/app/Enum/CacheTag.php new file mode 100644 index 00000000000..81984cbf9c5 --- /dev/null +++ b/app/Enum/CacheTag.php @@ -0,0 +1,18 @@ +update($albumInstance, $request->albums(), $keyName); + + AlbumRouteCacheUpdated::dispatch(); } /** diff --git a/app/Http/Controllers/Admin/Maintenance/GenSizeVariants.php b/app/Http/Controllers/Admin/Maintenance/GenSizeVariants.php index 327ec939742..9f6bc159384 100644 --- a/app/Http/Controllers/Admin/Maintenance/GenSizeVariants.php +++ b/app/Http/Controllers/Admin/Maintenance/GenSizeVariants.php @@ -4,6 +4,7 @@ use App\Contracts\Models\SizeVariantFactory; use App\Enum\SizeVariantType; +use App\Events\AlbumRouteCacheUpdated; use App\Exceptions\MediaFileOperationException; use App\Http\Requests\Maintenance\CreateThumbsRequest; use App\Image\PlaceholderEncoder; @@ -56,6 +57,8 @@ public function do(CreateThumbsRequest $request, SizeVariantFactory $sizeVariant } catch (MediaFileOperationException $e) { Log::error('Failed to create ' . $request->kind()->value . ' for photo id ' . $photo->id . ''); } + + AlbumRouteCacheUpdated::dispatch(); // @codeCoverageIgnoreEnd } } diff --git a/app/Http/Controllers/Admin/Maintenance/MissingFileSizes.php b/app/Http/Controllers/Admin/Maintenance/MissingFileSizes.php index 454ad0849e3..7d1a2400a5f 100644 --- a/app/Http/Controllers/Admin/Maintenance/MissingFileSizes.php +++ b/app/Http/Controllers/Admin/Maintenance/MissingFileSizes.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin\Maintenance; use App\Enum\StorageDiskType; +use App\Events\AlbumRouteCacheUpdated; use App\Http\Requests\Maintenance\MaintenanceRequest; use App\Models\SizeVariant; use Illuminate\Routing\Controller; @@ -52,6 +53,8 @@ public function do(MaintenanceRequest $request): void } // @codeCoverageIgnoreEnd } + + AlbumRouteCacheUpdated::dispatch(); } /** diff --git a/app/Http/Controllers/Admin/Maintenance/RegisterController.php b/app/Http/Controllers/Admin/Maintenance/RegisterController.php index 7bfefd1df64..087216c7e62 100644 --- a/app/Http/Controllers/Admin/Maintenance/RegisterController.php +++ b/app/Http/Controllers/Admin/Maintenance/RegisterController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers\Admin\Maintenance; +use App\Enum\CacheTag; +use App\Events\TaggedRouteCacheUpdated; use App\Http\Requests\Maintenance\RegisterRequest; use App\Http\Resources\GalleryConfigs\RegisterData; use App\Models\Configs; @@ -29,6 +31,8 @@ public function __invoke(RegisterRequest $request): RegisterData // Not valid, reset the key. Configs::set('license_key', ''); + TaggedRouteCacheUpdated::dispatch(CacheTag::SETTINGS); + return new RegisterData(false); } } diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 03491bf0402..967934b3ce0 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -2,6 +2,8 @@ namespace App\Http\Controllers\Admin; +use App\Enum\CacheTag; +use App\Events\TaggedRouteCacheUpdated; use App\Exceptions\InsufficientFilesystemPermissions; use App\Http\Requests\Settings\GetAllConfigsRequest; use App\Http\Requests\Settings\SetConfigsRequest; @@ -49,6 +51,7 @@ public function setConfigs(SetConfigsRequest $request): ConfigCollectionResource }); Configs::invalidateCache(); + TaggedRouteCacheUpdated::dispatch(CacheTag::SETTINGS); return new ConfigCollectionResource(Configs::orderBy('cat', 'asc')->get()); } diff --git a/app/Http/Controllers/Admin/UserManagementController.php b/app/Http/Controllers/Admin/UserManagementController.php index 19e7094f2ef..db85191d925 100644 --- a/app/Http/Controllers/Admin/UserManagementController.php +++ b/app/Http/Controllers/Admin/UserManagementController.php @@ -5,6 +5,8 @@ use App\Actions\Statistics\Spaces; use App\Actions\User\Create; use App\Actions\User\Save; +use App\Enum\CacheTag; +use App\Events\TaggedRouteCacheUpdated; use App\Exceptions\UnauthorizedException; use App\Http\Requests\UserManagement\AddUserRequest; use App\Http\Requests\UserManagement\DeleteUserRequest; @@ -60,6 +62,8 @@ public function save(SetUserSettingsRequest $request, Save $save): void quota_kb: $request->quota_kb(), note: $request->note() ); + + TaggedRouteCacheUpdated::dispatch(CacheTag::USERS); } /** @@ -78,6 +82,8 @@ public function delete(DeleteUserRequest $request): void throw new UnauthorizedException('You are not allowed to delete yourself'); } $request->user2()->delete(); + + TaggedRouteCacheUpdated::dispatch(CacheTag::USERS); } /** @@ -99,6 +105,8 @@ public function create(AddUserRequest $request, Create $create): UserManagementR note: $request->note() ); + TaggedRouteCacheUpdated::dispatch(CacheTag::USERS); + return new UserManagementResource($user, ['id' => $user->id, 'size' => 0], $request->is_se()); } } \ No newline at end of file diff --git a/app/Http/Controllers/Gallery/AlbumController.php b/app/Http/Controllers/Gallery/AlbumController.php index a7b7ae7c44a..dc5963440af 100644 --- a/app/Http/Controllers/Gallery/AlbumController.php +++ b/app/Http/Controllers/Gallery/AlbumController.php @@ -15,6 +15,7 @@ use App\Actions\Album\Transfer; use App\Actions\Album\Unlock; use App\Actions\Photo\Archive as PhotoArchive; +use App\Events\AlbumRouteCacheUpdated; use App\Exceptions\Internal\LycheeLogicException; use App\Exceptions\UnauthenticatedException; use App\Http\Requests\Album\AddAlbumRequest; @@ -108,6 +109,9 @@ public function createAlbum(AddAlbumRequest $request): string */ public function createTagAlbum(AddTagAlbumRequest $request, CreateTagAlbum $create): string { + // Root + AlbumRouteCacheUpdated::dispatch(''); + return $create->create($request->title(), $request->tags())->id; } @@ -140,7 +144,8 @@ public function updateAlbum(UpdateAlbumRequest $request, SetHeader $setHeader): album: $album, is_compact: $request->is_compact(), photo: $request->photo(), - shall_override: true); + shall_override: true + ); return EditableBaseAlbumResource::fromModel($album); } @@ -167,6 +172,7 @@ public function updateTagAlbum(UpdateTagAlbumRequest $request): EditableBaseAlbu $album->photo_timeline = $request->photo_timeline(); $album->save(); + // Root return EditableBaseAlbumResource::fromModel($album); } @@ -179,26 +185,46 @@ public function updateTagAlbum(UpdateTagAlbumRequest $request): EditableBaseAlbu * * @return AlbumProtectionPolicy */ - public function updateProtectionPolicy(SetAlbumProtectionPolicyRequest $request, + public function updateProtectionPolicy( + SetAlbumProtectionPolicyRequest $request, SetProtectionPolicy $setProtectionPolicy, - SetSmartProtectionPolicy $setSmartProtectionPolicy): AlbumProtectionPolicy - { + SetSmartProtectionPolicy $setSmartProtectionPolicy, + ): AlbumProtectionPolicy { if ($request->album() instanceof BaseSmartAlbum) { - $setSmartProtectionPolicy->do( - $request->album(), - $request->albumProtectionPolicy()->is_public - ); - - return AlbumProtectionPolicy::ofSmartAlbum($request->album()); + return $this->updateProtectionPolicySmart($request->album(), $request->albumProtectionPolicy()->is_public, $setSmartProtectionPolicy); } /** @var BaseAlbum $album */ $album = $request->album(); - $setProtectionPolicy->do( + + return $this->updateProtectionPolicyBase( $album, $request->albumProtectionPolicy(), $request->isPasswordProvided(), - $request->password() + $request->password(), + $setProtectionPolicy + ); + } + + private function updateProtectionPolicySmart(BaseSmartAlbum $album, bool $is_public, SetSmartProtectionPolicy $setSmartProtectionPolicy): AlbumProtectionPolicy + { + $setSmartProtectionPolicy->do($album, $is_public); + + return AlbumProtectionPolicy::ofSmartAlbum($album); + } + + private function updateProtectionPolicyBase( + BaseAlbum $album, + AlbumProtectionPolicy $protectionPolicy, + bool $shallSetPassword, + ?string $password, + SetProtectionPolicy $setProtectionPolicy): AlbumProtectionPolicy + { + $setProtectionPolicy->do( + $album, + $protectionPolicy, + $shallSetPassword, + $password ); return AlbumProtectionPolicy::ofBaseAlbum($album->refresh()); @@ -214,6 +240,11 @@ public function updateProtectionPolicy(SetAlbumProtectionPolicyRequest $request, */ public function delete(DeleteAlbumsRequest $request, Delete $delete): void { + Album::select('parent_id') + ->whereIn('id', $request->albumIds()) + ->get() + ->each(fn (Album $album) => AlbumRouteCacheUpdated::dispatch($album->parent_id ?? '')); + $fileDeleter = $delete->do($request->albumIds()); App::terminating(fn () => $fileDeleter->do()); } @@ -244,6 +275,7 @@ public function getTargetListAlbums(TargetListAlbumRequest $request, ListAlbums */ public function merge(MergeAlbumsRequest $request, Merge $merge): void { + $request->albums()->each(fn (Album $album) => AlbumRouteCacheUpdated::dispatch($album->id)); $merge->do($request->album(), $request->albums()); } @@ -257,6 +289,7 @@ public function merge(MergeAlbumsRequest $request, Merge $merge): void */ public function move(MoveAlbumsRequest $request, Move $move): void { + $request->albums()->each(fn (Album $album) => AlbumRouteCacheUpdated::dispatch($album->id)); $move->do($request->album(), $request->albums()); } @@ -367,4 +400,4 @@ public function deleteTrack(DeleteTrackRequest $request): void { $request->album()->deleteTrack(); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php index 6176cef97fd..e19e4746151 100644 --- a/app/Http/Controllers/OauthController.php +++ b/app/Http/Controllers/OauthController.php @@ -3,7 +3,9 @@ namespace App\Http\Controllers; use App\Actions\Oauth\Oauth as OauthAction; +use App\Enum\CacheTag; use App\Enum\OauthProvidersType; +use App\Events\TaggedRouteCacheUpdated; use App\Exceptions\UnauthenticatedException; use App\Exceptions\UnauthorizedException; use App\Http\Requests\Profile\ClearOauthRequest; @@ -86,6 +88,8 @@ public function register(string $provider) $providerEnum = $this->oauth->validateProviderOrDie($provider); Session::put($providerEnum->value, OauthAction::OAUTH_REGISTER); + TaggedRouteCacheUpdated::dispatch(CacheTag::USER); + return Socialite::driver($providerEnum->value)->redirect(); } @@ -115,6 +119,8 @@ public function clear(ClearOauthRequest $request): void /** @var User $user */ $user = Auth::user() ?? throw new UnauthenticatedException(); $user->oauthCredentials()->where('provider', '=', $request->provider())->delete(); + + TaggedRouteCacheUpdated::dispatch(CacheTag::USER); } /** diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 8135c978e2a..ea55f925d07 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -5,6 +5,8 @@ use App\Actions\Profile\UpdateLogin; use App\Actions\User\TokenDisable; use App\Actions\User\TokenReset; +use App\Enum\CacheTag; +use App\Events\TaggedRouteCacheUpdated; use App\Exceptions\ModelDBException; use App\Exceptions\UnauthenticatedException; use App\Http\Requests\Profile\ChangeTokenRequest; @@ -53,6 +55,8 @@ public function update(UpdateProfileRequest $request, UpdateLogin $updateLogin): // to be unauthenticated upon the next request. Auth::login($currentUser); + TaggedRouteCacheUpdated::dispatch(CacheTag::USER); + return new UserResource($currentUser); } @@ -69,6 +73,8 @@ public function resetToken(ChangeTokenRequest $request, TokenReset $tokenReset): { $token = $tokenReset->do(); + TaggedRouteCacheUpdated::dispatch(CacheTag::USER); + return new UserToken($token); } @@ -82,6 +88,8 @@ public function resetToken(ChangeTokenRequest $request, TokenReset $tokenReset): */ public function unsetToken(ChangeTokenRequest $request, TokenDisable $tokenDisable): void { + TaggedRouteCacheUpdated::dispatch(CacheTag::USER); + $tokenDisable->do(); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index eebbcc8e4bc..84bc586e06a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -73,6 +73,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, 'response_cache', + 'album_cache_refresher', ], ]; @@ -92,10 +93,11 @@ class Kernel extends HttpKernel 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'login_required_v1' => \App\Legacy\V1\Middleware\LoginRequiredV1::class, // remove me in non-legacy build 'login_required' => \App\Http\Middleware\LoginRequired::class, - 'cache_control' => \App\Http\Middleware\CacheControl::class, + 'cache_control' => \App\Http\Middleware\Caching\CacheControl::class, 'support' => \LycheeVerify\Http\Middleware\VerifySupporterStatus::class, 'config_integrity' => \App\Http\Middleware\ConfigIntegrity::class, 'unlock_with_password' => \App\Http\Middleware\UnlockWithPassword::class, - 'response_cache' => \App\Http\Middleware\ResponseCache::class, + 'response_cache' => \App\Http\Middleware\Caching\ResponseCache::class, + 'album_cache_refresher' => \App\Http\Middleware\Caching\AlbumRouteCacheRefresher::class, ]; } diff --git a/app/Http/Middleware/Caching/AlbumRouteCacheRefresher.php b/app/Http/Middleware/Caching/AlbumRouteCacheRefresher.php new file mode 100644 index 00000000000..e2a010ab567 --- /dev/null +++ b/app/Http/Middleware/Caching/AlbumRouteCacheRefresher.php @@ -0,0 +1,124 @@ +method() === 'GET') { + return $next($request); + } + + if (Configs::getValueAsBool('cache_enabled') === false) { + return $next($request); + } + + // ! We use $except as a ALLOW list instead of a DENY list + if (!$this->inExceptArray($request)) { + return $next($request); + } + + $full_album_ids = collect(); + + /** @var string|null $album_id */ + $album_id = $request->input(RequestAttribute::ALBUM_ID_ATTRIBUTE); + if ($album_id !== null) { + $full_album_ids->push($album_id); + } + + /** @var string[]|null */ + $albums_id = $request->input(RequestAttribute::ALBUM_IDS_ATTRIBUTE); + if ($albums_id !== null) { + $full_album_ids = $full_album_ids->merge($albums_id); + } + + /** @var string|null */ + $parent_id = $request->input(RequestAttribute::PARENT_ID_ATTRIBUTE); + if ($parent_id !== null) { + $full_album_ids->push($parent_id); + } + + /** @var string|null */ + $photo_id = $request->input(RequestAttribute::PHOTO_ID_ATTRIBUTE); + /** @var string[]|null */ + $photo_ids = $request->input(RequestAttribute::PHOTO_IDS_ATTRIBUTE); + + if ($photo_ids !== null || $photo_id !== null) { + $photos_album_ids = DB::table('photos') + ->select('album_id') + ->whereIn('id', $photo_ids ?? []) + ->orWhere('id', '=', $photo_id) + ->distinct() + ->pluck('album_id') + ->all(); + if (count($photos_album_ids) > 0) { + $full_album_ids = $full_album_ids->merge($photos_album_ids); + } + } + + if ($albums_id !== null || $album_id !== null) { + $albums_parents_ids = DB::table('albums') + ->select('parent_id') + ->whereIn('id', $albums_id ?? []) + ->orWhere('id', '=', $album_id) + ->distinct() + ->pluck('parent_id') + ->all(); + if (count($albums_parents_ids) > 0) { + $full_album_ids = $full_album_ids->merge($albums_parents_ids); + } + } + + $full_album_ids->each(fn ($album_id) => AlbumRouteCacheUpdated::dispatch($album_id ?? '')); + + return $next($request); + } +} diff --git a/app/Http/Middleware/CacheControl.php b/app/Http/Middleware/Caching/CacheControl.php similarity index 90% rename from app/Http/Middleware/CacheControl.php rename to app/Http/Middleware/Caching/CacheControl.php index 637ad170b6b..a787b562b2f 100644 --- a/app/Http/Middleware/CacheControl.php +++ b/app/Http/Middleware/Caching/CacheControl.php @@ -1,8 +1,6 @@ route_cache_manager->getConfig($request->route()->uri); + $config = $this->route_cache_manager->get_config($request->route()->uri); // Check with the route manager if we can cache this route. if ($config === false) { @@ -72,7 +72,7 @@ private function chacheWithoutTags(Request $request, \Closure $next, RouteCacheC return $next($request); } - $key = $this->route_cache_manager->getKey($request, $config); + $key = $this->route_cache_manager->get_key($request, $config); return Cache::remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); } @@ -87,7 +87,7 @@ private function chacheWithoutTags(Request $request, \Closure $next, RouteCacheC */ private function cacheWithTags(Request $request, \Closure $next, RouteCacheConfig $config): mixed { - $key = $this->route_cache_manager->getKey($request, $config); + $key = $this->route_cache_manager->get_key($request, $config); return Cache::tags([$config->tag])->remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); } diff --git a/app/Listeners/AlbumCacheCleaner.php b/app/Listeners/AlbumCacheCleaner.php new file mode 100644 index 00000000000..9c01a1d3dc8 --- /dev/null +++ b/app/Listeners/AlbumCacheCleaner.php @@ -0,0 +1,120 @@ +value)->flush(); + + return; + } + + $this->dropCachedRoutesWithoutExtra(); + + // By default we refresh all the smart albums. + $this->handleSmartAlbums(); + + if ($event->album_id === null) { + $this->handleAllAlbums(); + + return; + } + + // Root album => already taken care of with the route without extra. + if ($event->album_id === '') { + return; + } + + $this->handleAlbumId($event->album_id); + } + + /** + * Drop cache for all routes without extra (meaning which do not depend on album_id). + * + * @return void + */ + private function dropCachedRoutesWithoutExtra(): void + { + $cached_routes_without_extra = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, without_extra: true); + foreach ($cached_routes_without_extra as $route) { + $cache_key = $this->route_cache_manager->gen_key(uri: $route); + Cache::forget($cache_key); + } + } + + /** + * Drop cache for all routes related to albums. + * + * @return void + */ + private function handleAllAlbums(): void + { + // The long way. + $cached_routes_with_extra = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); + BaseAlbumImpl::select('id')->get()->each(function (BaseAlbumImpl $album) use ($cached_routes_with_extra) { + $extra = ['album_id' => $album->id]; + foreach ($cached_routes_with_extra as $route) { + $cache_key = $this->route_cache_manager->gen_key(uri: $route, extras: $extra); + Cache::forget($cache_key); + } + }); + } + + /** + * Drop cache fro all routes related to an album. + * + * @param string $album_id + * + * @return void + */ + private function handleAlbumId(string $album_id): void + { + $cached_routes_with_extra = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); + $extra = ['album_id' => $album_id]; + + foreach ($cached_routes_with_extra as $route) { + $cache_key = $this->route_cache_manager->gen_key(uri: $route, extras: $extra); + Cache::forget($cache_key); + } + } + + /** + * Drop cache for all smart albums too. + * + * @return void + */ + private function handleSmartAlbums(): void + { + $cached_routes_with_extra = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); + // Also reset smart albums ;) + collect(SmartAlbumType::cases())->each(function (SmartAlbumType $type) use ($cached_routes_with_extra) { + $extra = ['album_id' => $type->value]; + foreach ($cached_routes_with_extra as $route) { + $cache_key = $this->route_cache_manager->gen_key(uri: $route, extras: $extra); + Cache::forget($cache_key); + } + }); + } +} diff --git a/app/Listeners/CacheListener.php b/app/Listeners/CacheListener.php index b00447ee949..15cae57391f 100644 --- a/app/Listeners/CacheListener.php +++ b/app/Listeners/CacheListener.php @@ -2,31 +2,31 @@ namespace App\Listeners; +use App\Models\Configs; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; +use Illuminate\Cache\Events\KeyForgotten; use Illuminate\Support\Facades\Log; class CacheListener { - /** - * Create the event listener. - */ - public function __construct() - { - } - /** * Handle the event. */ - public function handle(CacheHit|CacheMissed $event): void + public function handle(CacheHit|CacheMissed|KeyForgotten $event): void { if (str_contains($event->key, 'lv:dev-lycheeOrg')) { return; } + if (Configs::getValueAsBool('cache_event_logging') === false) { + return; + } + match (get_class($event)) { CacheMissed::class => Log::info('CacheListener: Miss for ' . $event->key), CacheHit::class => Log::info('CacheListener: Hit for ' . $event->key), + KeyForgotten::class => Log::info('CacheListener: Forgetting key ' . $event->key), default => '', }; } diff --git a/app/Listeners/TaggedRouteCacheCleaner.php b/app/Listeners/TaggedRouteCacheCleaner.php new file mode 100644 index 00000000000..290a1432e3c --- /dev/null +++ b/app/Listeners/TaggedRouteCacheCleaner.php @@ -0,0 +1,35 @@ +route_cache_manager->retrieve_keys_for_tag($event->tag); + + foreach ($cached_routes as $route) { + $cache_key = $this->route_cache_manager->gen_key($route); + Cache::forget($cache_key); + } + + if (Cache::supportsTags()) { + Cache::tags($event->tag->value)->flush(); + } + } +} diff --git a/app/Metadata/Cache/RouteCacheConfig.php b/app/Metadata/Cache/RouteCacheConfig.php index 6ff16813a01..951484c0e3a 100644 --- a/app/Metadata/Cache/RouteCacheConfig.php +++ b/app/Metadata/Cache/RouteCacheConfig.php @@ -2,19 +2,21 @@ namespace App\Metadata\Cache; +use App\Enum\CacheTag; + final readonly class RouteCacheConfig { /** * Configuration of a route caching. * - * @param string|null $tag tags to quickly find the keys that need to be cleared - * @param bool $user_dependant whether the route has data depending of the user - * @param string[] $extra extra parameters to be used in the cache key + * @param CacheTag|null $tag tags to quickly find the keys that need to be cleared + * @param bool $user_dependant whether the route has data depending of the user + * @param string[] $extra extra parameters to be used in the cache key * * @return void */ public function __construct( - public ?string $tag, + public ?CacheTag $tag, public bool $user_dependant = false, public array $extra = [], ) { diff --git a/app/Metadata/Cache/RouteCacheManager.php b/app/Metadata/Cache/RouteCacheManager.php index 0f64e347ada..744d5dcca4b 100644 --- a/app/Metadata/Cache/RouteCacheManager.php +++ b/app/Metadata/Cache/RouteCacheManager.php @@ -2,8 +2,10 @@ namespace App\Metadata\Cache; +use App\Contracts\Http\Requests\RequestAttribute; +use App\Enum\CacheTag; +use App\Exceptions\Internal\LycheeLogicException; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; final readonly class RouteCacheManager @@ -23,28 +25,28 @@ public function __construct() { $this->cache_list = [ - 'api/v2/Album' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), + 'api/v2/Album' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: [RequestAttribute::ALBUM_ID_ATTRIBUTE]), 'api/v2/Album::getTargetListAlbums' => false, // TODO: cache me later. - 'api/v2/Albums' => new RouteCacheConfig(tag: 'gallery', user_dependant: true), - 'api/v2/Auth::config' => new RouteCacheConfig(tag: 'auth', user_dependant: true), - 'api/v2/Auth::rights' => new RouteCacheConfig(tag: 'auth', user_dependant: true), - 'api/v2/Auth::user' => new RouteCacheConfig(tag: 'user', user_dependant: true), + 'api/v2/Albums' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true), + 'api/v2/Auth::config' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), + 'api/v2/Auth::rights' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), + 'api/v2/Auth::user' => new RouteCacheConfig(tag: CacheTag::USER, user_dependant: true), 'api/v2/Diagnostics' => false, 'api/v2/Diagnostics::config' => false, - 'api/v2/Diagnostics::info' => new RouteCacheConfig(tag: 'settings', user_dependant: true), - 'api/v2/Diagnostics::permissions' => new RouteCacheConfig(tag: 'settings', user_dependant: true), - 'api/v2/Diagnostics::space' => new RouteCacheConfig(tag: 'settings', user_dependant: true), + 'api/v2/Diagnostics::info' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), + 'api/v2/Diagnostics::permissions' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), + 'api/v2/Diagnostics::space' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), // Response must be different for each call. 'api/v2/Frame' => false, - 'api/v2/Gallery::Footer' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Gallery::Init' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Gallery::getLayout' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Gallery::getUploadLimits' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/Gallery::Footer' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Gallery::Init' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Gallery::getLayout' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Gallery::getUploadLimits' => new RouteCacheConfig(tag: CacheTag::SETTINGS), 'api/v2/Jobs' => false, // TODO: fix me later - 'api/v2/LandingPage' => new RouteCacheConfig(tag: 'settings'), + 'api/v2/LandingPage' => new RouteCacheConfig(tag: CacheTag::SETTINGS), // We do not need to cache those. 'api/v2/Maintenance::cleaning' => false, @@ -55,26 +57,26 @@ public function __construct() 'api/v2/Maintenance::tree' => false, 'api/v2/Maintenance::update' => false, - 'api/v2/Map' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), - 'api/v2/Map::provider' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Oauth' => new RouteCacheConfig(tag: 'user', user_dependant: true), + 'api/v2/Map' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: [RequestAttribute::ALBUM_ID_ATTRIBUTE]), + 'api/v2/Map::provider' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Oauth' => new RouteCacheConfig(tag: CacheTag::USER, user_dependant: true), // Response must be different for each call. 'api/v2/Photo::random' => false, - 'api/v2/Search' => false, // TODO: how to support pagination ?? new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id', 'terms']), - 'api/v2/Search::init' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Settings' => new RouteCacheConfig(tag: 'settings', user_dependant: true), - 'api/v2/Settings::getLanguages' => new RouteCacheConfig(tag: 'settings'), - 'api/v2/Sharing' => new RouteCacheConfig(tag: 'gallery', user_dependant: true, extra: ['album_id']), - 'api/v2/Sharing::all' => new RouteCacheConfig(tag: 'gallery', user_dependant: true), - 'api/v2/Statistics::albumSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), - 'api/v2/Statistics::sizeVariantSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), - 'api/v2/Statistics::totalAlbumSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), - 'api/v2/Statistics::userSpace' => new RouteCacheConfig(tag: 'statistics', user_dependant: true), - 'api/v2/UserManagement' => new RouteCacheConfig(tag: 'users', user_dependant: true), - 'api/v2/Users' => new RouteCacheConfig(tag: 'users', user_dependant: true), - 'api/v2/Users::count' => new RouteCacheConfig(tag: 'users', user_dependant: true), + 'api/v2/Search' => false, // TODO: how to support pagination ?? new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: ['album_id', 'terms']), + 'api/v2/Search::init' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Settings' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), + 'api/v2/Settings::getLanguages' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Sharing' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: [RequestAttribute::ALBUM_ID_ATTRIBUTE]), + 'api/v2/Sharing::all' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true), + 'api/v2/Statistics::albumSpace' => new RouteCacheConfig(tag: CacheTag::STATISTICS, user_dependant: true), + 'api/v2/Statistics::sizeVariantSpace' => new RouteCacheConfig(tag: CacheTag::STATISTICS, user_dependant: true), + 'api/v2/Statistics::totalAlbumSpace' => new RouteCacheConfig(tag: CacheTag::STATISTICS, user_dependant: true), + 'api/v2/Statistics::userSpace' => new RouteCacheConfig(tag: CacheTag::STATISTICS, user_dependant: true), + 'api/v2/UserManagement' => new RouteCacheConfig(tag: CacheTag::USERS, user_dependant: true), + 'api/v2/Users' => new RouteCacheConfig(tag: CacheTag::USERS, user_dependant: true), + 'api/v2/Users::count' => new RouteCacheConfig(tag: CacheTag::USERS, user_dependant: true), 'api/v2/Version' => false, 'api/v2/WebAuthn' => false, @@ -83,7 +85,7 @@ public function __construct() ]; } - public function getConfig(string $uri): RouteCacheConfig|false + public function get_config(string $uri): RouteCacheConfig|false { if (!array_key_exists($uri, $this->cache_list)) { Log::warning('ResponseCache: No cache config for ' . $uri); @@ -94,7 +96,7 @@ public function getConfig(string $uri): RouteCacheConfig|false return $this->cache_list[$uri]; } - public function getKey(Request $request, RouteCacheConfig $config): string + public function get_key(Request $request, RouteCacheConfig $config): string { $key = self::REQUEST . $request->route()->uri; @@ -119,15 +121,25 @@ public function getKey(Request $request, RouteCacheConfig $config): string /** * Generate a key for the cache. * - * @param RouteCacheConfig $config * @param string $uri * @param int|null $userId * @param array $extras + * @param ?RouteCacheConfig $config * * @return string */ - public function genKey(RouteCacheConfig $config, string $uri, ?int $userId, array $extras): string - { + public function gen_key( + string $uri, + ?int $userId = null, + array $extras = [], + ?RouteCacheConfig $config = null, + ): string { + $config ??= $this->cache_list[$uri] ?? throw new LycheeLogicException('No cache config for ' . $uri); + + if ($config === false) { + throw new LycheeLogicException($uri . ' is not supposed to be cached.'); + } + $key = self::REQUEST . $uri; // If the request is user dependant, we add the user id to the key. @@ -152,9 +164,9 @@ public function genKey(RouteCacheConfig $config, string $uri, ?int $userId, arra * * @param string $uri * - * @return string|false + * @return CacheTag|false */ - public function getTag(string $uri): string|false + public function get_tag(string $uri): CacheTag|false { if (!array_key_exists($uri, $this->cache_list)) { return false; @@ -170,29 +182,26 @@ public function getTag(string $uri): string|false /** * Given a tag, return all the routes associated to this tag. * - * @param string $tag + * @param CacheTag $tag * * @return string[] */ - public function retrieveKeysForTag(string $tag): array + public function retrieve_keys_for_tag(CacheTag $tag, bool $with_extra = false, bool $without_extra = false): array { $keys = []; foreach ($this->cache_list as $uri => $value) { - if (is_array($value) && array_key_exists('tag', $value) && $value['tag'] === $tag) { + if ( + $value !== false && + $value->tag === $tag && + // Either with extra is set to false => ignore condition + // Or with extra is set to true and we have extra parameters => ignore condition + ($with_extra === false || count($value->extra) > 0) && + ($without_extra === false || count($value->extra) === 0) + ) { $keys[] = $uri; } } return $keys; } - - public function clearTag(string $tag): void - { - $keys = $this->retrieveKeysForTag($tag); - // TODO: refine. - - foreach ($keys as $key) { - Cache::forget($key); - } - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bfb44eb1ff0..a4c687ae7db 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -8,10 +8,14 @@ use App\Assets\SizeVariantGroupedWithRandomSuffixNamingStrategy; use App\Contracts\Models\AbstractSizeVariantNamingStrategy; use App\Contracts\Models\SizeVariantFactory; +use App\Events\AlbumRouteCacheUpdated; +use App\Events\TaggedRouteCacheUpdated; use App\Factories\AlbumFactory; use App\Image\SizeVariantDefaultFactory; use App\Image\StreamStatFilter; +use App\Listeners\AlbumCacheCleaner; use App\Listeners\CacheListener; +use App\Listeners\TaggedRouteCacheCleaner; use App\Metadata\Json\CommitsRequest; use App\Metadata\Json\UpdateRequest; use App\Metadata\Versions\FileVersion; @@ -26,6 +30,7 @@ use App\Policies\SettingsPolicy; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; +use Illuminate\Cache\Events\KeyForgotten; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Http\Resources\Json\JsonResource; @@ -93,6 +98,10 @@ public function boot() { Event::listen(CacheHit::class, CacheListener::class . '@handle'); Event::listen(CacheMissed::class, CacheListener::class . '@handle'); + Event::listen(KeyForgotten::class, CacheListener::class . '@handle'); + + Event::listen(AlbumRouteCacheUpdated::class, AlbumCacheCleaner::class . '@handle'); + Event::listen(TaggedRouteCacheUpdated::class, TaggedRouteCacheCleaner::class . '@handle'); // Prohibits: db:wipe, migrate:fresh, migrate:refresh, and migrate:reset DB::prohibitDestructiveCommands(config('app.env', 'production') !== 'dev'); diff --git a/database/migrations/2024_12_28_190150_caching_config.php b/database/migrations/2024_12_28_190150_caching_config.php index 2c99c740f9c..02fc4ce0ad8 100644 --- a/database/migrations/2024_12_28_190150_caching_config.php +++ b/database/migrations/2024_12_28_190150_caching_config.php @@ -16,6 +16,16 @@ public function getConfigs(): array 'is_secret' => false, 'level' => 0, ], + [ + 'key' => 'cache_event_logging', + 'value' => '1', // TODO: flip to false + 'cat' => 'Mod Cache', + 'type_range' => self::BOOL, + 'description' => 'Add log lines for events related to caching.', + 'details' => 'This may result in large amount of logs', + 'is_secret' => true, + 'level' => 0, + ], [ 'key' => 'cache_ttl', 'value' => '60', diff --git a/phpstan.neon b/phpstan.neon index 61da8eac1f3..f683464b442 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -126,7 +126,7 @@ parameters: # paths: # - tests - - message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(Is)?(Not)?(True|False|Equals|Int|Null|Empty|Count)\(\)#' + message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(Is)?(Not)?(True|False|Equals|Int|Null|Empty|Count|Array|Object)\(\)#' paths: - tests - @@ -134,7 +134,7 @@ parameters: paths: - tests - - message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assertString(Not)?(Contains|Ends|Starts)(String|With)\(\)#' + message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(String)?(Not)?(Contains|Ends|Starts)(String|With)?\(\)#' paths: - tests - diff --git a/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php b/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php new file mode 100644 index 00000000000..f642f0c6de1 --- /dev/null +++ b/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php @@ -0,0 +1,78 @@ +route_cache_manager = new RouteCacheManager(); + } + + public function testNoConfig(): void + { + Log::shouldReceive('warning')->once(); + $this->assertFalse($this->route_cache_manager->get_config('fake_url')); + } + + public function testConfigFalse(): void + { + $this->assertFalse($this->route_cache_manager->get_config('api/v2/Version')); + } + + public function testConfigValid(): void + { + $this->assertIsObject($this->route_cache_manager->get_config('api/v2/Album')); + } + + public function testGenKey(): void + { + $this->assertEquals('R:api/v2/Albums|U:', $this->route_cache_manager->gen_key('api/v2/Albums')); + $this->assertEquals('R:api/v2/Albums|U:1', $this->route_cache_manager->gen_key('api/v2/Albums', 1)); + $this->assertEquals('R:api/v2/Album|U:1|E::2', $this->route_cache_manager->gen_key('api/v2/Album', 1, ['album_id' => '2'])); + } + + public function testGetFromTag(): void + { + $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY); + $this->assertIsArray($routes); + $this->assertContains('api/v2/Album', $routes); + } + + public function testGetFromTagWithExtra(): void + { + $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); + $this->assertIsArray($routes); + $this->assertContains('api/v2/Album', $routes); + $this->assertNotContains('api/v2/Albums', $routes); + } + + public function testGetFromTagWithOutExtra(): void + { + $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, without_extra: true); + $this->assertIsArray($routes); + $this->assertContains('api/v2/Albums', $routes); + $this->assertNotContains('api/v2/Album', $routes); + } +} + From adf0a7004f895cbe8257eab269ab16948ca88d29 Mon Sep 17 00:00:00 2001 From: ildyria Date: Mon, 30 Dec 2024 02:05:27 +0100 Subject: [PATCH 5/7] fix cache clearing --- app/Http/Controllers/Gallery/AlbumController.php | 5 ----- app/Http/Middleware/Caching/ResponseCache.php | 2 +- app/Listeners/AlbumCacheCleaner.php | 6 +++--- app/Metadata/Cache/RouteCacheManager.php | 7 ++++--- phpunit.xml | 4 ++-- tests/Feature_v2/Statistics/AlbumSpaceTest.php | 4 ++++ 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Gallery/AlbumController.php b/app/Http/Controllers/Gallery/AlbumController.php index dc5963440af..42debd71f2b 100644 --- a/app/Http/Controllers/Gallery/AlbumController.php +++ b/app/Http/Controllers/Gallery/AlbumController.php @@ -240,11 +240,6 @@ private function updateProtectionPolicyBase( */ public function delete(DeleteAlbumsRequest $request, Delete $delete): void { - Album::select('parent_id') - ->whereIn('id', $request->albumIds()) - ->get() - ->each(fn (Album $album) => AlbumRouteCacheUpdated::dispatch($album->parent_id ?? '')); - $fileDeleter = $delete->do($request->albumIds()); App::terminating(fn () => $fileDeleter->do()); } diff --git a/app/Http/Middleware/Caching/ResponseCache.php b/app/Http/Middleware/Caching/ResponseCache.php index 2c71eefaf15..c00a828b75a 100644 --- a/app/Http/Middleware/Caching/ResponseCache.php +++ b/app/Http/Middleware/Caching/ResponseCache.php @@ -89,6 +89,6 @@ private function cacheWithTags(Request $request, \Closure $next, RouteCacheConfi { $key = $this->route_cache_manager->get_key($request, $config); - return Cache::tags([$config->tag])->remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); + return Cache::tags([$config->tag->value])->remember($key, Configs::getValueAsInt('cache_ttl'), fn () => $next($request)); } } diff --git a/app/Listeners/AlbumCacheCleaner.php b/app/Listeners/AlbumCacheCleaner.php index 9c01a1d3dc8..85af4b890d6 100644 --- a/app/Listeners/AlbumCacheCleaner.php +++ b/app/Listeners/AlbumCacheCleaner.php @@ -6,8 +6,8 @@ use App\Enum\SmartAlbumType; use App\Events\AlbumRouteCacheUpdated; use App\Metadata\Cache\RouteCacheManager; -use App\Models\BaseAlbumImpl; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\DB; class AlbumCacheCleaner { @@ -73,8 +73,8 @@ private function handleAllAlbums(): void { // The long way. $cached_routes_with_extra = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); - BaseAlbumImpl::select('id')->get()->each(function (BaseAlbumImpl $album) use ($cached_routes_with_extra) { - $extra = ['album_id' => $album->id]; + DB::table('base_albums')->select('id')->pluck('id')->each(function ($id) use ($cached_routes_with_extra) { + $extra = ['album_id' => $id]; foreach ($cached_routes_with_extra as $route) { $cache_key = $this->route_cache_manager->gen_key(uri: $route, extras: $extra); Cache::forget($cache_key); diff --git a/app/Metadata/Cache/RouteCacheManager.php b/app/Metadata/Cache/RouteCacheManager.php index 744d5dcca4b..d1b16162942 100644 --- a/app/Metadata/Cache/RouteCacheManager.php +++ b/app/Metadata/Cache/RouteCacheManager.php @@ -6,6 +6,7 @@ use App\Enum\CacheTag; use App\Exceptions\Internal\LycheeLogicException; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; final readonly class RouteCacheManager @@ -65,7 +66,7 @@ public function __construct() 'api/v2/Photo::random' => false, 'api/v2/Search' => false, // TODO: how to support pagination ?? new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: ['album_id', 'terms']), - 'api/v2/Search::init' => new RouteCacheConfig(tag: CacheTag::SETTINGS), + 'api/v2/Search::init' => false, 'api/v2/Settings' => new RouteCacheConfig(tag: CacheTag::SETTINGS, user_dependant: true), 'api/v2/Settings::getLanguages' => new RouteCacheConfig(tag: CacheTag::SETTINGS), 'api/v2/Sharing' => new RouteCacheConfig(tag: CacheTag::GALLERY, user_dependant: true, extra: [RequestAttribute::ALBUM_ID_ATTRIBUTE]), @@ -103,14 +104,14 @@ public function get_key(Request $request, RouteCacheConfig $config): string // If the request is user dependant, we add the user id to the key. // That way we ensure that this does not contaminate between logged in and looged out users. if ($config->user_dependant) { - $key .= self::USER . $request->user?->getId(); + $key .= self::USER . Auth::id(); } if (count($config->extra) > 0) { $key .= self::EXTRA; foreach ($config->extra as $extra) { /** @var string $vals */ - $vals = $request->query($extra) ?? ''; + $vals = $request->input($extra) ?? ''; $key .= ':' . $vals; } } diff --git a/phpunit.xml b/phpunit.xml index 3665cc969dd..6c74fe3e435 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -37,8 +37,8 @@ - - + + diff --git a/tests/Feature_v2/Statistics/AlbumSpaceTest.php b/tests/Feature_v2/Statistics/AlbumSpaceTest.php index 466b09786f9..1cea0abada6 100644 --- a/tests/Feature_v2/Statistics/AlbumSpaceTest.php +++ b/tests/Feature_v2/Statistics/AlbumSpaceTest.php @@ -14,6 +14,7 @@ namespace Tests\Feature_v2\Statistics; +use App\Models\Configs; use LycheeVerify\Http\Middleware\VerifySupporterStatus; use Tests\Feature_v2\Base\BaseApiV2Test; @@ -21,6 +22,9 @@ class AlbumSpaceTest extends BaseApiV2Test { public function testAlbumSpaceTestUnauthorized(): void { + Configs::set('cache_enabled', '0'); + Configs::invalidateCache(); + $response = $this->getJson('Statistics::albumSpace'); $this->assertSupporterRequired($response); From 454885a7fad5f931cf224707c65042d40cb4b118 Mon Sep 17 00:00:00 2001 From: ildyria Date: Mon, 30 Dec 2024 02:14:04 +0100 Subject: [PATCH 6/7] fix legacy phpstan --- phpstan.neon | 4 +-- .../Metadata/Cache/RouteCacheManagerTest.php | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index f683464b442..61da8eac1f3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -126,7 +126,7 @@ parameters: # paths: # - tests - - message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(Is)?(Not)?(True|False|Equals|Int|Null|Empty|Count|Array|Object)\(\)#' + message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(Is)?(Not)?(True|False|Equals|Int|Null|Empty|Count)\(\)#' paths: - tests - @@ -134,7 +134,7 @@ parameters: paths: - tests - - message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assert(String)?(Not)?(Contains|Ends|Starts)(String|With)?\(\)#' + message: '#Dynamic call to static method PHPUnit\\Framework\\Assert::assertString(Not)?(Contains|Ends|Starts)(String|With)\(\)#' paths: - tests - diff --git a/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php b/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php index f642f0c6de1..72e7d2ced5f 100644 --- a/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php +++ b/tests/Unit/Metadata/Cache/RouteCacheManagerTest.php @@ -32,47 +32,47 @@ public function setUp(): void public function testNoConfig(): void { Log::shouldReceive('warning')->once(); - $this->assertFalse($this->route_cache_manager->get_config('fake_url')); + self::assertFalse($this->route_cache_manager->get_config('fake_url')); } public function testConfigFalse(): void { - $this->assertFalse($this->route_cache_manager->get_config('api/v2/Version')); + self::assertFalse($this->route_cache_manager->get_config('api/v2/Version')); } public function testConfigValid(): void { - $this->assertIsObject($this->route_cache_manager->get_config('api/v2/Album')); + self::assertIsObject($this->route_cache_manager->get_config('api/v2/Album')); } public function testGenKey(): void { - $this->assertEquals('R:api/v2/Albums|U:', $this->route_cache_manager->gen_key('api/v2/Albums')); - $this->assertEquals('R:api/v2/Albums|U:1', $this->route_cache_manager->gen_key('api/v2/Albums', 1)); - $this->assertEquals('R:api/v2/Album|U:1|E::2', $this->route_cache_manager->gen_key('api/v2/Album', 1, ['album_id' => '2'])); + self::assertEquals('R:api/v2/Albums|U:', $this->route_cache_manager->gen_key('api/v2/Albums')); + self::assertEquals('R:api/v2/Albums|U:1', $this->route_cache_manager->gen_key('api/v2/Albums', 1)); + self::assertEquals('R:api/v2/Album|U:1|E::2', $this->route_cache_manager->gen_key('api/v2/Album', 1, ['album_id' => '2'])); } public function testGetFromTag(): void { $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY); - $this->assertIsArray($routes); - $this->assertContains('api/v2/Album', $routes); + self::assertIsArray($routes); + self::assertContains('api/v2/Album', $routes); } public function testGetFromTagWithExtra(): void { $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, with_extra: true); - $this->assertIsArray($routes); - $this->assertContains('api/v2/Album', $routes); - $this->assertNotContains('api/v2/Albums', $routes); + self::assertIsArray($routes); + self::assertContains('api/v2/Album', $routes); + self::assertNotContains('api/v2/Albums', $routes); } public function testGetFromTagWithOutExtra(): void { $routes = $this->route_cache_manager->retrieve_keys_for_tag(CacheTag::GALLERY, without_extra: true); - $this->assertIsArray($routes); - $this->assertContains('api/v2/Albums', $routes); - $this->assertNotContains('api/v2/Album', $routes); + self::assertIsArray($routes); + self::assertContains('api/v2/Albums', $routes); + self::assertNotContains('api/v2/Album', $routes); } } From d88fa693d5a3fbaf712732e4aa994164ec9f2427 Mon Sep 17 00:00:00 2001 From: ildyria Date: Mon, 30 Dec 2024 12:53:25 +0100 Subject: [PATCH 7/7] fix database --- .../migrations/2024_12_28_190150_caching_config.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/database/migrations/2024_12_28_190150_caching_config.php b/database/migrations/2024_12_28_190150_caching_config.php index 02fc4ce0ad8..d7ff6d3b9bc 100644 --- a/database/migrations/2024_12_28_190150_caching_config.php +++ b/database/migrations/2024_12_28_190150_caching_config.php @@ -8,17 +8,17 @@ public function getConfigs(): array return [ [ 'key' => 'cache_enabled', - 'value' => '1', + 'value' => '0', 'cat' => 'Mod Cache', 'type_range' => self::BOOL, 'description' => 'Enable caching of responses given requests.', - 'details' => 'This will significantly speed up the response time of Lychee.', + 'details' => 'This will significantly speed up the response time of Lychee. If you are using password protected albums, you should not enable this.', 'is_secret' => false, 'level' => 0, ], [ 'key' => 'cache_event_logging', - 'value' => '1', // TODO: flip to false + 'value' => '0', 'cat' => 'Mod Cache', 'type_range' => self::BOOL, 'description' => 'Add log lines for events related to caching.', @@ -28,11 +28,11 @@ public function getConfigs(): array ], [ 'key' => 'cache_ttl', - 'value' => '60', + 'value' => '300', 'cat' => 'Mod Cache', 'type_range' => self::POSITIVE, 'description' => 'Number of seconds responses should be cached.', - 'details' => '', + 'details' => 'Longer TTL will save more resources but may result in outdated responses.', 'is_secret' => false, 'level' => 1, ],