PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
← All changes | app/Http/Controllers/ReactionController.php +36 -12 2.7.72.10.01 View file →
@@ -4,9 +4,11 @@
4 4
5 5 use FluentCommunity\App\Models\Comment;
6 6 use FluentCommunity\App\Models\Feed;
7 7 use FluentCommunity\App\Models\Reaction;
8 +use FluentCommunity\App\Models\XProfile;
8 9 use FluentCommunity\App\Services\FeedsHelper;
10 +use FluentCommunity\App\Services\Helper;
9 11 use FluentCommunity\App\Services\ProfileHelper;
10 12 use FluentCommunity\Framework\Http\Request\Request;
11 13 use FluentCommunity\Framework\Support\Arr;
12 14
@@ -78,17 +80,18 @@
78 80 {
79 81 $currentUser = $this->getUser(true);
80 82 $feed = Feed::withoutGlobalScopes()->byUserAccess($currentUser->ID)->findOrFail($feed_id);
81 83 $type = $request->get('react_type', 'like');
84 + $type = in_array($type, ['like', 'bookmark'], true) ? $type : 'like';
82 85 $willRemove = $request->get('remove');
83 86
84 - if ($feed->status != 'published') {
87 + if (!in_array($feed->status, FeedsHelper::getViewableByLinkStatuses(), true)) {
85 88 return $this->sendError([
86 89 'message' => __('This post is not published yet', 'fluent-community')
87 90 ]);
88 91 }
89 92
90 - if ($currentUser->ID === $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) {
93 + if (!$willRemove && (int) $currentUser->ID === (int) $feed->user_id && apply_filters('fluent_community/disable_self_post_react', false, $feed)) {
91 94 return $this->sendError([
92 95 'message' => __('You cannot react to your own post', 'fluent-community')
93 96 ]);
94 97 }
@@ -122,20 +125,41 @@
122 125 'new_count' => $feed->reactions_count
123 126 ];
124 127 }
125 128
126 - $react = Reaction::create([
127 - 'user_id' => $currentUser->ID,
128 - 'object_id' => $feed->id,
129 - 'type' => $type,
130 - 'object_type' => 'feed'
131 - ]);
129 + // Serialize a user's concurrent reactions by locking their profile row,
130 + // so parallel add requests cannot each insert a duplicate reaction. The
131 + // like counter is updated atomically, so unrelated users never contend.
132 + $react = Helper::dbTransaction(function () use ($feed, $currentUser, $type) {
133 + XProfile::where('user_id', $currentUser->ID)->lockForUpdate()->first();
132 134
133 - if ($type == 'like') {
134 - $feed->reactions_count = $feed->reactions_count + 1;
135 - $feed->timestamps = false; // Don't update the updated_at timestamp
136 - $feed->save();
135 + $react = Reaction::where('user_id', $currentUser->ID)
136 + ->where('object_id', $feed->id)
137 + ->where('type', $type)
138 + ->objectType('feed')
139 + ->first();
137 140
141 + if ($react) {
142 + return $react;
143 + }
144 +
145 + $react = Reaction::create([
146 + 'user_id' => $currentUser->ID,
147 + 'object_id' => $feed->id,
148 + 'type' => $type,
149 + 'object_type' => 'feed'
150 + ]);
151 +
152 + if ($type == 'like') {
153 + // getQuery() so the atomic increment does not touch updated_at
154 + Feed::withoutGlobalScopes()->where('id', $feed->id)->getQuery()->increment('reactions_count');
155 + $feed->reactions_count = $feed->reactions_count + 1;
156 + }
157 +
158 + return $react;
159 + });
160 +
161 + if ($react->wasRecentlyCreated && $type == 'like') {
138 162 $react->load('xprofile');
139 163 do_action('fluent_community/feed/react_added', $react, $feed);
140 164 }
141 165