diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index 1dce7ce..5ece82b 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -4,9 +4,8 @@ on: [workflow_dispatch, push, pull_request]
jobs:
run:
- uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@main
+ uses: flarum/framework/.github/workflows/REUSABLE_backend.yml@2.x
with:
enable_backend_testing: true
enable_phpstan: true
backend_directory: .
- php_versions: '["7.4", "8.0", "8.1", "8.2", "8.3"]'
diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml
index 2f78a17..cfb4dfb 100644
--- a/.github/workflows/frontend.yml
+++ b/.github/workflows/frontend.yml
@@ -4,7 +4,7 @@ on: [workflow_dispatch, push, pull_request]
jobs:
run:
- uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@main
+ uses: flarum/framework/.github/workflows/REUSABLE_frontend.yml@2.x
with:
enable_bundlewatch: false
enable_prettier: true
diff --git a/composer.json b/composer.json
index 45a83f3..64c7794 100755
--- a/composer.json
+++ b/composer.json
@@ -23,8 +23,7 @@
}
],
"require": {
- "php": "^7.4 | ^8.0",
- "flarum/core": "^1.2.0"
+ "flarum/core": "^2.0.0"
},
"replace": {
"reflar/reactions": "*"
@@ -92,7 +91,7 @@
"analyse:phpstan": "Run static analysis"
},
"require-dev": {
- "flarum/testing": "^1.0.0",
+ "flarum/testing": "^2.0.0",
"flarum/likes": "*",
"fof/gamification": "*",
"flarum/phpstan": "*"
diff --git a/extend.php b/extend.php
index ef85134..b385623 100644
--- a/extend.php
+++ b/extend.php
@@ -11,16 +11,17 @@
namespace FoF\Reactions;
-use Flarum\Api\Controller as ApiController;
-use Flarum\Api\Serializer;
-use Flarum\Api\Serializer\BasicPostSerializer;
-use Flarum\Database\AbstractModel;
+use Flarum\Api\Context;
+use Flarum\Discussion\Discussion;
use Flarum\Extend;
-use Flarum\Post\Event\Saving;
use Flarum\Post\Post;
-use FoF\Reactions\Api\Controller;
-use FoF\Reactions\Api\Serializer\ReactionSerializer;
+use Flarum\Search\Database\DatabaseSearchDriver;
use FoF\Reactions\Notification\PostReactedBlueprint;
+use Flarum\Api\Resource;
+use Flarum\Api\Endpoint;
+use Flarum\Api\Schema;
+use FoF\Reactions\Search\Filter\PostFilter;
+use FoF\Reactions\Search\PostReactionSearcher;
return [
(new Extend\Frontend('admin'))
@@ -35,43 +36,35 @@
new Extend\Locales(__DIR__.'/resources/locale'),
- (new Extend\Routes('api'))
- ->get('/posts/{id}/reactions', 'post.reactions.index', Controller\ListPostReactionsController::class)
- ->delete('/posts/{id}/reactions/specific/{postReactionId}', 'post.reactions.specific.delete', Controller\DeletePostReactionController::class)
- ->delete('/posts/{id}/reactions/type/{reactionId}', 'post.reactions.type.delete', Controller\DeletePostReactionController::class)
- ->get('/reactions', 'reactions.index', Controller\ListReactionsController::class)
- ->post('/reactions', 'reactions.create', Controller\CreateReactionController::class)
- ->patch('/reactions/{id}', 'reactions.update', Controller\UpdateReactionController::class)
- ->delete('/reactions/{id}', 'reactions.delete', Controller\DeleteReactionController::class),
+ (new Extend\ModelVisibility(PostReaction::class))
+ ->scope(Access\ScopePostReactionVisibility::class),
(new Extend\Event())
- ->listen(Saving::class, Listener\SaveReactionsToDatabase::class)
->subscribe(Listener\SendNotifications::class),
(new Extend\Notification())
- ->type(PostReactedBlueprint::class, BasicPostSerializer::class, ['alert']),
+ ->type(PostReactedBlueprint::class, ['alert']),
- (new Extend\ApiSerializer(Serializer\ForumSerializer::class))
- ->hasMany('reactions', ReactionSerializer::class)
- ->attributes(ReactionsForumAttributes::class),
+ new Extend\ApiResource(Api\Resource\PostReactionResource::class),
- (new Extend\ApiSerializer(Serializer\PostSerializer::class))
- ->attributes(PostAttributes::class),
+ new Extend\ApiResource(Api\Resource\ReactionResource::class),
- (new Extend\ApiSerializer(Serializer\DiscussionSerializer::class))
- ->attributes(function (Serializer\DiscussionSerializer $serializer, AbstractModel $discussion, array $attributes): array {
- $attributes['canSeeReactions'] = (bool) $serializer->getActor()->can('canSeeReactions', $discussion);
+ (new Extend\ApiResource(Resource\ForumResource::class))
+ ->fields(ForumResourceFields::class)
+ ->endpoint(Endpoint\Show::class, fn (Endpoint\Show $show) => $show->addDefaultInclude(['reactions'])),
- return $attributes;
- }),
+ (new Extend\ApiResource(Resource\PostResource::class))
+ ->fields(PostResourceFields::class),
- (new Extend\ApiController(ApiController\ShowForumController::class))
- ->prepareDataForSerialization(function (ApiController\ShowForumController $controller, &$data) {
- $data['reactions'] = Reaction::get();
- }),
+ (new Extend\ApiResource(Resource\DiscussionResource::class))
+ ->fields(fn (): array => [
+ Schema\Boolean::make('canSeeReactions')
+ ->get(fn (Discussion $discussion, Context $context) => $context->getActor()->can('canSeeReactions', $discussion))
+ ]),
- (new Extend\ApiController(ApiController\ShowForumController::class))
- ->addInclude('reactions'),
+ (new Extend\SearchDriver(DatabaseSearchDriver::class))
+ ->addSearcher(PostReaction::class, PostReactionSearcher::class)
+ ->addFilter(PostReactionSearcher::class, PostFilter::class),
(new Extend\Settings())
->default('fof-reactions.react_own_post', false)
diff --git a/js/package.json b/js/package.json
index 05eb503..289fc08 100644
--- a/js/package.json
+++ b/js/package.json
@@ -11,8 +11,8 @@
},
"dependencies": {
"@flarum/prettier-config": "^1.0.0",
- "flarum-tsconfig": "^1.0.2",
- "flarum-webpack-config": "^2.0.0",
+ "flarum-tsconfig": "^2.0.0",
+ "flarum-webpack-config": "^3.0.0",
"fuzzyset": "^1.0.7",
"lodash.debounce": "^4.0.8",
"simple-emoji-map": "^0.5.1",
diff --git a/js/src/admin/extend.js b/js/src/admin/extend.js
new file mode 100644
index 0000000..66ecb6d
--- /dev/null
+++ b/js/src/admin/extend.js
@@ -0,0 +1,38 @@
+import Extend from 'flarum/common/extenders';
+import SettingsPage from './components/SettingsPage';
+import Reaction from '../common/models/Reaction';
+import Forum from 'flarum/common/models/Forum';
+
+export default [
+ new Extend.Store().add('reactions', Reaction),
+
+ new Extend.Model(Forum).hasMany('reactions', Reaction),
+
+ new Extend.Admin()
+ .permission(
+ () => ({
+ icon: 'far fa-thumbs-up',
+ label: app.translator.trans('fof-reactions.admin.permissions.react_posts_label'),
+ permission: 'discussion.reactPosts',
+ }),
+ 'reply'
+ )
+ .permission(
+ () => ({
+ icon: 'fas fa-info-circle',
+ label: app.translator.trans('fof-reactions.admin.permissions.see_reactions_label'),
+ permission: 'discussion.canSeeReactions',
+ allowGuest: true,
+ }),
+ 'view'
+ )
+ .permission(
+ () => ({
+ icon: 'fas fa-trash',
+ label: app.translator.trans('fof-reactions.admin.permissions.delete_post_reactions_label'),
+ permission: 'discussion.deleteReactionsPosts',
+ }),
+ 'moderate'
+ )
+ .page(SettingsPage),
+];
diff --git a/js/src/admin/index.js b/js/src/admin/index.js
index b4b6c43..873be3f 100644
--- a/js/src/admin/index.js
+++ b/js/src/admin/index.js
@@ -1,46 +1,12 @@
import app from 'flarum/admin/app';
-import Forum from 'flarum/common/models/Forum';
-import Model from 'flarum/common/Model';
-
-import SettingsPage from './components/SettingsPage';
-import Reaction from '../common/models/Reaction';
export * from './components';
export * from '../common/components';
export * from '../common/models';
export * from '../common/util';
-app.initializers.add('fof/reactions', () => {
- app.store.models.reactions = Reaction;
-
- Forum.prototype.reactions = Model.hasMany('reactions');
+export { default as extend } from './extend';
- app.extensionData
- .for('fof-reactions')
- .registerPermission(
- {
- icon: 'far fa-thumbs-up',
- label: app.translator.trans('fof-reactions.admin.permissions.react_posts_label'),
- permission: 'discussion.reactPosts',
- },
- 'reply'
- )
- .registerPermission(
- {
- icon: 'fas fa-info-circle',
- label: app.translator.trans('fof-reactions.admin.permissions.see_reactions_label'),
- permission: 'discussion.canSeeReactions',
- allowGuest: true,
- },
- 'view'
- )
- .registerPermission(
- {
- icon: 'fas fa-trash',
- label: app.translator.trans('fof-reactions.admin.permissions.delete_post_reactions_label'),
- permission: 'discussion.deleteReactionsPosts',
- },
- 'moderate'
- )
- .registerPage(SettingsPage);
+app.initializers.add('fof/reactions', () => {
+ //
});
diff --git a/js/src/common/util/emoji.js b/js/src/common/util/emoji.js
index c133891..54706f0 100755
--- a/js/src/common/util/emoji.js
+++ b/js/src/common/util/emoji.js
@@ -24,7 +24,7 @@ const search = (query) => {
};
};
-export default (reactionOrIdentifier) => {
+export default function emoji(reactionOrIdentifier) {
if (!reactionOrIdentifier) return {};
let identifier = reactionOrIdentifier.identifier || reactionOrIdentifier;
@@ -56,4 +56,4 @@ export default (reactionOrIdentifier) => {
emojiCache.set(reactionOrIdentifier, output);
return output || {};
-};
+}
diff --git a/js/src/forum/components/PostReactAction.js b/js/src/forum/components/PostReactAction.js
index b96f3a3..82a2caf 100755
--- a/js/src/forum/components/PostReactAction.js
+++ b/js/src/forum/components/PostReactAction.js
@@ -3,7 +3,6 @@ import Component from 'flarum/common/Component';
import ItemList from 'flarum/common/utils/ItemList';
import Button from 'flarum/common/components/Button';
import listItems from 'flarum/common/helpers/listItems';
-import LogInModal from 'flarum/forum/components/LogInModal';
import ReactionComponent from '../../common/components/ReactionComponent';
export default class PostReactAction extends Component {
@@ -148,7 +147,7 @@ export default class PostReactAction extends Component {
const allowAnonymous = app.forum.attribute('fofReactionsAllowAnonymous');
if (!app.session.user && !allowAnonymous) {
- app.modal.show(LogInModal);
+ app.modal.show(() => import('flarum/forum/components/LogInModal'));
return;
}
diff --git a/js/src/forum/components/PostReactedNotification.js b/js/src/forum/components/PostReactedNotification.js
index 4d312ef..fa4b7f3 100644
--- a/js/src/forum/components/PostReactedNotification.js
+++ b/js/src/forum/components/PostReactedNotification.js
@@ -1,6 +1,6 @@
import app from 'flarum/forum/app';
import Notification from 'flarum/forum/components/Notification';
-import icon from 'flarum/common/helpers/icon';
+import Icon from 'flarum/common/components/Icon';
import emoji from '../../common/util/emoji';
@@ -18,7 +18,7 @@ export default class PostReactedNotification extends Notification {
const { identifier, type } = JSON.parse(notification.content());
const user = notification.fromUser();
- const reaction = type === 'emoji' ?
: icon(identifier);
+ const reaction = type === 'emoji' ?
: ;
return app.translator.trans('fof-reactions.forum.notification', {
user,
diff --git a/js/src/forum/components/ReactionsModal.tsx b/js/src/forum/components/ReactionsModal.tsx
index 25b368b..2dd2258 100644
--- a/js/src/forum/components/ReactionsModal.tsx
+++ b/js/src/forum/components/ReactionsModal.tsx
@@ -1,7 +1,7 @@
import app from 'flarum/forum/app';
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
-import avatar from 'flarum/common/helpers/avatar';
+import Avatar from 'flarum/common/components/Avatar';
import username from 'flarum/common/helpers/username';
import Link from 'flarum/common/components/Link';
import ReactionComponent from '../../common/components/ReactionComponent';
@@ -85,13 +85,11 @@ export default class ReactionsModal extends Modal {
/>
)}
-
-
{Object.entries(users).map(([postReactionId, user]: [string, User], index: number) => (
- {avatar(user, { loading: 'lazy' })}
+
{username(user)}
{canDeleteReaction(user) && (
@@ -104,7 +102,6 @@ export default class ReactionsModal extends Modal {
)}
))}
-
{anonymousCount > 0 && {app.translator.trans('fof-reactions.forum.modal.anonymous_count', { count: anonymousCount })}}
);
@@ -113,7 +110,10 @@ export default class ReactionsModal extends Modal {
async load(): Promise {
this.loading = true;
- const response = await app.store.find>(`/posts/${this.attrs.post.id()}/reactions`, { include: 'user,reaction' });
+ const response = await app.store.find>(`post_reactions`, {
+ include: 'user,reaction',
+ filter: { post: this.attrs.post.id() },
+ });
const groupedReactions = groupBy(response, (r: PostReaction) => r.reactionId());
const reactions: ReactionGroup[] = [];
@@ -160,7 +160,12 @@ export default class ReactionsModal extends Modal {
await app.request({
method: 'DELETE',
- url: `${app.forum.attribute('apiUrl')}/posts/${this.attrs.post.id()}/reactions/${isSpecific ? 'specific' : 'type'}/${id}`,
+ url: `${app.forum.attribute('apiUrl')}/post_reactions/delete`,
+ body: {
+ postId: this.attrs.post.id(),
+ specific: isSpecific,
+ reactionOrPostReactionId: id,
+ },
});
// Filter out the deleted reaction type
diff --git a/js/src/forum/index.js b/js/src/forum/index.js
index eb304ab..01fffe1 100755
--- a/js/src/forum/index.js
+++ b/js/src/forum/index.js
@@ -4,7 +4,6 @@ import Forum from 'flarum/common/models/Forum';
import Discussion from 'flarum/common/models/Discussion';
import Post from 'flarum/common/models/Post';
import Model from 'flarum/common/Model';
-import NotificationGrid from 'flarum/forum/components/NotificationGrid';
import PostReactedNotification from './components/PostReactedNotification';
import Reaction from '../common/models/Reaction';
@@ -38,7 +37,7 @@ app.initializers.add('fof/reactions', () => {
addReactionAction();
addPusher();
- extend(NotificationGrid.prototype, 'notificationTypes', (items) => {
+ extend('flarum/forum/components/NotificationGrid', 'notificationTypes', (items) => {
items.add('postReacted', {
name: 'postReacted',
icon: 'far fa-smile',
diff --git a/phpstan.neon b/phpstan.neon
index bff5e7d..f9bf9f5 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -9,6 +9,5 @@ parameters:
- extend.php
excludePaths:
- *.blade.php
- checkMissingIterableValueType: false
databaseMigrationsPath: ['migrations']
\ No newline at end of file
diff --git a/resources/less/forum.less b/resources/less/forum.less
index d1df1fb..a47670a 100644
--- a/resources/less/forum.less
+++ b/resources/less/forum.less
@@ -50,8 +50,8 @@
}
.CommentPost--Reactions {
- background-color: @body-bg;
- color: @text-color;
+ background-color: var(--body-bg);
+ color: var(--text-color);
visibility: hidden;
opacity: 0;
transition: visibility 0s linear 300ms, opacity 300ms;
@@ -111,7 +111,7 @@
margin: 0;
a {
- color: @text-color;
+ color: var(--text-color);
font-size: 15px;
display: block;
margin-bottom: 10px;
diff --git a/src/Access/ScopePostReactionVisibility.php b/src/Access/ScopePostReactionVisibility.php
new file mode 100644
index 0000000..299b94c
--- /dev/null
+++ b/src/Access/ScopePostReactionVisibility.php
@@ -0,0 +1,16 @@
+whereHas('post', function (Builder $query) use ($actor) {
+ $query->whereVisibleTo($actor);
+ });
+ }
+}
diff --git a/src/Api/Controller/CreateReactionController.php b/src/Api/Controller/CreateReactionController.php
deleted file mode 100644
index 5a21e49..0000000
--- a/src/Api/Controller/CreateReactionController.php
+++ /dev/null
@@ -1,43 +0,0 @@
-bus = $bus;
- }
-
- protected function data(ServerRequestInterface $request, Document $document)
- {
- return $this->bus->dispatch(
- new CreateReaction(RequestUtil::getActor($request), Arr::get($request->getParsedBody(), 'data.attributes', $request->getParsedBody()))
- );
- }
-}
diff --git a/src/Api/Controller/DeletePostReactionController.php b/src/Api/Controller/DeletePostReactionController.php
deleted file mode 100644
index 112e366..0000000
--- a/src/Api/Controller/DeletePostReactionController.php
+++ /dev/null
@@ -1,67 +0,0 @@
-getQueryParams();
-
- $postId = Arr::get($params, 'id');
- $postReactionId = Arr::get($params, 'postReactionId');
- $reactionId = Arr::get($params, 'reactionId');
-
- $post = Post::whereVisibleTo($actor)->findOrFail($postId);
-
- if ($reactionId) {
- // Delete all post_reactions of a specific type (i.e. `reaction_id`)
- $actor->assertCan('deleteReactions', $post);
-
- PostReaction::query()->where('post_id', $postId)->where('reaction_id', $reactionId)->delete();
- PostAnonymousReaction::query()->where('post_id', $postId)->where('reaction_id', $reactionId)->delete();
- } elseif ($postReactionId) {
- // Delete a specific post_reaction for the post
- /**
- * @var PostReaction $reaction
- */
- $reaction = PostReaction::query()->where('post_id', $postId)->where('id', $postReactionId)->firstOrFail();
-
- // If the post is not the actor's, they must have permission to delete reactions
- if ($reaction->user_id != $actor->id) {
- $actor->assertCan('deleteReactions', $post);
- } else {
- $actor->assertCan('react', $post);
- }
-
- $reaction->delete();
- }
-
- // TODO should this send pusher updates? would need new type for non-specific, otherwise could spam pusher events
-
- return new EmptyResponse(204);
- }
-}
diff --git a/src/Api/Controller/DeleteReactionController.php b/src/Api/Controller/DeleteReactionController.php
deleted file mode 100644
index c2c3351..0000000
--- a/src/Api/Controller/DeleteReactionController.php
+++ /dev/null
@@ -1,45 +0,0 @@
-bus = $bus;
- }
-
- /**
- * @param ServerRequestInterface $request
- */
- protected function delete(ServerRequestInterface $request)
- {
- $this->bus->dispatch(
- new DeleteReaction(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request))
- );
- }
-}
diff --git a/src/Api/Controller/ListPostReactionsController.php b/src/Api/Controller/ListPostReactionsController.php
deleted file mode 100644
index ea6b5f2..0000000
--- a/src/Api/Controller/ListPostReactionsController.php
+++ /dev/null
@@ -1,77 +0,0 @@
-posts = $posts;
- $this->settings = $settings;
- }
-
- /**
- * @param ServerRequestInterface $request
- * @param Document $document
- *
- * @return mixed
- */
- protected function data(ServerRequestInterface $request, Document $document)
- {
- $actor = RequestUtil::getActor($request);
-
- $postId = Arr::get($request->getQueryParams(), 'id');
- $post = $this->posts->findOrFail($postId, $actor);
-
- $actor->assertCan('canSeeReactions', $post->discussion);
-
- $query = PostReaction::query()
- ->where('post_id', $post->id)
- ->whereNotNull('reaction_id');
-
- if ($this->settings->get('fof-reactions.anonymousReactions')) {
- $query->unionAll(
- PostAnonymousReaction::query()->where('post_id', $post->id)
- ->whereNotNull('reaction_id')->toBase()
- );
- }
-
- return $query->get();
- }
-}
diff --git a/src/Api/Controller/ListReactionsController.php b/src/Api/Controller/ListReactionsController.php
deleted file mode 100644
index 0451346..0000000
--- a/src/Api/Controller/ListReactionsController.php
+++ /dev/null
@@ -1,37 +0,0 @@
-bus = $bus;
- }
-
- /**
- * @param ServerRequestInterface $request
- * @param Document $document
- *
- * @return mixed
- */
- protected function data(ServerRequestInterface $request, Document $document)
- {
- $id = Arr::get($request->getQueryParams(), 'id');
- $actor = RequestUtil::getActor($request);
- $data = Arr::get($request->getParsedBody(), 'attributes', []);
-
- return $this->bus->dispatch(
- new EditReaction($id, $actor, $data)
- );
- }
-}
diff --git a/src/Api/Resource/PostReactionResource.php b/src/Api/Resource/PostReactionResource.php
new file mode 100644
index 0000000..dca17e6
--- /dev/null
+++ b/src/Api/Resource/PostReactionResource.php
@@ -0,0 +1,108 @@
+
+ */
+class PostReactionResource extends Resource\AbstractDatabaseResource
+{
+ public function type(): string
+ {
+ return 'post_reactions';
+ }
+
+ public function model(): string
+ {
+ return PostReaction::class;
+ }
+
+ public function endpoints(): array
+ {
+ return [
+ Endpoint\Endpoint::make('delete_reactions')
+ ->route('DELETE', '/delete')
+ ->action(function (Context $context) {
+ $actor = $context->getActor();
+ $data = $context->body();
+
+ $postId = Arr::get($data, 'postId');
+ $isSpecific = Arr::get($data, 'isSpecific');
+ $reactionOrPostReactionId = Arr::get($data, 'reactionOrPostReactionId');
+
+ $post = Post::whereVisibleTo($actor)->findOrFail($postId);
+
+ if (! $isSpecific) {
+ $reactionId = $reactionOrPostReactionId;
+
+ // Delete all post_reactions of a specific type (i.e. `reaction_id`)
+ $actor->assertCan('deleteReactions', $post);
+
+ PostReaction::query()->where('post_id', $postId)->where('reaction_id', $reactionId)->delete();
+ PostAnonymousReaction::query()->where('post_id', $postId)->where('reaction_id', $reactionId)->delete();
+ } elseif ($reactionOrPostReactionId) {
+ $postReactionId = $reactionOrPostReactionId;
+
+ // Delete a specific post_reaction for the post
+ /**
+ * @var PostReaction $reaction
+ */
+ $reaction = PostReaction::query()->where('post_id', $postId)->where('id', $postReactionId)->firstOrFail();
+
+ // If the post is not the actor's, they must have permission to delete reactions
+ if ($reaction->user_id != $actor->id) {
+ $actor->assertCan('deleteReactions', $post);
+ } else {
+ $actor->assertCan('react', $post);
+ }
+
+ $reaction->delete();
+ }
+ })
+ ->response(fn (OriginalContext $context) => new EmptyResponse(204)),
+ Endpoint\Index::make()
+ ->paginate(),
+ ];
+ }
+
+ public function fields(): array
+ {
+ return [
+
+ Schema\Integer::make('userId'),
+ Schema\Integer::make('postId'),
+ Schema\Integer::make('reactionId'),
+
+ Schema\Relationship\ToOne::make('reaction')
+ ->includable()
+ ->type('reactions'),
+ Schema\Relationship\ToOne::make('user')
+ ->includable()
+ ->type('users'),
+ Schema\Relationship\ToOne::make('post')
+ ->includable()
+ ->type('posts'),
+ ];
+ }
+
+ public function sorts(): array
+ {
+ return [
+ // SortColumn::make('createdAt'),
+ ];
+ }
+}
diff --git a/src/Api/Resource/ReactionResource.php b/src/Api/Resource/ReactionResource.php
new file mode 100644
index 0000000..4b24fdb
--- /dev/null
+++ b/src/Api/Resource/ReactionResource.php
@@ -0,0 +1,118 @@
+
+ */
+class ReactionResource extends Resource\AbstractDatabaseResource
+{
+ public function type(): string
+ {
+ return 'reactions';
+ }
+
+ public function model(): string
+ {
+ return Reaction::class;
+ }
+
+ public function endpoints(): array
+ {
+ return [
+ Endpoint\Create::make()
+ ->admin(),
+ Endpoint\Update::make()
+ ->admin(),
+ Endpoint\Delete::make()
+ ->admin(),
+ Endpoint\Index::make(),
+ ];
+ }
+
+ public function fields(): array
+ {
+ return [
+
+ Schema\Str::make('identifier')
+ ->requiredOnCreate()
+ ->maxLength(255)
+ ->unique('reactions', 'identifier')
+ ->writable(),
+ Schema\Str::make('display')
+ ->maxLength(255)
+ ->nullable()
+ ->writableOnUpdate(),
+ Schema\Str::make('type')
+ ->requiredOnCreate()
+ ->maxLength(255)
+ ->regex('/icon|emoji/i')
+ ->writable(),
+ Schema\Boolean::make('enabled')
+ ->nullable()
+ ->writable(),
+
+ ];
+ }
+
+ public function sorts(): array
+ {
+ return [
+ // SortColumn::make('createdAt'),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function creating(object $model, OriginalContext $context): ?object
+ {
+ $this->events->dispatch(new Creating($model, $context->getActor(), Arr::get($context->body(), 'data.attributes', $context->body())));
+
+ return parent::creating($model, $context);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function created(object $model, OriginalContext $context): ?object
+ {
+ $this->events->dispatch(new Created($model, $context->getActor()));
+
+ return parent::created($model, $context);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function deleting(object $model, OriginalContext $context): void
+ {
+ $this->events->dispatch(new Deleting($model, $context->getActor()));
+
+ parent::deleting($model, $context);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function deleted(object $model, OriginalContext $context): void
+ {
+ $this->events->dispatch(new Deleted($model, $context->getActor()));
+
+ parent::deleted($model, $context);
+ }
+}
diff --git a/src/Api/Serializer/PostReactionSerializer.php b/src/Api/Serializer/PostReactionSerializer.php
index e587365..790ed0d 100644
--- a/src/Api/Serializer/PostReactionSerializer.php
+++ b/src/Api/Serializer/PostReactionSerializer.php
@@ -17,6 +17,7 @@
use FoF\Reactions\PostReaction;
use Illuminate\Support\Str;
+/** @TODO: Remove this in favor of the api resource class that was added. */
class PostReactionSerializer extends AbstractSerializer
{
protected $type = 'post_reactions';
diff --git a/src/Api/Serializer/ReactionSerializer.php b/src/Api/Serializer/ReactionSerializer.php
deleted file mode 100644
index 1f17c5c..0000000
--- a/src/Api/Serializer/ReactionSerializer.php
+++ /dev/null
@@ -1,35 +0,0 @@
- $reaction->identifier,
- 'display' => $reaction->display,
- 'type' => $reaction->type,
- 'enabled' => $reaction->enabled,
- ];
- }
-}
diff --git a/src/Command/CreateReaction.php b/src/Command/CreateReaction.php
deleted file mode 100644
index 9013d54..0000000
--- a/src/Command/CreateReaction.php
+++ /dev/null
@@ -1,41 +0,0 @@
-actor = $actor;
- $this->data = $data;
- }
-}
diff --git a/src/Command/CreateReactionHandler.php b/src/Command/CreateReactionHandler.php
deleted file mode 100644
index 1689e09..0000000
--- a/src/Command/CreateReactionHandler.php
+++ /dev/null
@@ -1,73 +0,0 @@
-validator = $validator;
- $this->events = $events;
- }
-
- /**
- * @param CreateReaction $command
- *
- * @throws PermissionDeniedException
- *
- * @return Reaction
- */
- public function handle(CreateReaction $command)
- {
- $actor = $command->actor;
- $data = $command->data;
-
- $actor->assertAdmin();
-
- $reaction = Reaction::build(
- Arr::get($data, 'identifier'),
- Arr::get($data, 'type')
- );
-
- $this->events->dispatch(new Creating($reaction, $actor, $data));
-
- $this->validator->assertValid($reaction->getAttributes());
-
- $reaction->save();
-
- $this->events->dispatch(new Created($reaction, $actor));
-
- return $reaction;
- }
-}
diff --git a/src/Command/DeleteReaction.php b/src/Command/DeleteReaction.php
deleted file mode 100644
index 20eaf04..0000000
--- a/src/Command/DeleteReaction.php
+++ /dev/null
@@ -1,52 +0,0 @@
-reactionId = $reactionId;
- $this->actor = $actor;
- $this->data = $data;
- }
-}
diff --git a/src/Command/DeleteReactionHandler.php b/src/Command/DeleteReactionHandler.php
deleted file mode 100644
index fbf2d95..0000000
--- a/src/Command/DeleteReactionHandler.php
+++ /dev/null
@@ -1,56 +0,0 @@
-events = $events;
- }
-
- /**
- * @param DeleteReaction $command
- *
- * @throws PermissionDeniedException
- *
- * @return void
- */
- public function handle(DeleteReaction $command)
- {
- $actor = $command->actor;
-
- $actor->assertAdmin();
-
- $reaction = Reaction::where('id', $command->reactionId)->first();
-
- $this->events->dispatch(new Deleting($reaction, $actor));
-
- $reaction->delete();
-
- $this->events->dispatch(new Deleted($reaction, $actor));
- }
-}
diff --git a/src/Command/EditReaction.php b/src/Command/EditReaction.php
deleted file mode 100644
index 0184adc..0000000
--- a/src/Command/EditReaction.php
+++ /dev/null
@@ -1,50 +0,0 @@
-reactionId = $reactionId;
- $this->actor = $actor;
- $this->data = $data;
- }
-}
diff --git a/src/Command/EditReactionHandler.php b/src/Command/EditReactionHandler.php
deleted file mode 100644
index ac9498f..0000000
--- a/src/Command/EditReactionHandler.php
+++ /dev/null
@@ -1,73 +0,0 @@
-validator = $validator;
- }
-
- /**
- * @param EditReaction $command
- *
- * @throws PermissionDeniedException
- *
- * @return Reaction
- */
- public function handle(EditReaction $command)
- {
- $actor = $command->actor;
- $data = $command->data;
-
- $actor->assertAdmin();
-
- $reaction = Reaction::query()->where('id', $command->reactionId)->firstOrFail();
-
- if (isset($data['identifier'])) {
- $reaction->identifier = $data['identifier'];
- }
-
- if (isset($data['display'])) {
- $reaction->display = $data['display'];
- }
-
- if (isset($data['type'])) {
- $reaction->type = $data['type'];
- }
-
- if (isset($data['enabled'])) {
- $reaction->enabled = $data['enabled'];
- }
-
- $this->validator->assertValid($reaction->getDirty());
-
- if ($reaction->isDirty()) {
- $reaction->save();
- }
-
- return $reaction;
- }
-}
diff --git a/src/ForumResourceFields.php b/src/ForumResourceFields.php
new file mode 100644
index 0000000..9d04bdf
--- /dev/null
+++ b/src/ForumResourceFields.php
@@ -0,0 +1,38 @@
+get(fn () => [
+ $this->settings->get('fof-reactions.convertToUpvote'),
+ $this->settings->get('fof-reactions.convertToDownvote'),
+ $this->settings->get('fof-reactions.convertToLike'),
+ ]),
+
+ Schema\Relationship\ToMany::make('reactions')
+ ->includable()
+ ->get(fn () => Reaction::all()->all()),
+ ];
+ }
+}
diff --git a/src/Notification/PostReactedBlueprint.php b/src/Notification/PostReactedBlueprint.php
index 1b817ea..f647fe1 100755
--- a/src/Notification/PostReactedBlueprint.php
+++ b/src/Notification/PostReactedBlueprint.php
@@ -11,11 +11,13 @@
namespace FoF\Reactions\Notification;
+use Flarum\Database\AbstractModel;
+use Flarum\Notification\AlertableInterface;
use Flarum\Notification\Blueprint\BlueprintInterface;
use Flarum\Post\Post;
use Flarum\User\User;
-class PostReactedBlueprint implements BlueprintInterface
+class PostReactedBlueprint implements BlueprintInterface, AlertableInterface
{
/**
* @var Post
@@ -46,7 +48,7 @@ public function __construct(Post $post, User $user, string $reaction)
/**
* {@inheritdoc}
*/
- public static function getType()
+ public static function getType(): string
{
return 'postReacted';
}
@@ -54,7 +56,7 @@ public static function getType()
/**
* {@inheritdoc}
*/
- public static function getSubjectModel()
+ public static function getSubjectModel(): string
{
return Post::class;
}
@@ -62,7 +64,7 @@ public static function getSubjectModel()
/**
* {@inheritdoc}
*/
- public function getSubject()
+ public function getSubject(): ?AbstractModel
{
return $this->post;
}
@@ -70,7 +72,7 @@ public function getSubject()
/**
* {@inheritdoc}
*/
- public function getFromUser()
+ public function getFromUser(): ?User
{
return $this->user;
}
@@ -88,7 +90,7 @@ public function getReactionType()
/**
* {@inheritdoc}
*/
- public function getData()
+ public function getData(): mixed
{
return $this->reaction;
}
diff --git a/src/PostAttributes.php b/src/PostAttributes.php
deleted file mode 100644
index 5779b8d..0000000
--- a/src/PostAttributes.php
+++ /dev/null
@@ -1,97 +0,0 @@
-settings = $settings;
- }
-
- public function __invoke(PostSerializer $serializer, Post $post, array $attributes): array
- {
- $actor = $serializer->getActor();
-
- $attributes['canReact'] = (bool) $actor->can('react', $post);
- $attributes['canDeletePostReactions'] = (bool) $actor->can('deleteReactions', $post);
-
- // Get reaction counts for the post.
- $reactionCounts = $this->getReactionCountsForPost($post);
-
- // Get user's reaction to the post.
- $userReaction = $this->getActorReactionForPost($actor, $post, $serializer->getRequest());
-
- // Add custom attributes.
- $attributes['reactionCounts'] = $reactionCounts;
- $attributes['userReactionIdentifier'] = $userReaction;
-
- return $attributes;
- }
-
- protected function getReactionCountsForPost(Post $post): array
- {
- // Initialize counts array
- $counts = [];
-
- // Query for reactions from registered users
- $registeredReactions = PostReaction::where('post_id', $post->id)
- ->groupBy('reaction_id')
- ->selectRaw('reaction_id, COUNT(*) as count')
- ->pluck('count', 'reaction_id');
-
- // Query for anonymous reactions if allowed
- $anonymousReactions = collect([]);
- if ($this->settings->get('fof-reactions.anonymousReactions')) {
- $anonymousReactions = PostAnonymousReaction::where('post_id', $post->id)
- ->groupBy('reaction_id')
- ->selectRaw('reaction_id, COUNT(*) as count')
- ->pluck('count', 'reaction_id');
- }
-
- // Merge the registered and anonymous reactions
- $reactions = Reaction::all();
- foreach ($reactions as $reaction) {
- $counts[$reaction->id] = $registeredReactions->get($reaction->id, 0) + $anonymousReactions->get($reaction->id, 0);
- }
-
- return $counts;
- }
-
- protected function getActorReactionForPost(User $actor, Post $post, ServerRequestInterface $request): ?int
- {
- if ($actor->isGuest()) {
- $session = $request->getAttribute('session');
-
- if ($session === null) {
- return null;
- }
-
- return PostAnonymousReaction::where('post_id', $post->id)
- ->where('guest_id', $session->getId())
- ->value('reaction_id');
- }
-
- return PostReaction::where('post_id', $post->id)
- ->where('user_id', $actor->id)
- ->value('reaction_id');
- }
-}
diff --git a/src/PostReaction.php b/src/PostReaction.php
index 21feffb..db70716 100644
--- a/src/PostReaction.php
+++ b/src/PostReaction.php
@@ -12,6 +12,7 @@
namespace FoF\Reactions;
use Flarum\Database\AbstractModel;
+use Flarum\Database\ScopeVisibilityTrait;
use Flarum\Post\Post;
use Flarum\User\User;
@@ -29,11 +30,13 @@
*/
class PostReaction extends AbstractModel
{
+ use ScopeVisibilityTrait;
+
protected $table = 'post_reactions';
public $timestamps = true;
- public $dates = ['created_at', 'updated_at'];
+ protected $casts = ['created_at' => 'datetime', 'updated_at' => 'datetime'];
public function reaction()
{
diff --git a/src/PostResourceEndpoints.php b/src/PostResourceEndpoints.php
new file mode 100644
index 0000000..e76686c
--- /dev/null
+++ b/src/PostResourceEndpoints.php
@@ -0,0 +1,63 @@
+route('DELETE', '/{id}/reactions/specific/{postReactionId}')
+ ->action($this->action(...))
+ ->response(fn () => new EmptyResponse(204)),
+ Endpoint::make('reactions.type.delete')
+ ->route('DELETE', '/{id}/reactions/type/{reactionId}')
+ ->action($this->action(...))
+ ->response(fn () => new EmptyResponse(204)),
+ ];
+ }
+
+ protected function action(Context $context): null
+ {
+ $actor = $context->getActor();
+
+ /** @var Post $post */
+ $post = $context->model;
+
+ $postReactionId = $context->queryParam('postReactionId');
+ $reactionId = $context->queryParam('reactionId');
+
+ if ($reactionId) {
+ // Delete all post_reactions of a specific type (i.e. `reaction_id`)
+ $actor->assertCan('deleteReactions', $post);
+
+ PostReaction::query()->where('post_id', $post->id)->where('reaction_id', $reactionId)->delete();
+ PostAnonymousReaction::query()->where('post_id', $post->id)->where('reaction_id', $reactionId)->delete();
+ } elseif ($postReactionId) {
+ // Delete a specific post_reaction for the post
+ /**
+ * @var PostReaction $reaction
+ */
+ $reaction = PostReaction::query()->where('post_id', $post->id)->where('id', $postReactionId)->firstOrFail();
+
+ // If the post is not the actor's, they must have permission to delete reactions
+ if ($reaction->user_id != $actor->id) {
+ $actor->assertCan('deleteReactions', $post);
+ } else {
+ $actor->assertCan('react', $post);
+ }
+
+ $reaction->delete();
+ }
+
+ // TODO should this send pusher updates? would need new type for non-specific, otherwise could spam pusher events
+
+ return null;
+ }
+}
diff --git a/src/Listener/SaveReactionsToDatabase.php b/src/PostResourceFields.php
old mode 100755
new mode 100644
similarity index 65%
rename from src/Listener/SaveReactionsToDatabase.php
rename to src/PostResourceFields.php
index 040b7dc..a3efe07
--- a/src/Listener/SaveReactionsToDatabase.php
+++ b/src/PostResourceFields.php
@@ -9,12 +9,14 @@
* file that was distributed with this source code.
*/
-namespace FoF\Reactions\Listener;
+namespace FoF\Reactions;
+use Flarum\Api\Context;
+use Flarum\Api\Schema;
use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\ValidationException;
use Flarum\Likes\Event\PostWasLiked;
-use Flarum\Post\Event\Saving;
+use Flarum\Locale\TranslatorInterface;
use Flarum\Post\Post;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\User;
@@ -22,60 +24,90 @@
use FoF\Reactions\Event\PostWasReacted;
use FoF\Reactions\Event\PostWasUnreacted;
use FoF\Reactions\Event\WillReactToPost;
-use FoF\Reactions\PostAnonymousReaction;
-use FoF\Reactions\PostReaction;
-use FoF\Reactions\Reaction;
use Illuminate\Contracts\Events\Dispatcher;
-use Illuminate\Support\Arr;
use Psr\Http\Message\ServerRequestInterface;
-use Pusher;
-use Symfony\Contracts\Translation\TranslatorInterface;
-class SaveReactionsToDatabase
+class PostResourceFields
{
- /**
- * @var SettingsRepositoryInterface
- */
- protected $settings;
+ public function __construct(
+ protected SettingsRepositoryInterface $settings,
+ protected Dispatcher $events,
+ protected ExtensionManager $extensions,
+ protected TranslatorInterface $translator
+ ) {
+ }
- /**
- * @var TranslatorInterface
- */
- protected $translator;
+ public function __invoke(): array
+ {
+ return [
+ Schema\Boolean::make('canReact')
+ ->get(fn (Post $post, Context $context) => $context->getActor()->can('react', $post)),
+ Schema\Boolean::make('canDeletePostReactions')
+ ->get(fn (Post $post, Context $context) => $context->getActor()->can('deleteReactions', $post)),
+ Schema\Arr::make('reactionCounts')
+ ->get(fn (Post $post) => $this->getReactionCountsForPost($post)),
+ Schema\Number::make('userReactionIdentifier')
+ ->get(fn (Post $post, Context $context) => $this->getActorReactionForPost($context->getActor(), $post, $context->request)),
+
+ Schema\Str::make('reaction')
+ ->hidden()
+ ->writableOnUpdate()
+ ->save($this->setReaction(...)),
+ ];
+ }
- /**
- * @var ExtensionManager
- */
- protected $extensions;
+ protected function getReactionCountsForPost(Post $post): array
+ {
+ // Initialize counts array
+ $counts = [];
+
+ // Query for reactions from registered users
+ $registeredReactions = PostReaction::where('post_id', $post->id)
+ ->groupBy('reaction_id')
+ ->selectRaw('reaction_id, COUNT(*) as count')
+ ->pluck('count', 'reaction_id');
+
+ // Query for anonymous reactions if allowed
+ $anonymousReactions = collect([]);
+ if ($this->settings->get('fof-reactions.anonymousReactions')) {
+ $anonymousReactions = PostAnonymousReaction::where('post_id', $post->id)
+ ->groupBy('reaction_id')
+ ->selectRaw('reaction_id, COUNT(*) as count')
+ ->pluck('count', 'reaction_id');
+ }
- /**
- * @var Dispatcher
- */
- protected $events;
+ // Merge the registered and anonymous reactions
+ $reactions = Reaction::all();
+ foreach ($reactions as $reaction) {
+ $counts[$reaction->id] = $registeredReactions->get($reaction->id, 0) + $anonymousReactions->get($reaction->id, 0);
+ }
- public function __construct(SettingsRepositoryInterface $settings, TranslatorInterface $translator, ExtensionManager $extensions, Dispatcher $events)
- {
- $this->settings = $settings;
- $this->translator = $translator;
- $this->extensions = $extensions;
- $this->events = $events;
+ return $counts;
}
- /**
- * @param Saving $event
- *
- * @throws \Flarum\User\Exception\PermissionDeniedException
- * @throws \Flarum\Foundation\ValidationException
- */
- public function handle(Saving $event)
+ protected function getActorReactionForPost(User $actor, Post $post, ServerRequestInterface $request): ?int
{
- $post = $event->post;
- $data = $event->data;
+ if ($actor->isGuest()) {
+ $session = $request->getAttribute('session');
+
+ if ($session === null) {
+ return null;
+ }
+
+ return PostAnonymousReaction::where('post_id', $post->id)
+ ->where('guest_id', $session->getId())
+ ->value('reaction_id');
+ }
- if ($post->exists && Arr::has($data, 'attributes.reaction')) {
- $actor = $event->actor;
+ return PostReaction::where('post_id', $post->id)
+ ->where('user_id', $actor->id)
+ ->value('reaction_id');
+ }
- $reactionId = Arr::get($data, 'attributes.reaction');
+ protected function setReaction(Post $post, ?string $reactionId, Context $context): void
+ {
+ if ($post->exists) {
+ $actor = $context->getActor();
$actor->assertCan('react', $post);
@@ -83,8 +115,8 @@ public function handle(Saving $event)
$this->events->dispatch(new WillReactToPost($post, $actor, $reaction));
- $gamification = $this->isExtensionEnabled('fof-gamification');
- $likes = $this->isExtensionEnabled('flarum-likes');
+ $gamification = $this->extensions->isEnabled('fof-gamification');
+ $likes = $this->extensions->isEnabled('flarum-likes');
$gamificationUpvote = $this->settings->get('fof-reactions.convertToUpvote');
$gamificationDownvote = $this->settings->get('fof-reactions.convertToDownvote');
@@ -121,12 +153,14 @@ public function handle(Saving $event)
$post->raise(new PostWasLiked($post, $actor));
}
} else {
- $guestId = $this->getSessionId();
+ $guestId = $context->request->getAttribute('session')?->getId();
+
if ($actor->isGuest()) {
$postReaction = PostAnonymousReaction::where([['guest_id', $guestId], ['post_id', $post->id]])->first();
} else {
$postReaction = PostReaction::where([['user_id', $actor->id], ['post_id', $post->id]])->first();
}
+
$removeReaction = is_null($reactionId) || ($postReaction && $postReaction->reaction_id == $reactionId);
if ($removeReaction) {
@@ -220,18 +254,4 @@ protected function validateReaction($reactionId)
]);
}
}
-
- protected function isExtensionEnabled(string $extension): bool
- {
- return $this->extensions->isEnabled($extension);
- }
-
- protected function getSessionId(): ?string
- {
- /** @var ServerRequestInterface $request */
- $request = resolve('fof-reactions.request');
- $session = $request->getAttribute('session');
-
- return $session ? $session->getId() : null;
- }
}
diff --git a/src/ReactionsForumAttributes.php b/src/ReactionsForumAttributes.php
deleted file mode 100644
index 091092c..0000000
--- a/src/ReactionsForumAttributes.php
+++ /dev/null
@@ -1,39 +0,0 @@
-settings = $settings;
- }
-
- public function __invoke(ForumSerializer $serializer): array
- {
- $attributes['ReactionConverts'] = [
- $this->settings->get('fof-reactions.convertToUpvote'),
- $this->settings->get('fof-reactions.convertToDownvote'),
- $this->settings->get('fof-reactions.convertToLike'),
- ];
-
- return $attributes;
- }
-}
diff --git a/src/Search/Filter/PostFilter.php b/src/Search/Filter/PostFilter.php
new file mode 100644
index 0000000..e1087ce
--- /dev/null
+++ b/src/Search/Filter/PostFilter.php
@@ -0,0 +1,39 @@
+
+ */
+class PostFilter implements FilterInterface
+{
+ use ValidateFilterTrait;
+
+ public function __construct(
+ protected PostRepository $posts
+ ) {
+ }
+
+ public function getFilterKey(): string
+ {
+ return 'post';
+ }
+
+ public function filter(SearchState $state, string|array $value, bool $negate): void
+ {
+ $value = $this->asInt($value);
+
+ $post = $this->posts->findOrFail($value, $state->getActor());
+
+ $state->getActor()->assertCan('canSeeReactions', $post->discussion);
+
+ $state->getQuery()->where('post_id', $negate ? '!=' : '=', $value);
+ }
+}
diff --git a/src/Search/PostReactionSearcher.php b/src/Search/PostReactionSearcher.php
new file mode 100644
index 0000000..e7f9db8
--- /dev/null
+++ b/src/Search/PostReactionSearcher.php
@@ -0,0 +1,40 @@
+whereNotNull('reaction_id')
+ ->whereVisibleTo($actor);
+
+ if ($this->settings->get('fof-reactions.anonymousReactions')) {
+ $query->unionAll(
+ PostAnonymousReaction::query()
+ ->whereNotNull('reaction_id')
+ ->whereVisibleTo($actor)
+ ->toBase()
+ );
+ }
+
+ return $query;
+ }
+}
diff --git a/src/Validator/ReactionValidator.php b/src/Validator/ReactionValidator.php
deleted file mode 100644
index 01492aa..0000000
--- a/src/Validator/ReactionValidator.php
+++ /dev/null
@@ -1,38 +0,0 @@
- [
- 'required',
- 'string',
- 'unique:reactions',
- ],
- 'display' => [
- 'nullable',
- 'string',
- ],
- 'type' => [
- 'required',
- 'string',
- 'regex:/icon|emoji/i',
- ],
- 'enabled' => [
- 'nullable',
- 'bool',
- ],
- ];
-}
diff --git a/tests/integration/api/CreateReactionTest.php b/tests/integration/api/CreateReactionTest.php
index 55da001..33ba41d 100644
--- a/tests/integration/api/CreateReactionTest.php
+++ b/tests/integration/api/CreateReactionTest.php
@@ -14,6 +14,7 @@
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use FoF\Reactions\Reaction;
+use PHPUnit\Framework\Attributes\Test;
class CreateReactionTest extends TestCase
{
@@ -32,9 +33,7 @@ public function setUp(): void
]);
}
- /**
- * @test
- */
+ #[Test]
public function admin_can_create_reaction()
{
$response = $this->send(
@@ -75,9 +74,7 @@ public function admin_can_create_reaction()
$this->assertEquals('emoji', $reaction->type);
}
- /**
- * @test
- */
+ #[Test]
public function normal_user_cannot_create_reaction()
{
$response = $this->send(
@@ -99,9 +96,7 @@ public function normal_user_cannot_create_reaction()
$this->assertEquals(403, $response->getStatusCode());
}
- /**
- * @test
- */
+ #[Test]
public function cannot_create_reaction_without_identifier()
{
$response = $this->send(
@@ -127,9 +122,7 @@ public function cannot_create_reaction_without_identifier()
$this->assertEquals('/data/attributes/identifier', $json['errors'][0]['source']['pointer']);
}
- /**
- * @test
- */
+ #[Test]
public function cannot_create_reaction_without_type()
{
$response = $this->send(
@@ -155,9 +148,7 @@ public function cannot_create_reaction_without_type()
$this->assertEquals('/data/attributes/type', $json['errors'][0]['source']['pointer']);
}
- /**
- * @test
- */
+ #[Test]
public function cannot_create_reaction_with_invalid_type()
{
$response = $this->send(
diff --git a/tests/integration/api/DeleteReactionTest.php b/tests/integration/api/DeleteReactionTest.php
index ba2e7fa..439d726 100644
--- a/tests/integration/api/DeleteReactionTest.php
+++ b/tests/integration/api/DeleteReactionTest.php
@@ -14,6 +14,7 @@
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use FoF\Reactions\Reaction;
+use PHPUnit\Framework\Attributes\Test;
class DeleteReactionTest extends TestCase
{
@@ -32,9 +33,7 @@ public function setUp(): void
]);
}
- /**
- * @test
- */
+ #[Test]
public function admin_can_delete_reaction()
{
$this->app();
@@ -52,9 +51,7 @@ public function admin_can_delete_reaction()
$this->assertNull(Reaction::find(5));
}
- /**
- * @test
- */
+ #[Test]
public function normal_user_cannot_delete_reaction()
{
$response = $this->send(
diff --git a/tests/integration/api/EditReactionTest.php b/tests/integration/api/EditReactionTest.php
index fc6d03a..b6773c5 100644
--- a/tests/integration/api/EditReactionTest.php
+++ b/tests/integration/api/EditReactionTest.php
@@ -14,6 +14,7 @@
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use FoF\Reactions\Reaction;
+use PHPUnit\Framework\Attributes\Test;
class EditReactionTest extends TestCase
{
@@ -55,9 +56,7 @@ protected function addNewReaction(): int
return $response['data']['id'];
}
- /**
- * @test
- */
+ #[Test]
public function admin_can_edit_reaction()
{
$this->app();
@@ -96,9 +95,7 @@ public function admin_can_edit_reaction()
$this->assertTrue($reaction->enabled);
}
- /**
- * @test
- */
+ #[Test]
public function normal_user_cannot_edit_reaction()
{
$id = $this->addNewReaction();
@@ -118,9 +115,7 @@ public function normal_user_cannot_edit_reaction()
$this->assertEquals(403, $response->getStatusCode());
}
- /**
- * @test
- */
+ #[Test]
public function cannot_edit_reaction_with_invalid_type()
{
$id = $this->addNewReaction();
@@ -139,9 +134,7 @@ public function cannot_edit_reaction_with_invalid_type()
$this->assertEquals(422, $response->getStatusCode());
}
- /**
- * @test
- */
+ #[Test]
public function cannot_edit_non_existent_reaction()
{
$response = $this->send(
diff --git a/tests/integration/api/ForumTest.php b/tests/integration/api/ForumTest.php
index c3a6ceb..02581e7 100644
--- a/tests/integration/api/ForumTest.php
+++ b/tests/integration/api/ForumTest.php
@@ -12,6 +12,7 @@
namespace FoF\Reactions\tests\integration\api;
use Flarum\Testing\integration\TestCase;
+use PHPUnit\Framework\Attributes\Test;
class ForumTest extends TestCase
{
@@ -22,9 +23,7 @@ public function setUp(): void
$this->extension('fof-reactions');
}
- /**
- * @test
- */
+ #[Test]
public function forum_relations_are_included()
{
$response = $this->send(
diff --git a/tests/integration/api/ListPostReactionsTest.php b/tests/integration/api/ListPostReactionsTest.php
index 6589ca5..0384287 100644
--- a/tests/integration/api/ListPostReactionsTest.php
+++ b/tests/integration/api/ListPostReactionsTest.php
@@ -15,6 +15,7 @@
use Flarum\Group\Group;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
+use PHPUnit\Framework\Attributes\Test;
class ListPostReactionsTest extends TestCase
{
@@ -82,9 +83,7 @@ protected function addGuestViewPermission()
]);
}
- /**
- * @test
- */
+ #[Test]
public function guest_cannot_see_reactions_when_permission_not_given_on_a_post_when_guest_reacting_is_off()
{
$response = $this->send(
@@ -95,9 +94,7 @@ public function guest_cannot_see_reactions_when_permission_not_given_on_a_post_w
$this->assertEquals(403, $response->getStatusCode());
}
- /**
- * @test
- */
+ #[Test]
public function guest_can_see_reactions_when_permission_given_on_a_post_when_guest_reacting_is_off()
{
$this->addGuestViewPermission();
@@ -139,9 +136,7 @@ public function guest_can_see_reactions_when_permission_given_on_a_post_when_gue
$this->assertEquals('users', $response['data'][3]['relationships']['user']['data']['type']);
}
- /**
- * @test
- */
+ #[Test]
public function guest_can_see_reactions_on_a_post_when_guest_reacting_is_on()
{
$this->setting('fof-reactions.anonymousReactions', true);
@@ -159,9 +154,7 @@ public function guest_can_see_reactions_on_a_post_when_guest_reacting_is_on()
$this->assertEquals(8, count($response['data']));
}
- /**
- * @test
- */
+ #[Test]
public function user_with_view_permission_can_see_reactions_on_a_post_when_guest_reacting_is_off()
{
$response = $this->send(
@@ -202,9 +195,7 @@ public function user_with_view_permission_can_see_reactions_on_a_post_when_guest
$this->assertEquals('users', $response['data'][3]['relationships']['user']['data']['type']);
}
- /**
- * @test
- */
+ #[Test]
public function user_with_view_permission_can_see_reactions_on_a_post_when_guest_reacting_is_on()
{
$this->setting('fof-reactions.anonymousReactions', true);
@@ -222,9 +213,7 @@ public function user_with_view_permission_can_see_reactions_on_a_post_when_guest
$this->assertEquals(8, count($response['data']));
}
- /**
- * @test
- */
+ #[Test]
public function user_without_view_permission_cannot_see_reactions_on_a_post_when_guest_reacting_is_off()
{
$response = $this->send(
@@ -236,9 +225,7 @@ public function user_without_view_permission_cannot_see_reactions_on_a_post_when
$this->assertEquals(403, $response->getStatusCode());
}
- /**
- * @test
- */
+ #[Test]
public function user_without_view_permission_cannot_see_reactions_on_a_post_when_guest_reacting_is_on()
{
$this->setting('fof-reactions.anonymousReactions', true);
diff --git a/tests/integration/api/ListReactionsTest.php b/tests/integration/api/ListReactionsTest.php
index 2fd9f15..085cca0 100644
--- a/tests/integration/api/ListReactionsTest.php
+++ b/tests/integration/api/ListReactionsTest.php
@@ -13,6 +13,7 @@
use Flarum\Testing\integration\TestCase;
use FoF\Reactions\Reaction;
+use PHPUnit\Framework\Attributes\Test;
class ListReactionsTest extends TestCase
{
@@ -23,9 +24,7 @@ public function setUp(): void
$this->extension('fof-reactions');
}
- /**
- * @test
- */
+ #[Test]
public function it_returns_all_reactions()
{
$response = $this->send(
diff --git a/tests/integration/api/PostAttributesTest.php b/tests/integration/api/PostAttributesTest.php
index 8bbbdb5..714f0ad 100644
--- a/tests/integration/api/PostAttributesTest.php
+++ b/tests/integration/api/PostAttributesTest.php
@@ -14,6 +14,7 @@
use Carbon\Carbon;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
+use PHPUnit\Framework\Attributes\Test;
class PostAttributesTest extends TestCase
{
@@ -72,9 +73,7 @@ protected function arrayOfReactionCounts(int $anon = 0): array
];
}
- /**
- * @test
- */
+ #[Test]
public function user_has_correct_post_attributes_when_anonymous_reactions_disabled()
{
$response = $this->send(
@@ -92,9 +91,7 @@ public function user_has_correct_post_attributes_when_anonymous_reactions_disabl
$this->assertEquals(1, $body['data']['attributes']['userReactionIdentifier'], 'User has reacted with reaction id 1');
}
- /**
- * @test
- */
+ #[Test]
public function guest_has_correct_post_attributes_when_anonymous_reactions_disabled()
{
$response = $this->send(
@@ -111,9 +108,7 @@ public function guest_has_correct_post_attributes_when_anonymous_reactions_disab
$this->assertEquals(null, $body['data']['attributes']['userReactionIdentifier'], 'User has reacted with reaction id 1');
}
- /**
- * @test
- */
+ #[Test]
public function user_has_correct_post_attributes_when_anonymous_reactions_enabled()
{
$this->setting('fof-reactions.anonymousReactions', true);
@@ -133,9 +128,7 @@ public function user_has_correct_post_attributes_when_anonymous_reactions_enable
$this->assertEquals(1, $body['data']['attributes']['userReactionIdentifier'], 'User has reacted with reaction id 1');
}
- /**
- * @test
- */
+ #[Test]
public function guest_has_correct_post_attributes_when_anonymous_reactions_enabled()
{
$this->setting('fof-reactions.anonymousReactions', true);
diff --git a/tests/integration/api/ReactTest.php b/tests/integration/api/ReactTest.php
index 91f6888..8cd21b6 100644
--- a/tests/integration/api/ReactTest.php
+++ b/tests/integration/api/ReactTest.php
@@ -19,6 +19,8 @@
use FoF\Reactions\PostAnonymousReaction;
use FoF\Reactions\PostReaction;
use Psr\Http\Message\ResponseInterface;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\Test;
class ReactTest extends TestCase
{
@@ -72,11 +74,8 @@ protected function rewriteDefaultPermissionsAfterBoot()
$this->database()->table('group_permission')->insert(['permission' => 'discussion.reactPosts', 'group_id' => 5]);
}
- /**
- * @dataProvider allowedUsersToReact
- *
- * @test
- */
+ #[Test]
+ #[DataProvider('allowedUsersToReact')]
public function can_react_to_a_post_if_allowed(int $postId, ?int $authenticatedAs, int $reactionId, string $message, bool $canReactOwnPost = null, bool $guestReactionsEnabled = null)
{
if (!is_null($canReactOwnPost)) {
@@ -117,11 +116,8 @@ public function can_react_to_a_post_if_allowed(int $postId, ?int $authenticatedA
}
}
- /**
- * @dataProvider unallowedUsersToReact
- *
- * @test
- */
+ #[Test]
+ #[DataProvider('unallowedUsersToReact')]
public function cannot_react_to_a_post_if_not_allowed(int $postId, ?int $authenticatedAs, int $reactionId, string $message, bool $canReactOwnPost = null, bool $guestReactionsEnabled = null)
{
if (!is_null($canReactOwnPost)) {
@@ -147,7 +143,7 @@ public function cannot_react_to_a_post_if_not_allowed(int $postId, ?int $authent
}
}
- public function allowedUsersToReact(): array
+ public static function allowedUsersToReact(): array
{
return [
// [$postId, $authAs, $reactionId, $message, $canReactOwnPost, $guestReactionsEnabled]
@@ -160,7 +156,7 @@ public function allowedUsersToReact(): array
];
}
- public function unallowedUsersToReact(): array
+ public static function unallowedUsersToReact(): array
{
return [
// [$postId, $authAs, $reactionId, $message, $canReactOwnPost, $guestReactionsEnabled]
@@ -211,9 +207,7 @@ protected function disableReactionId(int $reactionId): void
$this->database()->table('reactions')->where('id', $reactionId)->update(['enabled' => false]);
}
- /**
- * @test
- */
+ #[Test]
public function user_cannot_react_to_a_post_if_reaction_disabled()
{
$this->disableReactionId(1);
@@ -231,9 +225,7 @@ public function user_cannot_react_to_a_post_if_reaction_disabled()
$this->assertNull($postReaction, 'Reaction was saved to database');
}
- /**
- * @test
- */
+ #[Test]
public function guest_cannot_react_to_a_post_when_feature_is_enabled_but_reaction_disabled()
{
$this->setting('fof-reactions.anonymousReactions', true);
@@ -252,11 +244,8 @@ public function guest_cannot_react_to_a_post_when_feature_is_enabled_but_reactio
$this->assertNull($postReaction, 'Anonymous reaction was saved to database');
}
- /**
- * @dataProvider deleteSpecificPostReactionUsersData
- *
- * @test
- */
+ #[Test]
+ #[DataProvider('deleteSpecificPostReactionUsersData')]
public function user_can_delete_own_post_reaction_by_id($reactionAs, $authAs, $message, $statusCode)
{
$this->sendReactRequest(1, 1, $reactionAs);
@@ -299,7 +288,7 @@ public function user_can_delete_own_post_reaction_by_id($reactionAs, $authAs, $m
}
}
- public function deleteSpecificPostReactionUsersData()
+ public static function deleteSpecificPostReactionUsersData()
{
return [
// [$reactionAs, $authAs, $message, $statusCode]
@@ -311,9 +300,7 @@ public function deleteSpecificPostReactionUsersData()
];
}
- /**
- * @test
- */
+ #[Test]
public function user_with_permission_can_react_and_is_converted_to_like_when_likes_is_enabled()
{
$this->extension('flarum-likes');
@@ -334,9 +321,7 @@ public function user_with_permission_can_react_and_is_converted_to_like_when_lik
$this->assertTrue($likes->contains(3), 'User is in the collection of users who liked the post');
}
- /**
- * @test
- */
+ #[Test]
public function user_with_permission_can_react_and_not_have_it_converted_to_a_like()
{
$this->extension('flarum-likes');
@@ -355,9 +340,7 @@ public function user_with_permission_can_react_and_not_have_it_converted_to_a_li
$this->assertCount(0, $likes);
}
- /**
- * @test
- */
+ #[Test]
public function empty_string_as_convert_like_setting_does_nothing()
{
$this->extension('flarum-likes');