From 23fd4fd60bb1db870f04452964c2d3b5b3854f0e Mon Sep 17 00:00:00 2001 From: Albin N Date: Mon, 22 Apr 2024 14:55:58 +0200 Subject: [PATCH] caching improvements, enable webhooks support --- composer.json | 5 +- config/fabriq.php | 6 ++ src/EventServiceProvider.php | 3 + src/FabriqCoreServiceProvider.php | 2 +- .../Api/Fabriq/ContactController.php | 5 +- src/Listeners/BustPageCacheListener.php | 7 +- src/Listeners/CallCacheBustingWebhook.php | 84 +++++++++++++++++++ src/Models/Article.php | 4 +- src/Models/Contact.php | 4 +- src/Models/MenuItem.php | 11 +-- src/Models/Page.php | 5 +- src/Models/SmartBlock.php | 11 +-- .../Decorators/CachingMenuRepository.php | 5 +- .../Decorators/CachingPageRepository.php | 13 ++- tests/MenuItemFeatureTest.php | 4 +- 15 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 src/Listeners/CallCacheBustingWebhook.php diff --git a/composer.json b/composer.json index b2978f3..5eb1f3d 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ "spatie/once": "^2.0|^3.0", "pusher/pusher-php-server": "^7.0", "aws/aws-sdk-php": "^3.219", - "laravel/pint": "^1.2" + "laravel/pint": "^1.2", + "spatie/laravel-webhook-server": "^3.8" }, "require-dev": { "phpunit/phpunit": "^10.0", @@ -69,4 +70,4 @@ "url": "https://github.com/KarabinSE/laravel-make-user" } } -} \ No newline at end of file +} diff --git a/config/fabriq.php b/config/fabriq.php index 273ef10..bff955c 100644 --- a/config/fabriq.php +++ b/config/fabriq.php @@ -169,4 +169,10 @@ 'ui' => [ 'large_block_picker' => false, ], + + 'webhooks' => [ + 'enabled' => true, + 'secret' => env('FABRIQ_WEBHOOK_SECRET'), + 'endpoint' => env('FABRIQ_WEBHOOK_ENDPOINT'), + ], ]; diff --git a/src/EventServiceProvider.php b/src/EventServiceProvider.php index 9eca8eb..86b9ee4 100644 --- a/src/EventServiceProvider.php +++ b/src/EventServiceProvider.php @@ -3,6 +3,7 @@ namespace Ikoncept\Fabriq; use Ikoncept\Fabriq\Listeners\BustPageCacheListener; +use Ikoncept\Fabriq\Listeners\CallCacheBustingWebhook; use Ikoncept\Fabriq\Listeners\FlushTagCacheListener; use Ikoncept\Fabriq\Listeners\UpdateSlugListener; use Illuminate\Auth\Events\Registered; @@ -21,9 +22,11 @@ class EventServiceProvider extends ServiceProvider ], DefinitionsUpdated::class => [ UpdateSlugListener::class, + CallCacheBustingWebhook::class, ], DefinitionsPublished::class => [ BustPageCacheListener::class, + CallCacheBustingWebhook::class, ], TranslatedRevisionDeleted::class => [ FlushTagCacheListener::class, diff --git a/src/FabriqCoreServiceProvider.php b/src/FabriqCoreServiceProvider.php index 790058e..089d3fe 100644 --- a/src/FabriqCoreServiceProvider.php +++ b/src/FabriqCoreServiceProvider.php @@ -49,7 +49,7 @@ public function boot() $this->publishes([ __DIR__.'/../config/fortify.php' => config_path('fortify.php'), - ], 'config'); + ], 'fortify-config'); $this->loadMigrationsFrom([realpath(__DIR__.'/../database/migrations')]); diff --git a/src/Http/Controllers/Api/Fabriq/ContactController.php b/src/Http/Controllers/Api/Fabriq/ContactController.php index 0b5311c..0f19efe 100644 --- a/src/Http/Controllers/Api/Fabriq/ContactController.php +++ b/src/Http/Controllers/Api/Fabriq/ContactController.php @@ -18,9 +18,6 @@ class ContactController extends ApiController /** * Returns an index of contacts. - * - * @param Request $request - * @return JsonResponse */ public function index(Request $request): JsonResponse { @@ -68,7 +65,7 @@ public function update(UpdateContactRequest $request, int $id): JsonResponse 'enabled_locales' => $request->content['enabled_locales'] ?? [], ], $request->input('locale', app()->getLocale())); - $contact->save(); + $contact->saveQuietly(); return $this->respondWithItem($contact, Fabriq::getTransformerFor('contact')); } diff --git a/src/Listeners/BustPageCacheListener.php b/src/Listeners/BustPageCacheListener.php index 2a7d5b3..4bab994 100644 --- a/src/Listeners/BustPageCacheListener.php +++ b/src/Listeners/BustPageCacheListener.php @@ -22,7 +22,6 @@ public function __construct() /** * Handle the event. * - * @param DefinitionsPublished $event * @return void */ public function handle(DefinitionsPublished $event) @@ -30,15 +29,15 @@ public function handle(DefinitionsPublished $event) $tagName = Str::lower(class_basename($event->model)); Log::info('Flushing menu cache'); - Cache::tags('cms_menu')->flush(); + Cache::tags('fabriq_menu')->flush(); if (! $event->model->slugs) { return; } foreach ($event->model->slugs as $slug) { - Log::info('Flushing page cache', ['name' => $event->model->name, 'key' => 'cms_'.$tagName.'_'.$slug->slug]); - Cache::tags('cms_'.$tagName.'_'.$slug->slug)->flush(); + Log::info('Flushing page cache', ['name' => $event->model->name, 'key' => 'fabriq_'.$tagName.'_'.$slug->slug]); + Cache::tags('fabriq_'.$tagName.'_'.$slug->slug)->flush(); } } } diff --git a/src/Listeners/CallCacheBustingWebhook.php b/src/Listeners/CallCacheBustingWebhook.php new file mode 100644 index 0000000..613f4ad --- /dev/null +++ b/src/Listeners/CallCacheBustingWebhook.php @@ -0,0 +1,84 @@ +model; + + if (! config('fabriq.webhooks.enabled')) { + return; + } + + // Case for pages, skip busting on update + if (get_class($event) === DefinitionsUpdated::class && Fabriq::getFqnModel('page') === get_class($model)) { + return; + } + + $tagsToFlush = collect($event->model->getRevisionOptions()->cacheTagsToFlush)->map(function ($tag) use ($model) { + $parts = explode('|', $tag); + if (isset($parts[1])) { + $key = $parts[1]; + + if ($key === 'slug' && $model->slugs) { + + $slugs = collect([]); + foreach ($model->slugs as $slug) { + $slugs->push("{$parts[0]}_{$parts[1]}_{$slug->slug}"); + } + + return $slugs->toArray(); + } + + return "{$parts[0]}_{$parts[1]}_{$model->$key}"; + } + + return $tag; + })->flatten(); + + if (! $tagsToFlush->count()) { + return; + } + + // 1 per 5 seconds for the same key + RateLimiter::attempt( + key: hash('adler32', json_encode($tagsToFlush)), + maxAttempts: 1, + callback: function () use ($tagsToFlush) { + WebhookCall::create() + ->url(config('fabriq.webhooks.endpoint')) + ->payload([ + 'type' => 'cache_expiration', + 'invalid_cache_tags' => $tagsToFlush->toArray(), + ]) + ->useSecret(config('fabriq.webhooks.secret')) + ->dispatch(); + }, + decaySeconds: 1 + ); + } +} diff --git a/src/Models/Article.php b/src/Models/Article.php index e8fb520..dda9fe8 100644 --- a/src/Models/Article.php +++ b/src/Models/Article.php @@ -18,7 +18,7 @@ class Article extends Model { - use HasFactory, HasTranslatedRevisions, BroadcastsEvents; + use BroadcastsEvents, HasFactory, HasTranslatedRevisions; public const RELATIONSHIPS = ['template', 'template.fields', 'slugs']; @@ -59,7 +59,7 @@ public function getRevisionOptions(): RevisionOptions return RevisionOptions::create() ->registerDefaultTemplate('article') ->registerSpecialTypes(['image']) - ->registerCacheTagsToFlush(['cms_articles']) + ->registerCacheTagsToFlush(['fabriq_articles|slug']) ->registerGetters([ 'image' => 'getImages', ]); diff --git a/src/Models/Contact.php b/src/Models/Contact.php index 6487288..cfa9d49 100644 --- a/src/Models/Contact.php +++ b/src/Models/Contact.php @@ -17,7 +17,7 @@ class Contact extends Model { - use HasFactory, HasTranslatedRevisions, HasTags, BroadcastsEvents; + use BroadcastsEvents, HasFactory, HasTags, HasTranslatedRevisions; public const RELATIONSHIPS = ['images', 'tags']; @@ -46,7 +46,7 @@ public function getRevisionOptions(): RevisionOptions return RevisionOptions::create() ->registerDefaultTemplate('contact') ->registerSpecialTypes(['image']) - ->registerCacheTagsToFlush(['cms_contacts']) + ->registerCacheTagsToFlush(['fabriq_contacts']) ->registerGetters([ 'image' => 'getImages', ]); diff --git a/src/Models/MenuItem.php b/src/Models/MenuItem.php index c3e2f36..a8ff141 100644 --- a/src/Models/MenuItem.php +++ b/src/Models/MenuItem.php @@ -15,7 +15,7 @@ class MenuItem extends Model { - use HasFactory, NodeTrait, HasTranslatedRevisions; + use HasFactory, HasTranslatedRevisions, NodeTrait; public const RELATIONSHIPS = ['page']; @@ -66,7 +66,7 @@ public function getRevisionOptions(): RevisionOptions { return RevisionOptions::create() ->registerDefaultTemplate('menu-item') - ->registerCacheTagsToFlush(['cms_menu']); + ->registerCacheTagsToFlush(['fabriq_menu']); } public function page(): BelongsTo @@ -76,8 +76,6 @@ public function page(): BelongsTo /** * Get the title attribute. - * - * @return string */ public function getTitleAttribute(): string { @@ -103,7 +101,6 @@ public function getSlugString(string $locale = '') /** * Get the slug. * - * @param string $locale * @return mixed */ public function getSlug(string $locale = '') @@ -129,7 +126,7 @@ public function getRelativePathAttribute(): string return; } - return $carry.'/'.$subItem->getSlugString(); + return $carry.'/'.$subItem->getSlugString(); }, '').'/'.$this->getSlugString(); } @@ -140,7 +137,6 @@ public function getRelativePathAttribute(): string * Skip setting title. * * @param mixed $value - * @return void */ public function setTitleAttribute($value): void { @@ -150,7 +146,6 @@ public function setTitleAttribute($value): void * Skip setting page attribute. * * @param mixed $value - * @return void */ public function setPageAttribute($value): void { diff --git a/src/Models/Page.php b/src/Models/Page.php index 7a35e37..d0e16e0 100644 --- a/src/Models/Page.php +++ b/src/Models/Page.php @@ -30,7 +30,7 @@ class Page extends Model implements HasMedia { - use HasFactory, HasTranslatedRevisions, InteractsWithMedia, NodeTrait, Commentable, HasPaths, BroadcastsModelEvents; + use BroadcastsModelEvents, Commentable, HasFactory, HasPaths, HasTranslatedRevisions, InteractsWithMedia, NodeTrait; public const RELATIONSHIPS = ['template', 'template.fields']; @@ -113,7 +113,8 @@ public function getRevisionOptions(): RevisionOptions 'button' => 'getButton', 'buttons' => 'getButtons', 'smartBlock' => 'getSmartBlock', - ]); + ]) + ->registerCacheTagsToFlush(['fabriq_menu', 'fabriq_pages|slug']); } /** diff --git a/src/Models/SmartBlock.php b/src/Models/SmartBlock.php index 4528a71..f7e472f 100644 --- a/src/Models/SmartBlock.php +++ b/src/Models/SmartBlock.php @@ -49,7 +49,7 @@ public function getRevisionOptions(): RevisionOptions 'video' => 'getVideos', ]) ->registerDefaultTemplate('smart_block') - ->registerCacheTagsToFlush(['cms_pages', 'cms_smart_blocks']); + ->registerCacheTagsToFlush(['fabriq_pages', 'fabriq_smart_blocks']); } /** @@ -66,7 +66,6 @@ public function setLocalizedContentAttribute($value) } /** - * @param RevisionMeta $meta * @return mixed */ public function getImages(RevisionMeta $meta) @@ -75,7 +74,6 @@ public function getImages(RevisionMeta $meta) } /** - * @param RevisionMeta $meta * @return mixed */ public function getFiles(RevisionMeta $meta) @@ -84,7 +82,6 @@ public function getFiles(RevisionMeta $meta) } /** - * @param RevisionMeta $meta * @return mixed */ public function getVideos(RevisionMeta $meta) @@ -95,7 +92,6 @@ public function getVideos(RevisionMeta $meta) /** * Getter for button. * - * @param RevisionMeta $meta * @return mixed */ public function getButton(RevisionMeta $meta) @@ -106,7 +102,6 @@ public function getButton(RevisionMeta $meta) /** * Getter for buttons. * - * @param RevisionMeta $meta * @return mixed */ public function getButtons(RevisionMeta $meta) @@ -116,10 +111,6 @@ public function getButtons(RevisionMeta $meta) /** * Search for smart blocks. - * - * @param Builder $query - * @param string $search - * @return Builder */ public function scopeSearch(Builder $query, string $search): Builder { diff --git a/src/Repositories/Decorators/CachingMenuRepository.php b/src/Repositories/Decorators/CachingMenuRepository.php index 67aced7..aac1f7e 100644 --- a/src/Repositories/Decorators/CachingMenuRepository.php +++ b/src/Repositories/Decorators/CachingMenuRepository.php @@ -31,15 +31,14 @@ public function __construct(MenuRepositoryInterface $repository, Cache $cache) /** * Find by slug. * - * @param string $slug * @return mixed */ public function findBySlug(string $slug) { $locale = app()->getLocale(); - $menu = $this->cache->tags(['cms_menu_'.$slug, 'cms_menu']) + $menu = $this->cache->tags(['fabriq_menu_'.$slug, 'fabriq_menu']) ->rememberForever($locale, function () use ($slug) { - Log::info('Caching menu', ['cache_key' => 'cms_menu_'.$slug, 'cms_menu']); + Log::info('Caching menu', ['cache_key' => 'fabriq_menu_'.$slug, 'fabriq_menu']); return $this->repository->findBySlug($slug); }); diff --git a/src/Repositories/Decorators/CachingPageRepository.php b/src/Repositories/Decorators/CachingPageRepository.php index 4fdc5c9..3eb8864 100644 --- a/src/Repositories/Decorators/CachingPageRepository.php +++ b/src/Repositories/Decorators/CachingPageRepository.php @@ -31,15 +31,14 @@ public function __construct(PageRepositoryInterface $repository, Cache $cache) /** * Find by slug. * - * @param string $slug * @return mixed */ public function findBySlug(string $slug) { $locale = 'sv'; - $page = $this->cache->tags(['cms_pages', 'cms_page_'.$slug]) + $page = $this->cache->tags(['fabriq_pages', 'fabriq_page_'.$slug]) ->rememberForever($locale, function () use ($slug) { - Log::info('Caching page', ['cache_key' => 'cms_page_'.$slug, 'cms_page']); + Log::info('Caching page', ['cache_key' => 'fabriq_page_'.$slug, 'fabriq_page']); return $this->repository->findBySlug($slug); }); @@ -50,15 +49,14 @@ public function findBySlug(string $slug) /** * Find preview by slug. * - * @param string $slug * @return mixed */ public function findPreviewBySlug(string $slug) { $locale = 'sv'; - $page = $this->cache->tags(['cms_page_'.$slug, 'cms_page']) + $page = $this->cache->tags(['fabriq_page_'.$slug, 'fabriq_page']) ->rememberForever($locale, function () use ($slug) { - Log::info('Caching page', ['cache_key' => 'cms_page_'.$slug, 'cms_page']); + Log::info('Caching page', ['cache_key' => 'fabriq_page_'.$slug, 'fabriq_page']); return $this->repository->findPreviewBySlug($slug); }); @@ -69,12 +67,11 @@ public function findPreviewBySlug(string $slug) /** * Find pages by ids. * - * @param array $ids * @return mixed */ public function findByIds(array $ids) { - $pages = $this->cache->tags(['cms_page']) + $pages = $this->cache->tags(['fabriq_page']) ->rememberForever(base64_encode(implode('-', $ids)), function () use ($ids) { return $this->repository->findByIds($ids); }); diff --git a/tests/MenuItemFeatureTest.php b/tests/MenuItemFeatureTest.php index c65e522..4f21e58 100644 --- a/tests/MenuItemFeatureTest.php +++ b/tests/MenuItemFeatureTest.php @@ -118,7 +118,7 @@ public function it_can_update_a_single_menu_item() Event::assertDispatchedTimes(TranslatedRevisionUpdated::class, 2); Event::assertDispatched(function (TranslatedRevisionUpdated $event) { if (get_class($event->model) === Fabriq::getFqnModel('menuItem')) { - return $event->model->getRevisionOptions()->cacheTagsToFlush[0] === 'cms_menu'; + return $event->model->getRevisionOptions()->cacheTagsToFlush[0] === 'fabriq_menu'; } }); } @@ -417,7 +417,7 @@ public function it_will_prepend_a_slash_to_the_relative_url() // Act $response = $this->withHeaders(['X-LOCALE' => 'en']) - ->json('GET', '/menus/'.$menu->slug.'/public/'.'?include=children'); + ->json('GET', '/menus/'.$menu->slug.'/public/'.'?include=children'); // Assert $response->assertOk();