From dca26cc504b2651f1c8fc41cc3923ee3d620bebe Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 23 Feb 2023 20:32:27 +0900 Subject: [PATCH] Add emoji_reactions property to status api object --- app/models/status.rb | 24 ++++++++ app/models/status_stat.rb | 11 ++++ ...ji_reactions_grouped_by_name_serializer.rb | 31 ++++++++++ app/serializers/rest/status_serializer.rb | 6 +- ...416_add_emoji_reactions_to_status_stats.rb | 5 ++ db/schema.rb | 6 ++ lib/mastodon/cache_cli.rb | 61 +++++++++++++++++++ 7 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 app/serializers/rest/emoji_reactions_grouped_by_name_serializer.rb create mode 100644 db/migrate/20230223102416_add_emoji_reactions_to_status_stats.rb create mode 100644 lib/mastodon/cache_cli.rb diff --git a/app/models/status.rb b/app/models/status.rb index 96e47c6e6..d6acee868 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -65,6 +65,7 @@ class Status < ApplicationRecord belongs_to :quote, class_name: 'Status', inverse_of: :quoted, optional: true has_many :favourites, inverse_of: :status, dependent: :destroy + has_many :emoji_reactions, inverse_of: :status, dependent: :destroy has_many :bookmarks, inverse_of: :status, dependent: :destroy has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy has_many :reblogged_by_accounts, through: :reblogs, class_name: 'Account', source: :account @@ -323,6 +324,29 @@ class Status < ApplicationRecord update_status_stat!(key => [public_send(key) - 1, 0].max) end + def emoji_reactions_grouped_by_name(account = nil) + (Oj.load(status_stat&.emoji_reactions || '', mode: :strict) || []).tap do |emoji_reactions| + if account.present? + emoji_reactions.each do |emoji_reaction| + emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s) + emoji_reaction['count'] = emoji_reaction['account_ids'].size + emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s) + end + end + end + end + + def generate_emoji_reactions_grouped_by_name + records = emoji_reactions.group(:name).order(Arel.sql('MIN(created_at) ASC')).select('name, min(custom_emoji_id) as custom_emoji_id, count(*) as count, array_agg(account_id::text order by created_at) as account_ids') + Oj.dump(ActiveModelSerializers::SerializableResource.new(records, each_serializer: REST::EmojiReactionsGroupedByNameSerializer, scope: nil, scope_name: :current_user)) + end + + def refresh_emoji_reactions_grouped_by_name! + generate_emoji_reactions_grouped_by_name.tap do |emoji_reactions| + update_status_stat!(emoji_reactions: emoji_reactions) + end + end + def trendable? if attributes['trendable'].nil? account.trendable? diff --git a/app/models/status_stat.rb b/app/models/status_stat.rb index 47aa14477..86dd3980e 100644 --- a/app/models/status_stat.rb +++ b/app/models/status_stat.rb @@ -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 +# emoji_reactions :string # created_at :datetime not null # updated_at :datetime not null # @@ -27,4 +28,14 @@ class StatusStat < ApplicationRecord def favourites_count [attributes['favourites_count'], 0].max end + + def emoji_reactions + attributes['emoji_reactions'] || '' + end + + private + + def reset_parent_cache + Rails.cache.delete("statuses/#{status_id}") + end end diff --git a/app/serializers/rest/emoji_reactions_grouped_by_name_serializer.rb b/app/serializers/rest/emoji_reactions_grouped_by_name_serializer.rb new file mode 100644 index 000000000..ac8054179 --- /dev/null +++ b/app/serializers/rest/emoji_reactions_grouped_by_name_serializer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# name: string, +#count: number, +#account_ids: Array, +#me: boolean, +#url: string, +#domain: string + +class REST::EmojiReactionsGroupedByNameSerializer < ActiveModel::Serializer + attributes :name, :count + + attribute :me, if: :current_user? + attribute :url, if: :custom_emoji? + attribute :static_url, if: :custom_emoji? + attribute :domain, if: :custom_emoji? + attribute :account_ids, if: :has_account_ids? + + def current_user? + !current_user.nil? + end + + def custom_emoji? + object.respond_to?(:custom_emoji) + end + + def has_account_ids? + object.respond_to?(:account_ids) + end +end + \ No newline at end of file diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index ce49cb04f..504f8241b 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -6,7 +6,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 + :favourites_count, :emoji_reactions, :edited_at attribute :favourited, if: :current_user? attribute :reblogged, if: :current_user? @@ -103,6 +103,10 @@ class REST::StatusSerializer < ActiveModel::Serializer end end + def emoji_reactions + object.emoji_reactions_grouped_by_name(current_user&.account) + end + def reblogged if relationships relationships.reblogs_map[object.id] || false diff --git a/db/migrate/20230223102416_add_emoji_reactions_to_status_stats.rb b/db/migrate/20230223102416_add_emoji_reactions_to_status_stats.rb new file mode 100644 index 000000000..7f2ec68cc --- /dev/null +++ b/db/migrate/20230223102416_add_emoji_reactions_to_status_stats.rb @@ -0,0 +1,5 @@ +class AddEmojiReactionsToStatusStats < ActiveRecord::Migration[6.1] + def change + add_column :status_stats, :emoji_reactions, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 7d5e4811d..7df9f315e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -965,8 +965,14 @@ ActiveRecord::Schema[7.1].define(version: 2023_12_12_073317) do t.bigint "replies_count", default: 0, null: false t.bigint "reblogs_count", default: 0, null: false t.bigint "favourites_count", default: 0, null: false +<<<<<<< HEAD t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false +======= + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "emoji_reactions" +>>>>>>> 092f9916b0 (Add emoji_reactions property to status api object) t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true end diff --git a/lib/mastodon/cache_cli.rb b/lib/mastodon/cache_cli.rb new file mode 100644 index 000000000..71da96179 --- /dev/null +++ b/lib/mastodon/cache_cli.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require_relative '../../config/boot' +require_relative '../../config/environment' +require_relative 'cli_helper' + +module Mastodon + class CacheCLI < Thor + include CLIHelper + + def self.exit_on_failure? + true + end + + desc 'clear', 'Clear out the cache storage' + def clear + Rails.cache.clear + say('OK', :green) + end + + option :concurrency, type: :numeric, default: 5, aliases: [:c] + option :verbose, type: :boolean, aliases: [:v] + desc 'recount TYPE', 'Update hard-cached counters' + long_desc <<~LONG_DESC + Update hard-cached counters of TYPE by counting referenced + records from scratch. TYPE can be "accounts" or "statuses". + + It may take a very long time to finish, depending on the + size of the database. + LONG_DESC + def recount(type) + case type + when 'accounts' + processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account| + account_stat = account.account_stat + account_stat.following_count = account.active_relationships.count + account_stat.followers_count = account.passive_relationships.count + account_stat.statuses_count = account.statuses.where.not(visibility: :direct).count + + account_stat.save if account_stat.changed? + end + when 'statuses' + processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status| + status_stat = status.status_stat + 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.emoji_reactions = status.generate_emoji_reactions_grouped_by_name + + status_stat.save if status_stat.changed? + end + else + say("Unknown type: #{type}", :red) + exit(1) + end + + say + say("OK, recounted #{processed} records", :green) + end + end +end