diff --git a/app/lib/activitypub/activity/like.rb b/app/lib/activitypub/activity/like.rb index a335749869..065a2a7d9a 100644 --- a/app/lib/activitypub/activity/like.rb +++ b/app/lib/activitypub/activity/like.rb @@ -26,7 +26,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity def process_favourite return if @account.favourited?(@original_status) - favourite = @original_status.favourites.create!(account: @account) + favourite = @original_status.favourites.create!(account: @account, uri: @json['id']) LocalNotificationWorker.perform_async(@original_status.account_id, favourite.id, 'Favourite', 'favourite') Trends.statuses.register(@original_status) diff --git a/app/lib/vacuum/statuses_vacuum.rb b/app/lib/vacuum/statuses_vacuum.rb index ad1de07380..947ae560f6 100644 --- a/app/lib/vacuum/statuses_vacuum.rb +++ b/app/lib/vacuum/statuses_vacuum.rb @@ -32,9 +32,24 @@ class Vacuum::StatusesVacuum end def statuses_scope - Status.unscoped.kept - .joins(:account).merge(Account.remote) - .where('statuses.id < ?', retention_period_as_id) + scope = Status.unscoped.kept + .joins(:account).merge(Account.remote) + .where('statuses.id < ?', retention_period_as_id) + + if Setting.delete_content_cache_without_reaction + scope = scope.where.not(id: favourited_statuses) + .where.not(id: bookmarked_statuses) + end + + scope + end + + def favourited_statuses + Favourite.local.select(:status_id) + end + + def bookmarked_statuses + Bookmark.select(:status_id) end def retention_period_as_id diff --git a/app/models/emoji_reaction.rb b/app/models/emoji_reaction.rb index ae30d17a3f..7eebe81cb1 100644 --- a/app/models/emoji_reaction.rb +++ b/app/models/emoji_reaction.rb @@ -22,6 +22,8 @@ class EmojiReaction < ApplicationRecord update_index('statuses', :status) + scope :local, -> { where(uri: nil) } + belongs_to :account, inverse_of: :emoji_reactions belongs_to :status, inverse_of: :emoji_reactions belongs_to :custom_emoji, optional: true diff --git a/app/models/favourite.rb b/app/models/favourite.rb index 042f72beae..c2256099a6 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -9,6 +9,7 @@ # updated_at :datetime not null # account_id :bigint(8) not null # status_id :bigint(8) not null +# uri :string # class Favourite < ApplicationRecord @@ -16,6 +17,8 @@ class Favourite < ApplicationRecord update_index('statuses', :status) + scope :local, -> { where(uri: nil) } + belongs_to :account, inverse_of: :favourites belongs_to :status, inverse_of: :favourites diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 555e443316..fd694a887e 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -41,6 +41,7 @@ class Form::AdminSettings media_cache_retention_period content_cache_retention_period backups_retention_period + delete_content_cache_without_reaction status_page_url captcha_enabled ng_words @@ -98,6 +99,7 @@ class Form::AdminSettings unlocked_friend stranger_mention_from_local_ng enable_local_timeline + delete_content_cache_without_reaction ).freeze UPLOAD_KEYS = %i( diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 5a67016148..b0971c1444 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -12,7 +12,12 @@ .fields-group = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + + .fields-group = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }, hint: false, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period') + = f.input :delete_content_cache_without_reaction, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false + + .fields-group = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } .actions diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index b2e8990fd5..8737b97d77 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -345,6 +345,7 @@ en: closed_registrations_message: Custom message when sign-ups are not available content_cache_retention_period: Content cache retention period custom_css: Custom CSS + delete_content_cache_without_reaction: Exclude favorite/bookmarked posts from deletion enable_emoji_reaction: Enable emoji reaction function enable_local_timeline: Enable local timeline enable_public_unlisted_visibility: Enable public-unlisted visibility / public-unlisted searchability diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 8c0b90977d..e6fc715493 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -358,6 +358,7 @@ ja: closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ content_cache_retention_period: コンテンツキャッシュの保持期間 custom_css: カスタムCSS + delete_content_cache_without_reaction: お気に入り・ブックマークされた投稿を削除対象から除外する enable_emoji_reaction: 絵文字リアクション機能を有効にする enable_local_timeline: ローカルタイムラインを有効にする enable_public_unlisted_visibility: 公開範囲「ローカル公開」と検索許可「ローカルとフォロワー」を有効にする diff --git a/config/settings.yml b/config/settings.yml index 9ffa0a7d5b..f4ed5dc695 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -43,6 +43,7 @@ defaults: &defaults show_domain_blocks_rationale: 'disabled' require_invite_text: false backups_retention_period: 7 + delete_content_cache_without_reaction: false captcha_enabled: false receive_other_servers_emoji_reaction: false streaming_other_servers_emoji_reaction: false diff --git a/db/migrate/20240212224800_add_uri_to_favourites.rb b/db/migrate/20240212224800_add_uri_to_favourites.rb new file mode 100644 index 0000000000..c718b5419d --- /dev/null +++ b/db/migrate/20240212224800_add_uri_to_favourites.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddUriToFavourites < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_column :favourites, :uri, :string + add_index :favourites, :uri, unique: true, algorithm: :concurrently + end +end diff --git a/db/migrate/20240212230358_fix_uri_index_to_emoji_reactions.rb b/db/migrate/20240212230358_fix_uri_index_to_emoji_reactions.rb new file mode 100644 index 0000000000..1523f8adbb --- /dev/null +++ b/db/migrate/20240212230358_fix_uri_index_to_emoji_reactions.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class FixUriIndexToEmojiReactions < ActiveRecord::Migration[7.1] + disable_ddl_transaction! + + def change + add_index :emoji_reactions, :uri, unique: true, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 28c3c8463a..bbe9102c05 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[7.1].define(version: 2024_01_21_231131) do +ActiveRecord::Schema[7.1].define(version: 2024_02_12_230358) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -598,6 +598,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_21_231131) do t.index ["account_id"], name: "index_emoji_reactions_on_account_id" t.index ["custom_emoji_id"], name: "index_emoji_reactions_on_custom_emoji_id" t.index ["status_id"], name: "index_emoji_reactions_on_status_id" + t.index ["uri"], name: "index_emoji_reactions_on_uri", unique: true end create_table "encrypted_messages", id: :bigint, default: -> { "timestamp_id('encrypted_messages'::text)" }, force: :cascade do |t| @@ -619,9 +620,11 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_21_231131) do t.datetime "updated_at", precision: nil, null: false t.bigint "account_id", null: false t.bigint "status_id", null: false + t.string "uri" t.index ["account_id", "id"], name: "index_favourites_on_account_id_and_id" t.index ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true t.index ["status_id"], name: "index_favourites_on_status_id" + t.index ["uri"], name: "index_favourites_on_uri", unique: true end create_table "featured_tags", force: :cascade do |t| diff --git a/lib/tasks/dangerous.rake b/lib/tasks/dangerous.rake index 322b78885c..ac529381e2 100644 --- a/lib/tasks/dangerous.rake +++ b/lib/tasks/dangerous.rake @@ -82,6 +82,8 @@ namespace :dangerous do 20240117021025 20240117022353 20240121231131 + 20240212224800 + 20240212230358 ) # Removed: account_groups target_tables = %w( @@ -140,6 +142,7 @@ namespace :dangerous do # Removed: domain_blocks reject_send_unlisted_dissubscribable %w(domain_blocks reject_send_sensitive), %w(domain_blocks reject_straight_follow), + %w(favourites uri), %w(lists notify), %w(statuses limited_scope), %w(statuses markdown), diff --git a/spec/lib/activitypub/activity/like_spec.rb b/spec/lib/activitypub/activity/like_spec.rb index a8d450c383..a6e64c40f8 100644 --- a/spec/lib/activitypub/activity/like_spec.rb +++ b/spec/lib/activitypub/activity/like_spec.rb @@ -49,6 +49,10 @@ RSpec.describe ActivityPub::Activity::Like do it 'creates a favourite from sender to status' do expect(sender.favourited?(status)).to be true end + + it 'creates a favourite and set uri' do + expect(status.favourites.first.uri).to eq 'foo' + end end describe '#perform when receive emoji reaction' do diff --git a/spec/lib/vacuum/statuses_vacuum_spec.rb b/spec/lib/vacuum/statuses_vacuum_spec.rb index d5c0139506..5bdf9fecb0 100644 --- a/spec/lib/vacuum/statuses_vacuum_spec.rb +++ b/spec/lib/vacuum/statuses_vacuum_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Vacuum::StatusesVacuum do let(:retention_period) { 7.days } + let(:local_account) { Fabricate(:account) } let(:remote_account) { Fabricate(:account, domain: 'example.com') } describe '#perform' do @@ -35,4 +36,57 @@ RSpec.describe Vacuum::StatusesVacuum do expect { local_status_recent.reload }.to_not raise_error end end + + describe '#perform with reaction' do + let!(:remote_status_old) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) } + let!(:remote_status_old_faved_byl) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) } + let!(:remote_status_old_faved_byr) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) } + let!(:remote_status_old_bmed_byl) { Fabricate(:status, account: remote_account, created_at: (retention_period + 2.days).ago) } + + let(:delete_content_cache_without_reaction) { true } + + before do + Setting.delete_content_cache_without_reaction = delete_content_cache_without_reaction + Fabricate(:favourite, account: local_account, status: remote_status_old_faved_byl) + Fabricate(:favourite, account: remote_account, status: remote_status_old_faved_byr, uri: 'https://example.com/fav') + Fabricate(:bookmark, account: local_account, status: remote_status_old_bmed_byl) + subject.perform + end + + it 'deletes remote statuses past the retention period' do + expect { remote_status_old.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'deletes remote statuses favourited by remote user' do + expect { remote_status_old_faved_byr.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'does not delete remote statuses favourited by local user' do + expect { remote_status_old_faved_byl.reload }.to_not raise_error + end + + it 'does not delete remote statuses bookmarked by local user' do + expect { remote_status_old_bmed_byl.reload }.to_not raise_error + end + + context 'when excepting is disabled' do + let(:delete_content_cache_without_reaction) { false } + + it 'deletes remote statuses past the retention period' do + expect { remote_status_old.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'deletes remote statuses favourited by remote user' do + expect { remote_status_old_faved_byr.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'deletes remote statuses favourited by local user' do + expect { remote_status_old_faved_byl.reload }.to raise_error ActiveRecord::RecordNotFound + end + + it 'deletes remote statuses bookmarked by local user' do + expect { remote_status_old_bmed_byl.reload }.to raise_error ActiveRecord::RecordNotFound + end + end + end end