Skip to content

Commit

Permalink
Cache reaction counts
Browse files Browse the repository at this point in the history
  • Loading branch information
TheEssem committed May 24, 2024
1 parent 2422b50 commit 190bdc1
Show file tree
Hide file tree
Showing 12 changed files with 59 additions and 10 deletions.
15 changes: 13 additions & 2 deletions app/controllers/api/v1/statuses/reactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,20 @@ def create
end

def destroy
UnreactWorker.perform_async(current_account.id, @status.id, params[:id])
react = current_account.status_reactions.find_by(status_id: params[:status_id], name: params[:id])

render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reactions_map: { @status.id => false })
if react
@status = react.status
count = [@status.reactions_count - 1, 0].max
UnreactWorker.perform_async(current_account.id, @status.id, params[:id])
else
@status = Status.find(params[:status_id])
count = @status.reactions_count
authorize @status, :show?
end

relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reactions_map: { @status.id => false }, attributes_map: { @status.id => { reactions_count: count } })
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
rescue Mastodon::NotPermittedError
not_found
end
Expand Down
6 changes: 4 additions & 2 deletions app/javascript/flavours/glitch/actions/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ export const addReaction = (statusId, name, url) => (dispatch, getState) => {

// encodeURIComponent is required for the Keycap Number Sign emoji, see:
// <https://github.com/glitch-soc/mastodon/pull/1980#issuecomment-1345538932>
api(getState).post(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(name)}`).then(() => {
api(getState).post(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(name)}`).then((response) => {
dispatch(importFetchedStatus(response.data));
dispatch(addReactionSuccess(statusId, name));
}).catch(err => {
if (!alreadyAdded) {
Expand Down Expand Up @@ -582,7 +583,8 @@ export const addReactionFail = (statusId, name, error) => ({
export const removeReaction = (statusId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(statusId, name));

api(getState).post(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(name)}`).then(() => {
api(getState).post(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(name)}`).then((response) => {
dispatch(importFetchedStatus(response.data));
dispatch(removeReactionSuccess(statusId, name));
}).catch(err => {
dispatch(removeReactionFail(statusId, name, err));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ class StatusActionBar extends ImmutablePureComponent {
/>
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
<EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} title={intl.formatMessage(messages.react)} icon={AddReactionIcon} disabled={!canReact} />
<EmojiPickerDropdown className='status__action-bar-button' onPickEmoji={this.handleEmojiPick} title={intl.formatMessage(messages.react)} icon={AddReactionIcon} disabled={!canReact} counter={withCounters ? status.get('reactions_count') : undefined} />
<IconButton className='status__action-bar-button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />

{filterButton}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,9 @@ class DetailedStatus extends ImmutablePureComponent {
reactionLink = (
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reactions`} className='detailed-status__link'>
<span className='detailed-status__reactions'>
<AnimatedNumber value={status.get('reactions').reduce((total, obj) => total + obj.get('count'), 0)} />
<AnimatedNumber value={status.get('reactions_count')} />
</span>
<FormattedMessage id='status.reactions' defaultMessage='{count, plural, one {reaction} other {reactions}}' values={{ count: status.get('reactions').reduce((total, obj) => total + obj.get('count'), 0) }} />
<FormattedMessage id='status.reactions' defaultMessage='{count, plural, one {reaction} other {reactions}}' values={{ count: status.get('reactions_count') }} />
</Link>
);
} else {
Expand All @@ -293,9 +293,9 @@ class DetailedStatus extends ImmutablePureComponent {
reactionLink = (
<a href={`/interact/${status.get('id')}?type=reaction`} className='detailed-status__link' onClick={this.handleModalLink}>
<span className='detailed-status__reactions'>
<AnimatedNumber value={status.get('reactions').reduce((total, obj) => total + obj.get('count'), 0)} />
<AnimatedNumber value={status.get('reactions_count')} />
</span>
<FormattedMessage id='status.reactions' defaultMessage='{count, plural, one {reaction} other {reactions}}' values={{ count: status.get('reactions').reduce((total, obj) => total + obj.get('count'), 0) }} />
<FormattedMessage id='status.reactions' defaultMessage='{count, plural, one {reaction} other {reactions}}' values={{ count: status.get('reactions_count') }} />
</a>
);
}
Expand Down
4 changes: 4 additions & 0 deletions app/models/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ def favourites_count
status_stat&.favourites_count || 0
end

def reactions_count
status_stat&.reactions_count || 0
end

def increment_count!(key)
update_status_stat!(key => public_send(key) + 1)
end
Expand Down
13 changes: 13 additions & 0 deletions app/models/status_reaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,21 @@ class StatusReaction < ApplicationRecord

before_validation :set_custom_emoji

after_create :increment_cache_counters
after_destroy :decrement_cache_counters

private

def increment_cache_counters
status&.increment_count!(:reactions_count)
end

def decrement_cache_counters
return if association(:status).loaded? && status.marked_for_destruction?

status&.decrement_count!(:reactions_count)
end

# Sets custom_emoji to nil when disabled
def set_custom_emoji
self.custom_emoji = CustomEmoji.find_by(disabled: false, shortcode: name, domain: custom_emoji.domain) if name.present? && custom_emoji.present?
Expand Down
5 changes: 5 additions & 0 deletions app/models/status_stat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# replies_count :bigint(8) default(0), not null
# reblogs_count :bigint(8) default(0), not null
# favourites_count :bigint(8) default(0), not null
# reactions_count :bigint(8) default(0), not null
# created_at :datetime not null
# updated_at :datetime not null
#
Expand All @@ -27,4 +28,8 @@ def reblogs_count
def favourites_count
[attributes['favourites_count'], 0].max
end

def reactions_count
[attributes['reactions_count'], 0].max
end
end
6 changes: 5 additions & 1 deletion app/serializers/rest/status_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
:favourites_count, :edited_at, :conversation_id
:favourites_count, :reactions_count, :edited_at, :conversation_id

attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?
Expand Down Expand Up @@ -96,6 +96,10 @@ def favourites_count
relationships&.attributes_map&.dig(object.id, :favourites_count) || object.favourites_count
end

def reactions_count
relationships&.attributes_map&.dig(object.id, :reactions_count) || object.reactions_count
end

def favourited
if relationships
relationships.favourites_map[object.id] || false
Expand Down
1 change: 1 addition & 0 deletions app/services/delete_account_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ def purge_favourites!
def purge_status_reactions!
@account.status_reactions.in_batches do |status_reactions|
ids = status_reactions.pluck(:status_id)
StatusStat.where(status_id: ids).update_all('reactions_count = GREATEST(0, reactions_count - 1)')
Chewy.strategy.current.update(StatusesIndex, ids) if Chewy.enabled?
Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" })
status_reactions.delete_all
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddReactionCountToStatusStat < ActiveRecord::Migration[7.1]
def change
add_column :status_stats, :reactions_count, :bigint, default: 0, null: false
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@
t.bigint "favourites_count", default: 0, null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.bigint "reactions_count", default: 0, null: false
t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true
end

Expand Down
1 change: 1 addition & 0 deletions lib/mastodon/cli/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def recount_status_stats(status)
status_stat.replies_count = status.replies.where.not(visibility: :direct).count
status_stat.reblogs_count = status.reblogs.count
status_stat.favourites_count = status.favourites.count
status_stat.reactions_count = status.status_reactions.count

status_stat.save if status_stat.changed?
end
Expand Down

0 comments on commit 190bdc1

Please sign in to comment.