aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--db/migrations/6.2.2_add_unique_constraint_to_forum_posting_reactions.php27
-rw-r--r--lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php42
-rw-r--r--resources/vue/components/forum/posts/PostReactions.vue16
3 files changed, 67 insertions, 18 deletions
diff --git a/db/migrations/6.2.2_add_unique_constraint_to_forum_posting_reactions.php b/db/migrations/6.2.2_add_unique_constraint_to_forum_posting_reactions.php
new file mode 100644
index 0000000..4a73a9f
--- /dev/null
+++ b/db/migrations/6.2.2_add_unique_constraint_to_forum_posting_reactions.php
@@ -0,0 +1,27 @@
+<?php
+
+final class AddUniqueConstraintToForumPostingReactions extends Migration
+{
+ protected function up()
+ {
+ // remove duplicate reactions
+ DBManager::get()->exec("
+ DELETE t1
+ FROM forum_posting_reactions AS t1
+ JOIN forum_posting_reactions AS t2
+ ON t1.posting_id = t2.posting_id
+ AND t1.user_id = t2.user_id
+ AND t1.emoji = t2.emoji
+ AND t1.id > t2.id;
+ ");
+
+ DBManager::get()->exec(
+ "ALTER TABLE forum_posting_reactions ADD CONSTRAINT unique_posting_user_emoji UNIQUE (posting_id, user_id, emoji)"
+ );
+ }
+
+ protected function down()
+ {
+ DBManager::get()->exec("ALTER TABLE forum_posting_reactions DROP INDEX unique_posting_user_emoji");
+ }
+}
diff --git a/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php b/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php
index 992cf0d..60e66c0 100644
--- a/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php
+++ b/lib/classes/JsonApi/Routes/Forum/PostingReactionStore.php
@@ -38,23 +38,39 @@ class PostingReactionStore extends JsonApiController
throw new AuthorizationFailedException();
}
- $posting_reaction = PostingReaction::create([
+ $data = [
'posting_id' => $posting->posting_id,
- 'user_id' => $user->user_id,
- 'emoji' => self::arrayGet($json, 'data.attributes.emoji')
- ]);
+ 'user_id' => $user->user_id,
+ 'emoji' => self::arrayGet($json, 'data.attributes.emoji'),
+ ];
+
+ $reaction = PostingReaction::findOneBySQL(
+ "posting_id = :posting_id AND user_id = :user_id AND emoji = :emoji",
+ $data
+ );
- if ($user->user_id !== $posting->user_id) {
- \PersonalNotifications::add(
- $posting->user_id,
- \URLHelper::getURL('dispatch.php/course/forum/discussions/show/'.$posting->discussion_id, ['cid' => $posting->range_id], true)."#post_" . $posting->posting_id,
- sprintf(_("%s hat auf deinen Beitrag reagiert."), $user->getFullName()),
- null,
- self::arrayGet($json, 'data.meta.emoji-icon')
- );
+ if (!$reaction) {
+ $reaction = PostingReaction::create($data);
+
+ if ($user->user_id !== $posting->user_id) {
+ \PersonalNotifications::add(
+ $posting->user_id,
+ \URLHelper::getURL(
+ "dispatch.php/course/forum/discussions/show/{$posting->discussion_id}#post_{$posting->posting_id}",
+ ['cid' => $posting->range_id],
+ true
+ ),
+ studip_interpolate(
+ _('%{name} hat auf deinen Beitrag reagiert.'),
+ ['name' => $user->getFullName()]
+ ),
+ null,
+ self::arrayGet($json, 'data.meta.emoji-icon')
+ );
+ }
}
- return $this->getCreatedResponse($posting_reaction);
+ return $this->getCreatedResponse($reaction);
}
protected function validateResourceDocument($json, $data)
diff --git a/resources/vue/components/forum/posts/PostReactions.vue b/resources/vue/components/forum/posts/PostReactions.vue
index 31643ba..ca5e5a2 100644
--- a/resources/vue/components/forum/posts/PostReactions.vue
+++ b/resources/vue/components/forum/posts/PostReactions.vue
@@ -27,6 +27,7 @@ const props = defineProps({
const showReactions = ref(false);
const reactionStatusMessage = ref(null);
+const isLoading = ref(false);
const transformedReactions = computed(() => props.reactions.map(reaction => {
return {
@@ -39,7 +40,7 @@ const groupedReactions = computed(() => Object.groupBy(transformedReactions.valu
const announceToScreenReader = message => reactionStatusMessage.value.textContent = message;
-const getPostReactionJSONAPIObject = (emoji) => ({
+const getPostReactionJSONAPIObject = emoji => ({
data: {
type: 'forum-posting-reactions',
attributes: {
@@ -57,9 +58,9 @@ const getPostReactionJSONAPIObject = (emoji) => ({
}
}
}
-})
+});
-const storeReaction = async (emoji) => {
+const storeReaction = async emoji => {
try {
const response = await STUDIP.jsonapi.withPromises().POST(
'forum-posting-reactions?include=user&fields[users]=id,username,formatted-name',
@@ -69,25 +70,28 @@ const storeReaction = async (emoji) => {
const reaction = await deserializeJSONAPIResponse(response);
forumDiscussionPost.addPostReaction(reaction, props.posting_id);
showReactions.value = false;
+ return reaction;
} catch (error) {
STUDIP.Report.error(error);
}
}
-const deleteReaction = async (reactionId) => {
+const deleteReaction = async reactionId => {
try {
await STUDIP.jsonapi.withPromises().DELETE(`forum-posting-reactions/${reactionId}`);
forumDiscussionPost.removePostReaction(reactionId, props.posting_id);
+ return true;
} catch (error) {
STUDIP.Report.error(error);
}
}
const toggleReaction = async (emoji, reactions = transformedReactions.value) => {
- if (forumConfig.allowGuestAccess) {
+ if (forumConfig.allowGuestAccess || isLoading.value) {
return;
}
+ isLoading.value = true;
const userReaction = findUserReaction(emoji, reactions);
if (userReaction) {
@@ -97,6 +101,8 @@ const toggleReaction = async (emoji, reactions = transformedReactions.value) =>
await storeReaction(emoji);
announceToScreenReader($gettext('Reaktion wurde hinzugefügt.'));
}
+
+ isLoading.value = false;
}
const findUserReaction = (emoji, reactions = transformedReactions.value) => reactions.find(reaction => reaction.user.id === STUDIP.USER_ID && reaction.emoji === emoji);