Skip to content

Commit

Permalink
feat: Purge updated discussion tags only
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaucau committed Jun 16, 2023
1 parent 82205bd commit 111915a
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 58 deletions.
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
}
],
"require": {
"flarum/core": "^1.7",
"flarum/core": "^1.8",
"php": ">=8.0"
},
"require-dev": {
"flarum/phpstan": "^1.7"
"flarum/phpstan": "^1.8",
"flarum/tags": "*"
},
"suggest": {
"blomstra/flarum-redis": "This library allows using Redis as cache, session and for the queue. https://github.com/blomstra/flarum-redis#set-up"
Expand All @@ -50,7 +51,11 @@
"name": "fas fa-bolt",
"backgroundColor": "#2b82d9",
"color": "#fff"
}
},
"optional-dependencies": [
"flarum/tags",
"fof/masquerade"
]
},
"flarum-cli": {
"modules": {
Expand Down
11 changes: 11 additions & 0 deletions extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use ACPL\FlarumCache\Api\Controller\LSCacheCsrfResponseController;
use ACPL\FlarumCache\Api\Controller\PurgeLSCacheController;
use ACPL\FlarumCache\Command\LSCacheClearCommand;
use ACPL\FlarumCache\Compatibility\FlarumTags\FlarumTagsPurgeMiddleware;
use ACPL\FlarumCache\Compatibility\FofMasquerade\Middleware as FofMasqueradeMiddleware;
use ACPL\FlarumCache\Listener\ClearingCacheListener;
use ACPL\FlarumCache\Middleware\LoginMiddleware;
use ACPL\FlarumCache\Middleware\LogoutMiddleware;
Expand Down Expand Up @@ -68,4 +70,13 @@
(new Extend\Routes('api'))->get('/lscache-purge', 'lscache.purge', PurgeLSCacheController::class),
(new Extend\Console())->command(LSCacheClearCommand::class),
(new Extend\Event())->listen(ClearingCache::class, ClearingCacheListener::class),

// Extensions
(new Extend\Conditional)
->whenExtensionEnabled('flarum-tags', [
(new Extend\Middleware('api'))->add(FlarumTagsPurgeMiddleware ::class),
])
->whenExtensionEnabled('fof-masquerade', [
(new Extend\Middleware('api'))->add(FofMasqueradeMiddleware::class),
])
];
82 changes: 82 additions & 0 deletions src/Abstract/PurgeMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace ACPL\FlarumCache\Abstract;

use ACPL\FlarumCache\LSCacheHeadersEnum;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

abstract class PurgeMiddleware implements MiddlewareInterface
{
protected SettingsRepositoryInterface $settings;

protected string $currentRouteName;
protected bool $isDiscussion;
protected bool $isPost;

public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);

if (
! in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])
|| $response->getStatusCode() >= 400
) {
return $response;
}

$this->currentRouteName = $request->getAttribute('routeName');
$this->isDiscussion = str_starts_with($this->currentRouteName, 'discussions');
$this->isPost = str_starts_with($this->currentRouteName, 'posts');

// If this is just an update of the last read post, there is no point in clearing the public cache
if ($this->isDiscussion && Arr::get(
$request->getParsedBody(),
'data.attributes.lastReadPostNumber')
) {
return $response;
}


return $this->processPurge($request, $handler, $response);
}

abstract protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
): ResponseInterface;


protected function addPurgeParamsToResponse(ResponseInterface $response, array $newPurgeParams): ResponseInterface
{
if ($response->hasHeader(LSCacheHeadersEnum::PURGE)) {
$existingPurgeParams = explode(',', $response->getHeaderLine(LSCacheHeadersEnum::PURGE));
$newPurgeParams = array_unique(array_merge($existingPurgeParams, $newPurgeParams));
}

if (count($newPurgeParams) < 1) {
return $response;
}

if ($this->settings->get('acpl-lscache.serve_stale') && ! array_key_exists('stale', $newPurgeParams)) {
array_unshift($newPurgeParams, 'stale');
}

return $response->withHeader(LSCacheHeadersEnum::PURGE, implode(',', $newPurgeParams));
}

protected function getRouteParams($request): array
{
return $request->getAttribute('routeParameters');
}
}
82 changes: 82 additions & 0 deletions src/Compatibility/FlarumTags/FlarumTagsPurgeMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace ACPL\FlarumCache\Compatibility\FlarumTags;

use ACPL\FlarumCache\Abstract\PurgeMiddleware;
use Flarum\Discussion\Discussion;
use Flarum\Http\UrlGenerator;
use Flarum\Post\Post;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\Tags\Tag;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class FlarumTagsPurgeMiddleware extends PurgeMiddleware
{
protected UrlGenerator $url;

public function __construct(SettingsRepositoryInterface $settings, UrlGenerator $url)
{
$this->url = $url;
parent::__construct($settings);
}

protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
): ResponseInterface {
$isDiscussion = $this->isDiscussion;
$isPost = $this->isPost;

if (! $isDiscussion && ! $isPost) {
return $response;
}

$body = $request->getParsedBody();
$routeName = $this->currentRouteName;

// When a post is edited, there is no need to purge tags cache unless the post is being hidden
if ($routeName === 'posts.update' && ! Arr::has($body, 'data.attributes.isHidden')) {
return $response;
}

$payload = $response->getBody()->getContents();
$payload = json_decode($payload, true);

if ($isDiscussion) {
$discussionId = Arr::get($payload, 'data.id');
} else {
$discussionId = Arr::get($payload, 'data.relationships.discussion.data.id');
if (! $discussionId) {
$postId = Arr::get($payload, 'data.id');
$discussionId = Post::find($postId)->discussion_id;
}
}

$discussion = Discussion::find($discussionId);
if (! $discussion) {
return $response;
}

/**
* @var Tag[] $tags
* @phpstan-ignore-next-line
*/
$tags = $discussion->tags;

if (! $tags) {
return $response;
}

$purgeParams = ['tags.index', 'tags'];

foreach ($tags as $tag) {
$purgeParams[] = "tag_$tag->slug";
}

return $this->addPurgeParamsToResponse($response, $purgeParams);
}
}
36 changes: 36 additions & 0 deletions src/Compatibility/FofMasquerade/Middleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace ACPL\FlarumCache\Compatibility\FofMasquerade;

use ACPL\FlarumCache\Abstract\PurgeMiddleware;
use Flarum\Http\RequestUtil;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;


class Middleware extends PurgeMiddleware
{
protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
): ResponseInterface {
// Purge user profile cache when updating FriendsOfFlarum/masquerade fields
if ($this->currentRouteName === 'masquerade.api.configure.save') {
$user = RequestUtil::getActor($request);
return $this->addPurgeParamsToResponse(
$response,
[
"tag=user_$user->id",
"tag=users_$user->id",
"tag=user_$user->username",
"tag=users_$user->username",
"tag=masquerade_$user->id",
]
);
}

return $response;
}
}
63 changes: 16 additions & 47 deletions src/Middleware/LSCachePurgeMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace ACPL\FlarumCache\Middleware;

use ACPL\FlarumCache\Abstract\PurgeMiddleware;
use ACPL\FlarumCache\LSCache;
use ACPL\FlarumCache\LSCacheHeadersEnum;
use Flarum\Discussion\Discussion;
use Flarum\Http\RequestUtil;
use Flarum\Post\Post;
use Flarum\Settings\SettingsRepositoryInterface;
Expand All @@ -14,53 +16,34 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class LSCachePurgeMiddleware implements MiddlewareInterface
class LSCachePurgeMiddleware extends PurgeMiddleware
{
private SettingsRepositoryInterface $settings;
protected function processPurge(
ServerRequestInterface $request,
RequestHandlerInterface $handler,
ResponseInterface $response
): ResponseInterface {
$routeName = $this->currentRouteName;

public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
$purgeParams = [];

if (
! in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE']) ||
$response->hasHeader(LSCacheHeadersEnum::PURGE) ||
$response->getStatusCode() >= 400
) {
return $response;
}
$params = $this->getRouteParams($request);

$routeName = $request->getAttribute('routeName');
$isDiscussion = $this->isDiscussion;
$isPost = $this->isPost;

$isDiscussion = Str::startsWith($routeName, 'discussions');
$body = $request->getParsedBody();

// If this is just an update of the last read post, there is no point in clearing the public cache
if ($isDiscussion && Arr::get($body, 'data.attributes.lastReadPostNumber')) {
return $response;
}

$purgeParams = [];

$params = $request->getAttribute('routeParameters');

$isPost = Str::startsWith($routeName, 'posts');

if ($isDiscussion || $isPost) {
$purgeList = $this->settings->get('acpl-lscache.purge_on_discussion_update');
if (! empty($purgeList)) {
$purgeList = explode("\n", $purgeList);
// Get only valid items
$purgeList = array_filter($purgeList, fn ($item) => Str::startsWith($item, ['/', 'tag=']));
$purgeList = array_filter($purgeList, fn($item) => Str::startsWith($item, ['/', 'tag=']));
$purgeParams = array_merge($purgeParams, $purgeList);
}

// If this is a post update, we don't need to clear the home page cache
// If this is a post update, we don't need to clear the home page cache unless the post is hidden
$isPostUpdate = $routeName === 'posts.update';
if (($isPostUpdate && Arr::has($body, 'data.attributes.isHidden')) || ! $isPostUpdate) {
array_push($purgeParams, 'tag=default', 'tag=index', 'tag=discussions.index');
Expand Down Expand Up @@ -97,20 +80,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}
}

//Clear user profile cache when updating FriendsOfFlarum/masquerade fields
if ($routeName === 'masquerade.api.configure.save') {
$user = RequestUtil::getActor($request);
$purgeParams[] = "tag=users$user->id,tag=masquerade$user->id";
}

if (count($purgeParams) < 1) {
return $response;
}

if ($this->settings->get('acpl-lscache.serve_stale')) {
array_unshift($purgeParams, 'stale');
}

return $response->withHeader(LSCacheHeadersEnum::PURGE, implode(',', $purgeParams));
return $this->addPurgeParamsToResponse($response, $purgeParams);
}
}
Loading

0 comments on commit 111915a

Please sign in to comment.