From 7e0ff2ad75a874be3c21bc0a1a5d9aba928a3ab0 Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Mon, 26 Feb 2024 17:12:28 +0100 Subject: [PATCH] feat: refactor sticky extension --- extensions/sticky/extend.php | 31 +++---- .../src/Api/DiscussionResourceFields.php | 41 +++++++++ .../src/Listener/SaveStickyToDatabase.php | 40 -------- .../integration/api/ListDiscussionsTest.php | 8 +- .../integration/api/StickyDiscussionsTest.php | 92 +++++++++++++++++++ 5 files changed, 149 insertions(+), 63 deletions(-) create mode 100644 extensions/sticky/src/Api/DiscussionResourceFields.php delete mode 100755 extensions/sticky/src/Listener/SaveStickyToDatabase.php create mode 100644 extensions/sticky/tests/integration/api/StickyDiscussionsTest.php diff --git a/extensions/sticky/extend.php b/extensions/sticky/extend.php index 4ca43eec0..79144acf7 100644 --- a/extensions/sticky/extend.php +++ b/extensions/sticky/extend.php @@ -7,17 +7,16 @@ * LICENSE file that was distributed with this source code. */ -use Flarum\Api\Controller\ListDiscussionsController; -use Flarum\Api\Serializer\DiscussionSerializer; +use Flarum\Api\Endpoint; +use Flarum\Api\Resource; use Flarum\Discussion\Discussion; -use Flarum\Discussion\Event\Saving; use Flarum\Discussion\Search\DiscussionSearcher; use Flarum\Extend; use Flarum\Search\Database\DatabaseSearchDriver; +use Flarum\Sticky\Api\DiscussionResourceFields; use Flarum\Sticky\Event\DiscussionWasStickied; use Flarum\Sticky\Event\DiscussionWasUnstickied; use Flarum\Sticky\Listener; -use Flarum\Sticky\Listener\SaveStickyToDatabase; use Flarum\Sticky\PinStickiedDiscussionsToTop; use Flarum\Sticky\Post\DiscussionStickiedPost; use Flarum\Sticky\Query\StickyFilter; @@ -27,30 +26,24 @@ return [ ->js(__DIR__.'/js/dist/forum.js') ->css(__DIR__.'/less/forum.less'), + (new Extend\Frontend('admin')) + ->js(__DIR__.'/js/dist/admin.js'), + + new Extend\Locales(__DIR__.'/locale'), + (new Extend\Model(Discussion::class)) ->cast('is_sticky', 'bool'), (new Extend\Post()) ->type(DiscussionStickiedPost::class), - (new Extend\ApiSerializer(DiscussionSerializer::class)) - ->attribute('isSticky', function (DiscussionSerializer $serializer, Discussion $discussion) { - return (bool) $discussion->is_sticky; - }) - ->attribute('canSticky', function (DiscussionSerializer $serializer, $discussion) { - return (bool) $serializer->getActor()->can('sticky', $discussion); + (new Extend\ApiResource(Resource\DiscussionResource::class)) + ->fields(DiscussionResourceFields::class) + ->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint): Endpoint\Index { + return $endpoint->addDefaultInclude(['firstPost']); }), - (new Extend\ApiController(ListDiscussionsController::class)) - ->addInclude('firstPost'), - - (new Extend\Frontend('admin')) - ->js(__DIR__.'/js/dist/admin.js'), - - new Extend\Locales(__DIR__.'/locale'), - (new Extend\Event()) - ->listen(Saving::class, SaveStickyToDatabase::class) ->listen(DiscussionWasStickied::class, [Listener\CreatePostWhenDiscussionIsStickied::class, 'whenDiscussionWasStickied']) ->listen(DiscussionWasUnstickied::class, [Listener\CreatePostWhenDiscussionIsStickied::class, 'whenDiscussionWasUnstickied']), diff --git a/extensions/sticky/src/Api/DiscussionResourceFields.php b/extensions/sticky/src/Api/DiscussionResourceFields.php new file mode 100644 index 000000000..d763b00ad --- /dev/null +++ b/extensions/sticky/src/Api/DiscussionResourceFields.php @@ -0,0 +1,41 @@ +writable(function (Discussion $discussion, Context $context) { + return $context->endpoint instanceof Update + && $context->getActor()->can('sticky', $discussion); + }) + ->set(function (Discussion $discussion, bool $isSticky, Context $context) { + $actor = $context->getActor(); + + if ($discussion->is_sticky === $isSticky) { + return; + } + + $discussion->is_sticky = $isSticky; + + $discussion->raise( + $discussion->is_sticky + ? new DiscussionWasStickied($discussion, $actor) + : new DiscussionWasUnstickied($discussion, $actor) + ); + }), + Schema\Boolean::make('canSticky') + ->get(fn (Discussion $discussion, Context $context) => $context->getActor()->can('sticky', $discussion)), + ]; + } +} diff --git a/extensions/sticky/src/Listener/SaveStickyToDatabase.php b/extensions/sticky/src/Listener/SaveStickyToDatabase.php deleted file mode 100755 index 719c92cdf..000000000 --- a/extensions/sticky/src/Listener/SaveStickyToDatabase.php +++ /dev/null @@ -1,40 +0,0 @@ -data['attributes']['isSticky'])) { - $isSticky = (bool) $event->data['attributes']['isSticky']; - $discussion = $event->discussion; - $actor = $event->actor; - - $actor->assertCan('sticky', $discussion); - - if ((bool) $discussion->is_sticky === $isSticky) { - return; - } - - $discussion->is_sticky = $isSticky; - - $discussion->raise( - $discussion->is_sticky - ? new DiscussionWasStickied($discussion, $actor) - : new DiscussionWasUnstickied($discussion, $actor) - ); - } - } -} diff --git a/extensions/sticky/tests/integration/api/ListDiscussionsTest.php b/extensions/sticky/tests/integration/api/ListDiscussionsTest.php index 3f595acbb..4db57a65a 100644 --- a/extensions/sticky/tests/integration/api/ListDiscussionsTest.php +++ b/extensions/sticky/tests/integration/api/ListDiscussionsTest.php @@ -63,7 +63,7 @@ class ListDiscussionsTest extends TestCase $data = json_decode($response->getBody()->getContents(), true); - $this->assertEquals([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); + $this->assertEqualsCanonicalizing([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); } /** @test */ @@ -79,7 +79,7 @@ class ListDiscussionsTest extends TestCase $data = json_decode($response->getBody()->getContents(), true); - $this->assertEquals([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); + $this->assertEqualsCanonicalizing([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); } /** @test */ @@ -95,7 +95,7 @@ class ListDiscussionsTest extends TestCase $data = json_decode($response->getBody()->getContents(), true); - $this->assertEquals([2, 4, 3, 1], Arr::pluck($data['data'], 'id')); + $this->assertEqualsCanonicalizing([2, 4, 3, 1], Arr::pluck($data['data'], 'id')); } /** @test */ @@ -115,6 +115,6 @@ class ListDiscussionsTest extends TestCase $data = json_decode($response->getBody()->getContents(), true); - $this->assertEquals([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); + $this->assertEqualsCanonicalizing([3, 1, 2, 4], Arr::pluck($data['data'], 'id')); } } diff --git a/extensions/sticky/tests/integration/api/StickyDiscussionsTest.php b/extensions/sticky/tests/integration/api/StickyDiscussionsTest.php new file mode 100644 index 000000000..77633db3e --- /dev/null +++ b/extensions/sticky/tests/integration/api/StickyDiscussionsTest.php @@ -0,0 +1,92 @@ +extension('flarum-sticky'); + + $this->prepareDatabase([ + 'users' => [ + ['id' => 1, 'username' => 'Muralf', 'email' => 'muralf@machine.local', 'is_email_confirmed' => 1], + $this->normalUser(), + ['id' => 3, 'username' => 'Muralf_', 'email' => 'muralf_@machine.local', 'is_email_confirmed' => 1], + ], + 'discussions' => [ + ['id' => 1, 'title' => __CLASS__, 'created_at' => Carbon::now(), 'last_posted_at' => Carbon::now(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => true, 'last_post_number' => 1], + ['id' => 2, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinutes(2), 'last_posted_at' => Carbon::now()->addMinutes(5), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => false, 'last_post_number' => 1], + ['id' => 3, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinutes(3), 'last_posted_at' => Carbon::now()->addMinute(), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => true, 'last_post_number' => 1], + ['id' => 4, 'title' => __CLASS__, 'created_at' => Carbon::now()->addMinutes(4), 'last_posted_at' => Carbon::now()->addMinutes(2), 'user_id' => 1, 'first_post_id' => 1, 'comment_count' => 1, 'is_sticky' => false, 'last_post_number' => 1], + ], + 'groups' => [ + ['id' => 5, 'name_singular' => 'Group', 'name_plural' => 'Groups', 'color' => 'blue'], + ], + 'group_user' => [ + ['user_id' => 2, 'group_id' => 5] + ], + 'group_permission' => [ + ['group_id' => 5, 'permission' => 'discussion.sticky'], + ], + ]); + } + + /** + * @dataProvider stickyDataProvider + * @test + */ + public function can_sticky_if_allowed(int $actorId, bool $allowed, bool $sticky) + { + $response = $this->send( + $this->request('PATCH', '/api/discussions/1', [ + 'authenticatedAs' => $actorId, + 'json' => [ + 'data' => [ + 'attributes' => [ + 'isSticky' => $sticky + ] + ] + ] + ]) + ); + + $body = $response->getBody()->getContents(); + $json = json_decode($body, true); + + if ($allowed) { + $this->assertEquals(200, $response->getStatusCode(), $body); + $this->assertEquals($sticky, $json['data']['attributes']['isSticky']); + } else { + $this->assertEquals(403, $response->getStatusCode(), $body); + } + } + + public static function stickyDataProvider(): array + { + return [ + [1, true, true], + [1, true, false], + [2, true, true], + [2, true, false], + [3, false, true], + [3, false, false], + ]; + } +}