From d0ba77047e539b7ae102296d096fcfd668a2ec92 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 1 Nov 2022 13:01:39 +0100 Subject: Change max. thumbnail dimensions to 640x360px (360p) (#19619) --- spec/models/media_attachment_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index cbd9a09c5..29fd313ae 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -157,9 +157,9 @@ RSpec.describe MediaAttachment, type: :model do expect(media.file.meta["original"]["width"]).to eq 600 expect(media.file.meta["original"]["height"]).to eq 400 expect(media.file.meta["original"]["aspect"]).to eq 1.5 - expect(media.file.meta["small"]["width"]).to eq 490 - expect(media.file.meta["small"]["height"]).to eq 327 - expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327 + expect(media.file.meta["small"]["width"]).to eq 588 + expect(media.file.meta["small"]["height"]).to eq 392 + expect(media.file.meta["small"]["aspect"]).to eq 1.5 end it 'gives the file a random name' do -- cgit From c68e6b52d9d4da5d34380587cb6faaabdfedfb35 Mon Sep 17 00:00:00 2001 From: pea-sys <49807271+pea-sys@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:06:52 +0900 Subject: png optimization(loss less) (#19630) --- app/javascript/icons/android-chrome-144x144.png | Bin 6644 -> 5810 bytes app/javascript/icons/android-chrome-192x192.png | Bin 10138 -> 8741 bytes app/javascript/icons/android-chrome-256x256.png | Bin 14194 -> 11993 bytes app/javascript/icons/android-chrome-36x36.png | Bin 1050 -> 950 bytes app/javascript/icons/android-chrome-384x384.png | Bin 25667 -> 21112 bytes app/javascript/icons/android-chrome-48x48.png | Bin 1468 -> 1384 bytes app/javascript/icons/android-chrome-512x512.png | Bin 39853 -> 31858 bytes app/javascript/icons/android-chrome-72x72.png | Bin 2501 -> 2262 bytes app/javascript/icons/android-chrome-96x96.png | Bin 3713 -> 3306 bytes .../icons/apple-touch-icon-1024x1024.png | Bin 104580 -> 77950 bytes app/javascript/icons/apple-touch-icon-114x114.png | Bin 4641 -> 4123 bytes app/javascript/icons/apple-touch-icon-120x120.png | Bin 4914 -> 4366 bytes app/javascript/icons/apple-touch-icon-144x144.png | Bin 6644 -> 5810 bytes app/javascript/icons/apple-touch-icon-152x152.png | Bin 7002 -> 6177 bytes app/javascript/icons/apple-touch-icon-167x167.png | Bin 8096 -> 7041 bytes app/javascript/icons/apple-touch-icon-180x180.png | Bin 8947 -> 7709 bytes app/javascript/icons/apple-touch-icon-57x57.png | Bin 1857 -> 1673 bytes app/javascript/icons/apple-touch-icon-60x60.png | Bin 1952 -> 1761 bytes app/javascript/icons/apple-touch-icon-72x72.png | Bin 2501 -> 2262 bytes app/javascript/icons/apple-touch-icon-76x76.png | Bin 2617 -> 2360 bytes app/javascript/icons/favicon-16x16.png | Bin 639 -> 588 bytes app/javascript/icons/favicon-32x32.png | Bin 1250 -> 1114 bytes app/javascript/icons/favicon-48x48.png | Bin 1899 -> 1680 bytes app/javascript/images/mailer/icon_cached.png | Bin 2014 -> 1133 bytes app/javascript/images/mailer/icon_done.png | Bin 817 -> 477 bytes app/javascript/images/mailer/icon_email.png | Bin 2120 -> 1138 bytes .../images/mailer/icon_file_download.png | Bin 813 -> 346 bytes app/javascript/images/mailer/icon_flag.png | Bin 693 -> 298 bytes app/javascript/images/mailer/icon_grade.png | Bin 3243 -> 1959 bytes app/javascript/images/mailer/icon_lock_open.png | Bin 2498 -> 1642 bytes app/javascript/images/mailer/icon_person_add.png | Bin 2356 -> 1440 bytes app/javascript/images/mailer/icon_reply.png | Bin 2146 -> 1235 bytes app/javascript/images/mailer/logo.png | Bin 1673 -> 1150 bytes app/javascript/images/mailer/wordmark.png | Bin 8991 -> 6753 bytes app/javascript/images/preview.png | Bin 514463 -> 340408 bytes app/javascript/images/reticle.png | Bin 2199 -> 1439 bytes app/javascript/images/void.png | Bin 174 -> 80 bytes lib/assets/wordmark.dark.png | Bin 8991 -> 6753 bytes lib/assets/wordmark.light.png | Bin 8625 -> 6613 bytes public/avatars/original/missing.png | Bin 3292 -> 2897 bytes public/badge.png | Bin 4058 -> 2998 bytes public/headers/original/missing.png | Bin 81 -> 68 bytes public/oops.png | Bin 20552 -> 16948 bytes public/web-push-icon_expand.png | Bin 1380 -> 1077 bytes public/web-push-icon_favourite.png | Bin 1046 -> 721 bytes public/web-push-icon_reblog.png | Bin 851 -> 590 bytes spec/fabricators/assets/utah_teapot.png | Bin 248232 -> 195600 bytes spec/fixtures/files/emojo.png | Bin 29814 -> 29283 bytes 48 files changed, 0 insertions(+), 0 deletions(-) (limited to 'spec') diff --git a/app/javascript/icons/android-chrome-144x144.png b/app/javascript/icons/android-chrome-144x144.png index d282a6d3d..698fb4a26 100644 Binary files a/app/javascript/icons/android-chrome-144x144.png and b/app/javascript/icons/android-chrome-144x144.png differ diff --git a/app/javascript/icons/android-chrome-192x192.png b/app/javascript/icons/android-chrome-192x192.png index d3f9959c5..2b6b63264 100644 Binary files a/app/javascript/icons/android-chrome-192x192.png and b/app/javascript/icons/android-chrome-192x192.png differ diff --git a/app/javascript/icons/android-chrome-256x256.png b/app/javascript/icons/android-chrome-256x256.png index 98ce6ffbb..51e3849a2 100644 Binary files a/app/javascript/icons/android-chrome-256x256.png and b/app/javascript/icons/android-chrome-256x256.png differ diff --git a/app/javascript/icons/android-chrome-36x36.png b/app/javascript/icons/android-chrome-36x36.png index f8ffeeb76..925f69c4f 100644 Binary files a/app/javascript/icons/android-chrome-36x36.png and b/app/javascript/icons/android-chrome-36x36.png differ diff --git a/app/javascript/icons/android-chrome-384x384.png b/app/javascript/icons/android-chrome-384x384.png index 60b375354..9d256a83c 100644 Binary files a/app/javascript/icons/android-chrome-384x384.png and b/app/javascript/icons/android-chrome-384x384.png differ diff --git a/app/javascript/icons/android-chrome-48x48.png b/app/javascript/icons/android-chrome-48x48.png index ce49448d4..bcfe7475d 100644 Binary files a/app/javascript/icons/android-chrome-48x48.png and b/app/javascript/icons/android-chrome-48x48.png differ diff --git a/app/javascript/icons/android-chrome-512x512.png b/app/javascript/icons/android-chrome-512x512.png index bccaada29..bffacfb69 100644 Binary files a/app/javascript/icons/android-chrome-512x512.png and b/app/javascript/icons/android-chrome-512x512.png differ diff --git a/app/javascript/icons/android-chrome-72x72.png b/app/javascript/icons/android-chrome-72x72.png index ce57ab746..16679d573 100644 Binary files a/app/javascript/icons/android-chrome-72x72.png and b/app/javascript/icons/android-chrome-72x72.png differ diff --git a/app/javascript/icons/android-chrome-96x96.png b/app/javascript/icons/android-chrome-96x96.png index cd8f09ed4..9ade87cf3 100644 Binary files a/app/javascript/icons/android-chrome-96x96.png and b/app/javascript/icons/android-chrome-96x96.png differ diff --git a/app/javascript/icons/apple-touch-icon-1024x1024.png b/app/javascript/icons/apple-touch-icon-1024x1024.png index b1f18f3e3..8ec371eb2 100644 Binary files a/app/javascript/icons/apple-touch-icon-1024x1024.png and b/app/javascript/icons/apple-touch-icon-1024x1024.png differ diff --git a/app/javascript/icons/apple-touch-icon-114x114.png b/app/javascript/icons/apple-touch-icon-114x114.png index e13aaa87d..e1563f51e 100644 Binary files a/app/javascript/icons/apple-touch-icon-114x114.png and b/app/javascript/icons/apple-touch-icon-114x114.png differ diff --git a/app/javascript/icons/apple-touch-icon-120x120.png b/app/javascript/icons/apple-touch-icon-120x120.png index aa63012b5..e9a5f5b0e 100644 Binary files a/app/javascript/icons/apple-touch-icon-120x120.png and b/app/javascript/icons/apple-touch-icon-120x120.png differ diff --git a/app/javascript/icons/apple-touch-icon-144x144.png b/app/javascript/icons/apple-touch-icon-144x144.png index d282a6d3d..698fb4a26 100644 Binary files a/app/javascript/icons/apple-touch-icon-144x144.png and b/app/javascript/icons/apple-touch-icon-144x144.png differ diff --git a/app/javascript/icons/apple-touch-icon-152x152.png b/app/javascript/icons/apple-touch-icon-152x152.png index 0dec7d63e..0cc93cc28 100644 Binary files a/app/javascript/icons/apple-touch-icon-152x152.png and b/app/javascript/icons/apple-touch-icon-152x152.png differ diff --git a/app/javascript/icons/apple-touch-icon-167x167.png b/app/javascript/icons/apple-touch-icon-167x167.png index a622e1215..9bbbf5312 100644 Binary files a/app/javascript/icons/apple-touch-icon-167x167.png and b/app/javascript/icons/apple-touch-icon-167x167.png differ diff --git a/app/javascript/icons/apple-touch-icon-180x180.png b/app/javascript/icons/apple-touch-icon-180x180.png index 864046b56..329b803b9 100644 Binary files a/app/javascript/icons/apple-touch-icon-180x180.png and b/app/javascript/icons/apple-touch-icon-180x180.png differ diff --git a/app/javascript/icons/apple-touch-icon-57x57.png b/app/javascript/icons/apple-touch-icon-57x57.png index 116918ce2..e00e142c6 100644 Binary files a/app/javascript/icons/apple-touch-icon-57x57.png and b/app/javascript/icons/apple-touch-icon-57x57.png differ diff --git a/app/javascript/icons/apple-touch-icon-60x60.png b/app/javascript/icons/apple-touch-icon-60x60.png index 0eda96ed6..011285b56 100644 Binary files a/app/javascript/icons/apple-touch-icon-60x60.png and b/app/javascript/icons/apple-touch-icon-60x60.png differ diff --git a/app/javascript/icons/apple-touch-icon-72x72.png b/app/javascript/icons/apple-touch-icon-72x72.png index ce57ab746..16679d573 100644 Binary files a/app/javascript/icons/apple-touch-icon-72x72.png and b/app/javascript/icons/apple-touch-icon-72x72.png differ diff --git a/app/javascript/icons/apple-touch-icon-76x76.png b/app/javascript/icons/apple-touch-icon-76x76.png index 50e162891..83c874887 100644 Binary files a/app/javascript/icons/apple-touch-icon-76x76.png and b/app/javascript/icons/apple-touch-icon-76x76.png differ diff --git a/app/javascript/icons/favicon-16x16.png b/app/javascript/icons/favicon-16x16.png index 33ef3bb8c..eed8e0035 100644 Binary files a/app/javascript/icons/favicon-16x16.png and b/app/javascript/icons/favicon-16x16.png differ diff --git a/app/javascript/icons/favicon-32x32.png b/app/javascript/icons/favicon-32x32.png index 7b9a37403..9165746bc 100644 Binary files a/app/javascript/icons/favicon-32x32.png and b/app/javascript/icons/favicon-32x32.png differ diff --git a/app/javascript/icons/favicon-48x48.png b/app/javascript/icons/favicon-48x48.png index 5b35eb233..259676c0a 100644 Binary files a/app/javascript/icons/favicon-48x48.png and b/app/javascript/icons/favicon-48x48.png differ diff --git a/app/javascript/images/mailer/icon_cached.png b/app/javascript/images/mailer/icon_cached.png index e94abb7ba..73bf9d198 100644 Binary files a/app/javascript/images/mailer/icon_cached.png and b/app/javascript/images/mailer/icon_cached.png differ diff --git a/app/javascript/images/mailer/icon_done.png b/app/javascript/images/mailer/icon_done.png index 472364de4..bc669b7b6 100644 Binary files a/app/javascript/images/mailer/icon_done.png and b/app/javascript/images/mailer/icon_done.png differ diff --git a/app/javascript/images/mailer/icon_email.png b/app/javascript/images/mailer/icon_email.png index d8dfe161e..becbca2f9 100644 Binary files a/app/javascript/images/mailer/icon_email.png and b/app/javascript/images/mailer/icon_email.png differ diff --git a/app/javascript/images/mailer/icon_file_download.png b/app/javascript/images/mailer/icon_file_download.png index 24e424d3b..26ffddbdd 100644 Binary files a/app/javascript/images/mailer/icon_file_download.png and b/app/javascript/images/mailer/icon_file_download.png differ diff --git a/app/javascript/images/mailer/icon_flag.png b/app/javascript/images/mailer/icon_flag.png index 0f14f45a8..7e078fede 100644 Binary files a/app/javascript/images/mailer/icon_flag.png and b/app/javascript/images/mailer/icon_flag.png differ diff --git a/app/javascript/images/mailer/icon_grade.png b/app/javascript/images/mailer/icon_grade.png index 7f371ab11..94615fc4a 100644 Binary files a/app/javascript/images/mailer/icon_grade.png and b/app/javascript/images/mailer/icon_grade.png differ diff --git a/app/javascript/images/mailer/icon_lock_open.png b/app/javascript/images/mailer/icon_lock_open.png index d11c36475..5c1c36f95 100644 Binary files a/app/javascript/images/mailer/icon_lock_open.png and b/app/javascript/images/mailer/icon_lock_open.png differ diff --git a/app/javascript/images/mailer/icon_person_add.png b/app/javascript/images/mailer/icon_person_add.png index 696eb7495..9fe966391 100644 Binary files a/app/javascript/images/mailer/icon_person_add.png and b/app/javascript/images/mailer/icon_person_add.png differ diff --git a/app/javascript/images/mailer/icon_reply.png b/app/javascript/images/mailer/icon_reply.png index 51d92f845..83ddc07e8 100644 Binary files a/app/javascript/images/mailer/icon_reply.png and b/app/javascript/images/mailer/icon_reply.png differ diff --git a/app/javascript/images/mailer/logo.png b/app/javascript/images/mailer/logo.png index 77d0ef849..784be9539 100644 Binary files a/app/javascript/images/mailer/logo.png and b/app/javascript/images/mailer/logo.png differ diff --git a/app/javascript/images/mailer/wordmark.png b/app/javascript/images/mailer/wordmark.png index defe50178..6772b3318 100644 Binary files a/app/javascript/images/mailer/wordmark.png and b/app/javascript/images/mailer/wordmark.png differ diff --git a/app/javascript/images/preview.png b/app/javascript/images/preview.png index 369bed4b6..3d3a17b23 100644 Binary files a/app/javascript/images/preview.png and b/app/javascript/images/preview.png differ diff --git a/app/javascript/images/reticle.png b/app/javascript/images/reticle.png index 41a5d1c3a..a724ac0bc 100644 Binary files a/app/javascript/images/reticle.png and b/app/javascript/images/reticle.png differ diff --git a/app/javascript/images/void.png b/app/javascript/images/void.png index d73066688..c2b803c13 100644 Binary files a/app/javascript/images/void.png and b/app/javascript/images/void.png differ diff --git a/lib/assets/wordmark.dark.png b/lib/assets/wordmark.dark.png index defe50178..6772b3318 100644 Binary files a/lib/assets/wordmark.dark.png and b/lib/assets/wordmark.dark.png differ diff --git a/lib/assets/wordmark.light.png b/lib/assets/wordmark.light.png index d4485c0fb..6d95088a4 100644 Binary files a/lib/assets/wordmark.light.png and b/lib/assets/wordmark.light.png differ diff --git a/public/avatars/original/missing.png b/public/avatars/original/missing.png index 34c8e45e6..781370782 100644 Binary files a/public/avatars/original/missing.png and b/public/avatars/original/missing.png differ diff --git a/public/badge.png b/public/badge.png index bd618c729..7f9128f49 100644 Binary files a/public/badge.png and b/public/badge.png differ diff --git a/public/headers/original/missing.png b/public/headers/original/missing.png index 26b59e75a..240ca4f8d 100644 Binary files a/public/headers/original/missing.png and b/public/headers/original/missing.png differ diff --git a/public/oops.png b/public/oops.png index 1ac779f25..a98704400 100644 Binary files a/public/oops.png and b/public/oops.png differ diff --git a/public/web-push-icon_expand.png b/public/web-push-icon_expand.png index 5c115c769..9a948c09a 100644 Binary files a/public/web-push-icon_expand.png and b/public/web-push-icon_expand.png differ diff --git a/public/web-push-icon_favourite.png b/public/web-push-icon_favourite.png index 321f775ee..2cd9e827b 100644 Binary files a/public/web-push-icon_favourite.png and b/public/web-push-icon_favourite.png differ diff --git a/public/web-push-icon_reblog.png b/public/web-push-icon_reblog.png index 0f555ed09..4a7104bf3 100644 Binary files a/public/web-push-icon_reblog.png and b/public/web-push-icon_reblog.png differ diff --git a/spec/fabricators/assets/utah_teapot.png b/spec/fabricators/assets/utah_teapot.png index 6708361e5..ccf202de4 100644 Binary files a/spec/fabricators/assets/utah_teapot.png and b/spec/fabricators/assets/utah_teapot.png differ diff --git a/spec/fixtures/files/emojo.png b/spec/fixtures/files/emojo.png index cb5993499..6ef0a5fbc 100644 Binary files a/spec/fixtures/files/emojo.png and b/spec/fixtures/files/emojo.png differ -- cgit From 1dca08b76f25d15365127ded37202d783a50e298 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 3 Nov 2022 16:06:42 +0100 Subject: Fix admin action logs page (#19649) * Add tests * Fix crash when trying to display orphaned action logs * Add migration for older admin action logs --- app/helpers/admin/action_logs_helper.rb | 20 ++- .../20221101190723_backfill_admin_action_logs.rb | 150 +++++++++++++++++++++ db/schema.rb | 2 +- lib/tasks/tests.rake | 51 +++++-- .../admin/action_logs_controller_spec.rb | 13 ++ 5 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 db/post_migrate/20221101190723_backfill_admin_action_logs.rb (limited to 'spec') diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index fd1977ac5..215ecea0d 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -4,15 +4,19 @@ module Admin::ActionLogsHelper def log_target(log) case log.target_type when 'Account' - link_to log.human_identifier, admin_account_path(log.target_id) + link_to (log.human_identifier.presence || I18n.t('admin.action_logs.deleted_account')), admin_account_path(log.target_id) when 'User' - link_to log.human_identifier, admin_account_path(log.route_param) + if log.route_param.present? + link_to log.human_identifier, admin_account_path(log.route_param) + else + I18n.t('admin.action_logs.deleted_account') + end when 'UserRole' link_to log.human_identifier, admin_roles_path(log.target_id) when 'Report' - link_to "##{log.human_identifier}", admin_report_path(log.target_id) + link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id) when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain' - link_to log.human_identifier, "https://#{log.human_identifier}" + link_to log.human_identifier, "https://#{log.human_identifier.presence}" when 'Status' link_to log.human_identifier, log.permalink when 'AccountWarning' @@ -22,9 +26,13 @@ module Admin::ActionLogsHelper when 'IpBlock', 'Instance', 'CustomEmoji' log.human_identifier when 'CanonicalEmailBlock' - content_tag(:samp, log.human_identifier[0...7], title: log.human_identifier) + content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier) when 'Appeal' - link_to log.human_identifier, disputes_strike_path(log.route_param) + if log.route_param.present? + link_to log.human_identifier, disputes_strike_path(log.route_param.presence) + else + I18n.t('admin.action_logs.deleted_account') + end end end end diff --git a/db/post_migrate/20221101190723_backfill_admin_action_logs.rb b/db/post_migrate/20221101190723_backfill_admin_action_logs.rb new file mode 100644 index 000000000..9a64d1715 --- /dev/null +++ b/db/post_migrate/20221101190723_backfill_admin_action_logs.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +class BackfillAdminActionLogs < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + class Account < ApplicationRecord + # Dummy class, to make migration possible across version changes + has_one :user, inverse_of: :account + + def local? + domain.nil? + end + + def acct + local? ? username : "#{username}@#{domain}" + end + end + + class User < ApplicationRecord + # Dummy class, to make migration possible across version changes + belongs_to :account + end + + class Status < ApplicationRecord + include RoutingHelper + + # Dummy class, to make migration possible across version changes + belongs_to :account + + def local? + attributes['local'] || attributes['uri'].nil? + end + + def uri + local? ? activity_account_status_url(account, self) : attributes['uri'] + end + end + + class DomainBlock < ApplicationRecord; end + class DomainAllow < ApplicationRecord; end + class EmailDomainBlock < ApplicationRecord; end + class UnavailableDomain < ApplicationRecord; end + + class AccountWarning < ApplicationRecord + # Dummy class, to make migration possible across version changes + belongs_to :account + end + + class Announcement < ApplicationRecord; end + class IpBlock < ApplicationRecord; end + class CustomEmoji < ApplicationRecord; end + class CanonicalEmailBlock < ApplicationRecord; end + + class Appeal < ApplicationRecord + # Dummy class, to make migration possible across version changes + belongs_to :account + end + + class AdminActionLog < ApplicationRecord + # Dummy class, to make migration possible across version changes + + # Cannot use usual polymorphic support because of namespacing issues + belongs_to :status, foreign_key: :target_id + belongs_to :account, foreign_key: :target_id + belongs_to :user, foreign_key: :user_id + belongs_to :domain_block, foreign_key: :target_id + belongs_to :domain_allow, foreign_key: :target_id + belongs_to :email_domain_block, foreign_key: :target_id + belongs_to :unavailable_domain, foreign_key: :target_id + belongs_to :account_warning, foreign_key: :target_id + belongs_to :announcement, foreign_key: :target_id + belongs_to :ip_block, foreign_key: :target_id + belongs_to :custom_emoji, foreign_key: :target_id + belongs_to :canonical_email_block, foreign_key: :target_id + belongs_to :appeal, foreign_key: :target_id + end + + def up + safety_assured do + AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log| + next if log.account.nil? + log.update(human_identifier: log.account.acct) + end + + AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log| + next if log.user.nil? + log.update(human_identifier: log.user.account.acct, route_param: log.user.account_id) + end + + Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text') + + AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log| + next if log.domain_block.nil? + log.update(human_identifier: log.domain_block.domain) + end + + AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log| + next if log.domain_allow.nil? + log.update(human_identifier: log.domain_allow.domain) + end + + AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log| + next if log.email_domain_block.nil? + log.update(human_identifier: log.email_domain_block.domain) + end + + AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log| + next if log.unavailable_domain.nil? + log.update(human_identifier: log.unavailable_domain.domain) + end + + AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log| + next if log.status.nil? + log.update(human_identifier: log.status.account.acct, permalink: log.status.uri) + end + + AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log| + next if log.account_warning.nil? + log.update(human_identifier: log.account_warning.account.acct) + end + + AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log| + next if log.announcement.nil? + log.update(human_identifier: log.announcement.text) + end + + AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log| + next if log.ip_block.nil? + log.update(human_identifier: "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}") + end + + AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log| + next if log.custom_emoji.nil? + log.update(human_identifier: log.custom_emoji.shortcode) + end + + AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log| + next if log.canonical_email_block.nil? + log.update(human_identifier: log.canonical_email_block.canonical_email_hash) + end + + AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log| + next if log.appeal.nil? + log.update(human_identifier: log.appeal.account.acct, route_param: log.appeal.account_warning_id) + end + end + end + + def down; end +end diff --git a/db/schema.rb b/db/schema.rb index d7e40b133..12ec37c11 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_10_25_171544) do +ActiveRecord::Schema.define(version: 2022_11_01_190723) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake index 65bff6a8e..96d2f7112 100644 --- a/lib/tasks/tests.rake +++ b/lib/tasks/tests.rake @@ -53,6 +53,41 @@ namespace :tests do VALUES (1, 2, 'test', '{ "home", "public" }', true, true, now(), now()), (2, 2, 'take', '{ "home" }', false, false, now(), now()); + + -- Orphaned admin action logs + + INSERT INTO "admin_action_logs" + (account_id, action, target_type, target_id, created_at, updated_at) + VALUES + (1, 'destroy', 'Account', 1312, now(), now()), + (1, 'destroy', 'User', 1312, now(), now()), + (1, 'destroy', 'Report', 1312, now(), now()), + (1, 'destroy', 'DomainBlock', 1312, now(), now()), + (1, 'destroy', 'EmailDomainBlock', 1312, now(), now()), + (1, 'destroy', 'Status', 1312, now(), now()), + (1, 'destroy', 'CustomEmoji', 1312, now(), now()); + + -- Admin action logs with linked objects + + INSERT INTO "domain_blocks" + (id, domain, created_at, updated_at) + VALUES + (1, 'example.org', now(), now()); + + INSERT INTO "email_domain_blocks" + (id, domain, created_at, updated_at) + VALUES + (1, 'example.org', now(), now()); + + INSERT INTO "admin_action_logs" + (account_id, action, target_type, target_id, created_at, updated_at) + VALUES + (1, 'destroy', 'Account', 1, now(), now()), + (1, 'destroy', 'User', 1, now(), now()), + (1, 'destroy', 'DomainBlock', 1312, now(), now()), + (1, 'destroy', 'EmailDomainBlock', 1312, now(), now()), + (1, 'destroy', 'Status', 1, now(), now()), + (1, 'destroy', 'CustomEmoji', 3, now(), now()); SQL end @@ -207,18 +242,18 @@ namespace :tests do -- custom emoji INSERT INTO "custom_emojis" - (shortcode, created_at, updated_at) + (id, shortcode, created_at, updated_at) VALUES - ('test', now(), now()), - ('Test', now(), now()), - ('blobcat', now(), now()); + (1, 'test', now(), now()), + (2, 'Test', now(), now()), + (3, 'blobcat', now(), now()); INSERT INTO "custom_emojis" - (shortcode, domain, uri, created_at, updated_at) + (id, shortcode, domain, uri, created_at, updated_at) VALUES - ('blobcat', 'remote.org', 'https://remote.org/emoji/blobcat', now(), now()), - ('blobcat', 'Remote.org', 'https://remote.org/emoji/blobcat', now(), now()), - ('Blobcat', 'remote.org', 'https://remote.org/emoji/Blobcat', now(), now()); + (4, 'blobcat', 'remote.org', 'https://remote.org/emoji/blobcat', now(), now()), + (5, 'blobcat', 'Remote.org', 'https://remote.org/emoji/blobcat', now(), now()), + (6, 'Blobcat', 'remote.org', 'https://remote.org/emoji/Blobcat', now(), now()); -- favourites diff --git a/spec/controllers/admin/action_logs_controller_spec.rb b/spec/controllers/admin/action_logs_controller_spec.rb index c1957258f..7cd8cdf46 100644 --- a/spec/controllers/admin/action_logs_controller_spec.rb +++ b/spec/controllers/admin/action_logs_controller_spec.rb @@ -3,6 +3,19 @@ require 'rails_helper' describe Admin::ActionLogsController, type: :controller do + render_views + + # Action logs typically cause issues when their targets are not in the database + let!(:account) { Fabricate(:account) } + + let!(:orphaned_logs) do + %w( + Account User UserRole Report DomainBlock DomainAllow + EmailDomainBlock UnavailableDomain Status AccountWarning + Announcement IpBlock Instance CustomEmoji CanonicalEmailBlock Appeal + ).map { |type| Admin::ActionLog.new(account: account, action: 'destroy', target_type: type, target_id: 1312).save! } + end + describe 'GET #index' do it 'returns 200' do sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')) -- cgit From 9387beb3b381e3f164eb538f178d9b543b7fcf40 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 3 Nov 2022 23:12:08 +0100 Subject: Change flaky AccountSearchService test (#19650) --- spec/services/account_search_service_spec.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'spec') diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 5b7182586..81cbc175e 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -45,7 +45,6 @@ describe AccountSearchService, type: :service do results = subject.call('e@example.com', nil, limit: 2) - expect(results.size).to eq 2 expect(results).to eq([exact, remote]).or eq([exact, remote_too]) end end -- cgit From 4fb0aae636316e79b3c13c4000fda7765fa9474f Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Nov 2022 13:19:12 +0100 Subject: Change mentions of blocked users to not be processed (#19725) Fixes #19698 --- app/services/process_mentions_service.rb | 10 +++ spec/services/process_mentions_service_spec.rb | 100 ++++++++++++++++--------- 2 files changed, 74 insertions(+), 36 deletions(-) (limited to 'spec') diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index c9c158af1..b117db8c2 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -66,6 +66,16 @@ class ProcessMentionsService < BaseService end def assign_mentions! + # Make sure we never mention blocked accounts + unless @current_mentions.empty? + mentioned_domains = @current_mentions.map { |m| m.account.domain }.compact.uniq + blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains)) + mentioned_account_ids = @current_mentions.map(&:account_id) + blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) + + @current_mentions.select! { |mention| !(blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain)) } + end + @current_mentions.each do |mention| mention.save if mention.new_record? end diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index 89b265e9a..5b9d17a4c 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -1,43 +1,85 @@ require 'rails_helper' RSpec.describe ProcessMentionsService, type: :service do - let(:account) { Fabricate(:account, username: 'alice') } - let(:visibility) { :public } - let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) } + let(:account) { Fabricate(:account, username: 'alice') } subject { ProcessMentionsService.new } - context 'ActivityPub' do - context do - let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + context 'when mentions contain blocked accounts' do + let(:non_blocked_account) { Fabricate(:account) } + let(:individually_blocked_account) { Fabricate(:account) } + let(:domain_blocked_account) { Fabricate(:account, domain: 'evil.com') } + let(:status) { Fabricate(:status, account: account, text: "Hello @#{non_blocked_account.acct} @#{individually_blocked_account.acct} @#{domain_blocked_account.acct}", visibility: :public) } - before do - subject.call(status) - end + before do + account.block!(individually_blocked_account) + account.domain_blocks.create!(domain: domain_blocked_account.domain) - it 'creates a mention' do - expect(remote_user.mentions.where(status: status).count).to eq 1 - end + subject.call(status) + end + + it 'creates a mention to the non-blocked account' do + expect(non_blocked_account.mentions.where(status: status).count).to eq 1 end - context 'with an IDN domain' do - let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } - let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } + it 'does not create a mention to the individually blocked account' do + expect(individually_blocked_account.mentions.where(status: status).count).to eq 0 + end - before do - subject.call(status) + it 'does not create a mention to the domain-blocked account' do + expect(domain_blocked_account.mentions.where(status: status).count).to eq 0 + end + end + + context 'resolving a mention to a remote account' do + let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: :public) } + + context 'ActivityPub' do + context do + let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') } + + before do + subject.call(status) + end + + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end end - it 'creates a mention' do - expect(remote_user.mentions.where(status: status).count).to eq 1 + context 'with an IDN domain' do + let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') } + let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") } + + before do + subject.call(status) + end + + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end + end + + context 'with an IDN TLD' do + let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') } + let!(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") } + + before do + subject.call(status) + end + + it 'creates a mention' do + expect(remote_user.mentions.where(status: status).count).to eq 1 + end end end - context 'with an IDN TLD' do - let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') } - let!(:status) { Fabricate(:status, account: account, text: "Hello @foo@հայ.հայ") } + context 'Temporarily-unreachable ActivityPub user' do + let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) } before do + stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) + stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500) subject.call(status) end @@ -46,18 +88,4 @@ RSpec.describe ProcessMentionsService, type: :service do end end end - - context 'Temporarily-unreachable ActivityPub user' do - let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) } - - before do - stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404) - stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500) - subject.call(status) - end - - it 'creates a mention' do - expect(remote_user.mentions.where(status: status).count).to eq 1 - end - end end -- cgit From 5f9e47be34fcf42ff7fcd5668c7555d4a38e289a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 4 Nov 2022 13:21:06 +0100 Subject: Add caching for payload serialization during fan-out (#19642) --- app/lib/inline_renderer.rb | 11 +++++++ app/lib/status_cache_hydrator.rb | 47 +++++++++++++++++++++++++++ app/services/fan_out_on_write_service.rb | 11 ++++++- app/workers/push_update_worker.rb | 13 ++++---- spec/lib/status_cache_hydrator_spec.rb | 54 ++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 app/lib/status_cache_hydrator.rb create mode 100644 spec/lib/status_cache_hydrator_spec.rb (limited to 'spec') diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb index b70814748..4bb240b48 100644 --- a/app/lib/inline_renderer.rb +++ b/app/lib/inline_renderer.rb @@ -11,6 +11,7 @@ class InlineRenderer case @template when :status serializer = REST::StatusSerializer + preload_associations_for_status when :notification serializer = REST::NotificationSerializer when :conversation @@ -35,6 +36,16 @@ class InlineRenderer private + def preload_associations_for_status + ActiveRecord::Associations::Preloader.new.preload(@object, { + active_mentions: :account, + + reblog: { + active_mentions: :account, + }, + }) + end + def current_user @current_account&.user end diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb new file mode 100644 index 000000000..01e92b385 --- /dev/null +++ b/app/lib/status_cache_hydrator.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class StatusCacheHydrator + def initialize(status) + @status = status + end + + def hydrate(account_id) + # The cache of the serialized hash is generated by the fan-out-on-write service + payload = Rails.cache.fetch("fan-out/#{@status.id}") { InlineRenderer.render(@status, nil, :status) } + + # If we're delivering to the author who disabled the display of the application used to create the + # status, we need to hydrate the application, since it was not rendered for the basic payload + payload[:application] = ActiveModelSerializers::SerializableResource.new(@status.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:application].nil? && @status.account_id == account_id + + # We take advantage of the fact that some relationships can only occur with an original status, not + # the reblog that wraps it, so we can assume that some values are always false + if payload[:reblog] + payload[:favourited] = false + payload[:reblogged] = false + payload[:muted] = false + payload[:bookmarked] = false + payload[:pinned] = false + payload[:filtered] = CustomFilter.apply_cached_filters(CustomFilter.cached_filters_for(@status.reblog_of_id), @status.reblog).map { |filter| ActiveModelSerializers::SerializableResource.new(filter, serializer: REST::FilterResultSerializer).as_json } + + # If the reblogged status is being delivered to the author who disabled the display of the application + # used to create the status, we need to hydrate it here too + payload[:reblog][:application] = ActiveModelSerializers::SerializableResource.new(@status.reblog.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id + + payload[:reblog][:favourited] = Favourite.where(account_id: account_id, status_id: @status.reblog_of_id).exists? + payload[:reblog][:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.reblog_of_id).exists? + payload[:reblog][:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.reblog.conversation_id).exists? + payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists? + payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? + payload[:reblog][:filtered] = payload[:filtered] + else + payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists? + payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists? + payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists? + payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists? + payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? + payload[:filtered] = CustomFilter.apply_cached_filters(CustomFilter.cached_filters_for(@status.id), @status).map { |filter| ActiveModelSerializers::SerializableResource.new(filter, serializer: REST::FilterResultSerializer).as_json } + end + + payload + end +end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index ce20a146e..2554756a5 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -14,6 +14,7 @@ class FanOutOnWriteService < BaseService @options = options check_race_condition! + warm_payload_cache! fan_out_to_local_recipients! fan_out_to_public_recipients! if broadcastable? @@ -135,13 +136,21 @@ class FanOutOnWriteService < BaseService AccountConversation.add_status(@account, @status) unless update? end + def warm_payload_cache! + Rails.cache.write("fan-out/#{@status.id}", rendered_status) + end + def anonymous_payload @anonymous_payload ||= Oj.dump( event: update? ? :'status.update' : :update, - payload: InlineRenderer.render(@status, nil, :status) + payload: rendered_status ) end + def rendered_status + @rendered_status ||= InlineRenderer.render(@status, nil, :status) + end + def update? @options[:update] end diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb index 9f44c32b3..72c781749 100644 --- a/app/workers/push_update_worker.rb +++ b/app/workers/push_update_worker.rb @@ -5,11 +5,12 @@ class PushUpdateWorker include Redisable def perform(account_id, status_id, timeline_id = nil, options = {}) - @account = Account.find(account_id) - @status = Status.includes(active_mentions: :account, reblog: { active_mentions: :account }).find(status_id) - @timeline_id = timeline_id || "timeline:#{account.id}" + @status = Status.find(status_id) + @account_id = account_id + @timeline_id = timeline_id || "timeline:#{account_id}" @options = options.symbolize_keys + render_payload! publish! rescue ActiveRecord::RecordNotFound true @@ -17,14 +18,14 @@ class PushUpdateWorker private - def payload - InlineRenderer.render(@status, @account, :status) + def render_payload! + @payload = StatusCacheHydrator.new(@status).hydrate(@account_id) end def message Oj.dump( event: update? ? :'status.update' : :update, - payload: payload, + payload: @payload, queued_at: (Time.now.to_f * 1000.0).to_i ) end diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb new file mode 100644 index 000000000..ad9940a85 --- /dev/null +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe StatusCacheHydrator do + let(:status) { Fabricate(:status) } + let(:account) { Fabricate(:account) } + + describe '#hydrate' do + subject { described_class.new(status).hydrate(account.id) } + + let(:compare_to_hash) { InlineRenderer.render(status, account, :status) } + + context 'when cache is warm' do + before do + Rails.cache.write("fan-out/#{status.id}", InlineRenderer.render(status, nil, :status)) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'when cache is cold' do + before do + Rails.cache.delete("fan-out/#{status.id}") + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'when account has favourited status' do + before do + FavouriteService.new.call(account, status) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'when account has reblogged status' do + before do + ReblogService.new.call(account, status) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + end +end -- cgit From 03b991de6c5874f3e7d1b6f6ff70b853b8daf639 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Nov 2022 19:33:16 +0100 Subject: Fix various issues with store hydration (#19746) - Improve tests - Fix possible crash when application of a reblogged post isn't set - Fix discrepancies around favourited and reblogged attributes - Fix discrepancies around pinned attribute - Fix polls not being hydrated --- app/lib/status_cache_hydrator.rb | 22 +++++-- spec/lib/status_cache_hydrator_spec.rb | 111 +++++++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 32 deletions(-) (limited to 'spec') diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index 01e92b385..552419fff 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -16,23 +16,35 @@ class StatusCacheHydrator # We take advantage of the fact that some relationships can only occur with an original status, not # the reblog that wraps it, so we can assume that some values are always false if payload[:reblog] - payload[:favourited] = false - payload[:reblogged] = false payload[:muted] = false payload[:bookmarked] = false - payload[:pinned] = false + payload[:pinned] = false if @status.account_id == account_id payload[:filtered] = CustomFilter.apply_cached_filters(CustomFilter.cached_filters_for(@status.reblog_of_id), @status.reblog).map { |filter| ActiveModelSerializers::SerializableResource.new(filter, serializer: REST::FilterResultSerializer).as_json } # If the reblogged status is being delivered to the author who disabled the display of the application # used to create the status, we need to hydrate it here too - payload[:reblog][:application] = ActiveModelSerializers::SerializableResource.new(@status.reblog.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id + payload[:reblog][:application] = ActiveModelSerializers::SerializableResource.new(@status.reblog.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id && @status.reblog.application_id.present? payload[:reblog][:favourited] = Favourite.where(account_id: account_id, status_id: @status.reblog_of_id).exists? payload[:reblog][:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.reblog_of_id).exists? payload[:reblog][:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.reblog.conversation_id).exists? payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists? - payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? + payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id payload[:reblog][:filtered] = payload[:filtered] + + if payload[:reblog][:poll] + if @status.reblog.account_id == account_id + payload[:reblog][:poll][:voted] = true + payload[:reblog][:poll][:own_votes] = [] + else + own_votes = @status.reblog.poll.votes.where(account_id: account_id).pluck(:choice) + payload[:reblog][:poll][:voted] = !own_votes.empty? + payload[:reblog][:poll][:own_votes] = own_votes + end + end + + payload[:favourited] = payload[:reblog][:favourited] + payload[:reblogged] = payload[:reblog][:reblogged] else payload[:favourited] = Favourite.where(account_id: account_id, status_id: @status.id).exists? payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists? diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index ad9940a85..873d58464 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -7,48 +7,105 @@ describe StatusCacheHydrator do let(:account) { Fabricate(:account) } describe '#hydrate' do - subject { described_class.new(status).hydrate(account.id) } - let(:compare_to_hash) { InlineRenderer.render(status, account, :status) } - context 'when cache is warm' do - before do - Rails.cache.write("fan-out/#{status.id}", InlineRenderer.render(status, nil, :status)) + shared_examples 'shared behavior' do + context 'when handling a new status' do + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end end - it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) - end - end + context 'when handling a reblog' do + let(:reblog) { Fabricate(:status) } + let(:status) { Fabricate(:status, reblog: reblog) } - context 'when cache is cold' do - before do - Rails.cache.delete("fan-out/#{status.id}") - end + context 'that has been favourited' do + before do + FavouriteService.new.call(account, reblog) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'that has been reblogged' do + before do + ReblogService.new.call(account, reblog) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'that has been pinned' do + let(:reblog) { Fabricate(:status, account: account) } + + before do + StatusPin.create!(account: account, status: reblog) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end - it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + context 'that has been followed tags' do + let(:followed_tag) { Fabricate(:tag) } + + before do + reblog.tags << Fabricate(:tag) + reblog.tags << followed_tag + TagFollow.create!(tag: followed_tag, account: account, rate_limit: false) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'that has a poll authored by the user' do + let(:poll) { Fabricate(:poll, account: account) } + let(:reblog) { Fabricate(:status, poll: poll, account: account) } + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end + + context 'that has been voted in' do + let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } + let(:reblog) { Fabricate(:status, poll: poll) } + + before do + VoteService.new.call(account, poll, [0]) + end + + it 'renders the same attributes as a full render' do + expect(subject).to include(compare_to_hash) + end + end end end - context 'when account has favourited status' do - before do - FavouriteService.new.call(account, status) + context 'when cache is warm' do + subject do + Rails.cache.write("fan-out/#{status.id}", InlineRenderer.render(status, nil, :status)) + described_class.new(status).hydrate(account.id) end - it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) - end + it_behaves_like 'shared behavior' end - context 'when account has reblogged status' do - before do - ReblogService.new.call(account, status) + context 'when cache is cold' do + subject do + Rails.cache.delete("fan-out/#{status.id}") + described_class.new(status).hydrate(account.id) end - it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) - end + it_behaves_like 'shared behavior' end end end -- cgit From bb89f83cc06b9665205c62db21163f2ce43d97ed Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Nov 2022 20:01:33 +0100 Subject: Fix additional issues with status cache hydration (#19747) * Spare one SQL query when hydrating polls * Improve tests * Fix more discrepancies * Fix possible crash when the status has no application set --- app/lib/status_cache_hydrator.rb | 13 +++++++++---- spec/lib/status_cache_hydrator_spec.rb | 26 +++++++++++++++++++------- 2 files changed, 28 insertions(+), 11 deletions(-) (limited to 'spec') diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index 552419fff..ffe813ee9 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -11,7 +11,7 @@ class StatusCacheHydrator # If we're delivering to the author who disabled the display of the application used to create the # status, we need to hydrate the application, since it was not rendered for the basic payload - payload[:application] = ActiveModelSerializers::SerializableResource.new(@status.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:application].nil? && @status.account_id == account_id + payload[:application] = ActiveModelSerializers::SerializableResource.new(@status.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:application].nil? && @status.account_id == account_id && @status.application.present? # We take advantage of the fact that some relationships can only occur with an original status, not # the reblog that wraps it, so we can assume that some values are always false @@ -23,7 +23,7 @@ class StatusCacheHydrator # If the reblogged status is being delivered to the author who disabled the display of the application # used to create the status, we need to hydrate it here too - payload[:reblog][:application] = ActiveModelSerializers::SerializableResource.new(@status.reblog.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id && @status.reblog.application_id.present? + payload[:reblog][:application] = ActiveModelSerializers::SerializableResource.new(@status.reblog.application, serializer: REST::StatusSerializer::ApplicationSerializer).as_json if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id && @status.reblog.application.present? payload[:reblog][:favourited] = Favourite.where(account_id: account_id, status_id: @status.reblog_of_id).exists? payload[:reblog][:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.reblog_of_id).exists? @@ -37,7 +37,7 @@ class StatusCacheHydrator payload[:reblog][:poll][:voted] = true payload[:reblog][:poll][:own_votes] = [] else - own_votes = @status.reblog.poll.votes.where(account_id: account_id).pluck(:choice) + own_votes = PollVote.where(poll_id: @status.reblog.poll_id, account_id: account_id).pluck(:choice) payload[:reblog][:poll][:voted] = !own_votes.empty? payload[:reblog][:poll][:own_votes] = own_votes end @@ -50,8 +50,13 @@ class StatusCacheHydrator payload[:reblogged] = Status.where(account_id: account_id, reblog_of_id: @status.id).exists? payload[:muted] = ConversationMute.where(account_id: account_id, conversation_id: @status.conversation_id).exists? payload[:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.id).exists? - payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? + payload[:pinned] = StatusPin.where(account_id: account_id, status_id: @status.id).exists? if @status.account_id == account_id payload[:filtered] = CustomFilter.apply_cached_filters(CustomFilter.cached_filters_for(@status.id), @status).map { |filter| ActiveModelSerializers::SerializableResource.new(filter, serializer: REST::FilterResultSerializer).as_json } + + if payload[:poll] + payload[:poll][:voted] = @status.account_id == account_id + payload[:poll][:own_votes] = [] + end end payload diff --git a/spec/lib/status_cache_hydrator_spec.rb b/spec/lib/status_cache_hydrator_spec.rb index 873d58464..c9d8d0fe1 100644 --- a/spec/lib/status_cache_hydrator_spec.rb +++ b/spec/lib/status_cache_hydrator_spec.rb @@ -11,8 +11,20 @@ describe StatusCacheHydrator do shared_examples 'shared behavior' do context 'when handling a new status' do + let(:poll) { Fabricate(:poll) } + let(:status) { Fabricate(:status, poll: poll) } + + it 'renders the same attributes as a full render' do + expect(subject).to eql(compare_to_hash) + end + end + + context 'when handling a new status with own poll' do + let(:poll) { Fabricate(:poll, account: account) } + let(:status) { Fabricate(:status, poll: poll, account: account) } + it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -26,7 +38,7 @@ describe StatusCacheHydrator do end it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -36,7 +48,7 @@ describe StatusCacheHydrator do end it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -48,7 +60,7 @@ describe StatusCacheHydrator do end it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -62,7 +74,7 @@ describe StatusCacheHydrator do end it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -71,7 +83,7 @@ describe StatusCacheHydrator do let(:reblog) { Fabricate(:status, poll: poll, account: account) } it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end @@ -84,7 +96,7 @@ describe StatusCacheHydrator do end it 'renders the same attributes as a full render' do - expect(subject).to include(compare_to_hash) + expect(subject).to eql(compare_to_hash) end end end -- cgit