From 01ce9df88008cee705b7e02a4581802afa07c3df Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 24 Jan 2024 08:03:30 +0100 Subject: [PATCH 01/18] Fix search form re-rendering spuriously in web UI (#28876) --- .../features/compose/containers/search_container.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 758b6b07db..616b91369c 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -1,3 +1,4 @@ +import { createSelector } from '@reduxjs/toolkit'; import { connect } from 'react-redux'; import { @@ -12,10 +13,15 @@ import { import Search from '../components/search'; +const getRecentSearches = createSelector( + state => state.getIn(['search', 'recent']), + recent => recent.reverse(), +); + const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']).reverse(), + recent: getRecentSearches(state), }); const mapDispatchToProps = dispatch => ({ From 5b1eb09d546120cb456990e15a740d994011013f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 24 Jan 2024 10:38:10 +0100 Subject: [PATCH 02/18] Add annual reports for accounts (#28693) --- .../api/v1/annual_reports_controller.rb | 30 +++++++++ app/lib/annual_report.rb | 43 +++++++++++++ app/lib/annual_report/archetype.rb | 49 +++++++++++++++ .../commonly_interacted_with_accounts.rb | 22 +++++++ .../annual_report/most_reblogged_accounts.rb | 22 +++++++ app/lib/annual_report/most_used_apps.rb | 22 +++++++ app/lib/annual_report/percentiles.rb | 62 +++++++++++++++++++ app/lib/annual_report/source.rb | 16 +++++ app/lib/annual_report/time_series.rb | 30 +++++++++ app/lib/annual_report/top_hashtags.rb | 22 +++++++ app/lib/annual_report/top_statuses.rb | 21 +++++++ app/lib/annual_report/type_distribution.rb | 20 ++++++ app/models/generated_annual_report.rb | 37 +++++++++++ app/presenters/annual_reports_presenter.rb | 23 +++++++ .../rest/annual_report_serializer.rb | 5 ++ .../rest/annual_reports_serializer.rb | 7 +++ app/workers/generate_annual_report_worker.rb | 11 ++++ app/workers/scheduler/indexing_scheduler.rb | 2 + config/routes/api.rb | 6 ++ ...1033014_create_generated_annual_reports.rb | 17 +++++ db/schema.rb | 14 ++++- 21 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/annual_reports_controller.rb create mode 100644 app/lib/annual_report.rb create mode 100644 app/lib/annual_report/archetype.rb create mode 100644 app/lib/annual_report/commonly_interacted_with_accounts.rb create mode 100644 app/lib/annual_report/most_reblogged_accounts.rb create mode 100644 app/lib/annual_report/most_used_apps.rb create mode 100644 app/lib/annual_report/percentiles.rb create mode 100644 app/lib/annual_report/source.rb create mode 100644 app/lib/annual_report/time_series.rb create mode 100644 app/lib/annual_report/top_hashtags.rb create mode 100644 app/lib/annual_report/top_statuses.rb create mode 100644 app/lib/annual_report/type_distribution.rb create mode 100644 app/models/generated_annual_report.rb create mode 100644 app/presenters/annual_reports_presenter.rb create mode 100644 app/serializers/rest/annual_report_serializer.rb create mode 100644 app/serializers/rest/annual_reports_serializer.rb create mode 100644 app/workers/generate_annual_report_worker.rb create mode 100644 db/migrate/20240111033014_create_generated_annual_reports.rb diff --git a/app/controllers/api/v1/annual_reports_controller.rb b/app/controllers/api/v1/annual_reports_controller.rb new file mode 100644 index 0000000000..9bc8e68ac2 --- /dev/null +++ b/app/controllers/api/v1/annual_reports_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Api::V1::AnnualReportsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index + before_action :require_user! + before_action :set_annual_report, except: :index + + def index + with_read_replica do + @presenter = AnnualReportsPresenter.new(GeneratedAnnualReport.where(account_id: current_account.id).pending) + @relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id) + end + + render json: @presenter, + serializer: REST::AnnualReportsSerializer, + relationships: @relationships + end + + def read + @annual_report.view! + render_empty + end + + private + + def set_annual_report + @annual_report = GeneratedAnnualReport.find_by!(account_id: current_account.id, year: params[:id]) + end +end diff --git a/app/lib/annual_report.rb b/app/lib/annual_report.rb new file mode 100644 index 0000000000..cf4297f2a4 --- /dev/null +++ b/app/lib/annual_report.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +class AnnualReport + include DatabaseHelper + + SOURCES = [ + AnnualReport::Archetype, + AnnualReport::TypeDistribution, + AnnualReport::TopStatuses, + AnnualReport::MostUsedApps, + AnnualReport::CommonlyInteractedWithAccounts, + AnnualReport::TimeSeries, + AnnualReport::TopHashtags, + AnnualReport::MostRebloggedAccounts, + AnnualReport::Percentiles, + ].freeze + + SCHEMA = 1 + + def initialize(account, year) + @account = account + @year = year + end + + def generate + return if GeneratedAnnualReport.exists?(account: @account, year: @year) + + GeneratedAnnualReport.create( + account: @account, + year: @year, + schema_version: SCHEMA, + data: data + ) + end + + private + + def data + with_read_replica do + SOURCES.each_with_object({}) { |klass, hsh| hsh.merge!(klass.new(@account, @year).generate) } + end + end +end diff --git a/app/lib/annual_report/archetype.rb b/app/lib/annual_report/archetype.rb new file mode 100644 index 0000000000..ea9ef366df --- /dev/null +++ b/app/lib/annual_report/archetype.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class AnnualReport::Archetype < AnnualReport::Source + # Average number of posts (including replies and reblogs) made by + # each active user in a single year (2023) + AVERAGE_PER_YEAR = 113 + + def generate + { + archetype: archetype, + } + end + + private + + def archetype + if (standalone_count + replies_count + reblogs_count) < AVERAGE_PER_YEAR + :lurker + elsif reblogs_count > (standalone_count * 2) + :booster + elsif polls_count > (standalone_count * 0.1) # standalone_count includes posts with polls + :pollster + elsif replies_count > (standalone_count * 2) + :replier + else + :oracle + end + end + + def polls_count + @polls_count ||= base_scope.where.not(poll_id: nil).count + end + + def reblogs_count + @reblogs_count ||= base_scope.where.not(reblog_of_id: nil).count + end + + def replies_count + @replies_count ||= base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count + end + + def standalone_count + @standalone_count ||= base_scope.without_replies.without_reblogs.count + end + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/lib/annual_report/commonly_interacted_with_accounts.rb b/app/lib/annual_report/commonly_interacted_with_accounts.rb new file mode 100644 index 0000000000..af5e854c22 --- /dev/null +++ b/app/lib/annual_report/commonly_interacted_with_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::CommonlyInteractedWithAccounts < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + commonly_interacted_with_accounts: commonly_interacted_with_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def commonly_interacted_with_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(in_reply_to_account_id: @account.id).group(:in_reply_to_account_id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('in_reply_to_account_id, count(*) AS total')) + end +end diff --git a/app/lib/annual_report/most_reblogged_accounts.rb b/app/lib/annual_report/most_reblogged_accounts.rb new file mode 100644 index 0000000000..e3e8a7c90b --- /dev/null +++ b/app/lib/annual_report/most_reblogged_accounts.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostRebloggedAccounts < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_reblogged_accounts: most_reblogged_accounts.map do |(account_id, count)| + { + account_id: account_id, + count: count, + } + end, + } + end + + private + + def most_reblogged_accounts + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).where.not(reblog_of_id: nil).joins(reblog: :account).group('accounts.id').having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('accounts.id, count(*) as total')) + end +end diff --git a/app/lib/annual_report/most_used_apps.rb b/app/lib/annual_report/most_used_apps.rb new file mode 100644 index 0000000000..85ff1ff86e --- /dev/null +++ b/app/lib/annual_report/most_used_apps.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::MostUsedApps < AnnualReport::Source + SET_SIZE = 10 + + def generate + { + most_used_apps: most_used_apps.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def most_used_apps + @account.statuses.reorder(nil).where(id: year_as_snowflake_range).joins(:application).group('oauth_applications.name').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('oauth_applications.name, count(*) as total')) + end +end diff --git a/app/lib/annual_report/percentiles.rb b/app/lib/annual_report/percentiles.rb new file mode 100644 index 0000000000..9fe4698ee5 --- /dev/null +++ b/app/lib/annual_report/percentiles.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +class AnnualReport::Percentiles < AnnualReport::Source + def generate + { + percentiles: { + followers: (total_with_fewer_followers / (total_with_any_followers + 1.0)) * 100, + statuses: (total_with_fewer_statuses / (total_with_any_statuses + 1.0)) * 100, + }, + } + end + + private + + def followers_gained + @followers_gained ||= @account.passive_relationships.where("date_part('year', follows.created_at) = ?", @year).count + end + + def statuses_created + @statuses_created ||= @account.statuses.where(id: year_as_snowflake_range).count + end + + def total_with_fewer_followers + @total_with_fewer_followers ||= Follow.find_by_sql([<<~SQL.squish, { year: @year, comparison: followers_gained }]).first.total + WITH tmp0 AS ( + SELECT follows.target_account_id + FROM follows + INNER JOIN accounts ON accounts.id = follows.target_account_id + WHERE date_part('year', follows.created_at) = :year + AND accounts.domain IS NULL + GROUP BY follows.target_account_id + HAVING COUNT(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_fewer_statuses + @total_with_fewer_statuses ||= Status.find_by_sql([<<~SQL.squish, { comparison: statuses_created, min_id: year_as_snowflake_range.first, max_id: year_as_snowflake_range.last }]).first.total + WITH tmp0 AS ( + SELECT statuses.account_id + FROM statuses + INNER JOIN accounts ON accounts.id = statuses.account_id + WHERE statuses.id BETWEEN :min_id AND :max_id + AND accounts.domain IS NULL + GROUP BY statuses.account_id + HAVING count(*) < :comparison + ) + SELECT count(*) AS total + FROM tmp0 + SQL + end + + def total_with_any_followers + @total_with_any_followers ||= Follow.where("date_part('year', follows.created_at) = ?", @year).joins(:target_account).merge(Account.local).count('distinct follows.target_account_id') + end + + def total_with_any_statuses + @total_with_any_statuses ||= Status.where(id: year_as_snowflake_range).joins(:account).merge(Account.local).count('distinct statuses.account_id') + end +end diff --git a/app/lib/annual_report/source.rb b/app/lib/annual_report/source.rb new file mode 100644 index 0000000000..1ccb622676 --- /dev/null +++ b/app/lib/annual_report/source.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AnnualReport::Source + attr_reader :account, :year + + def initialize(account, year) + @account = account + @year = year + end + + protected + + def year_as_snowflake_range + (Mastodon::Snowflake.id_at(DateTime.new(year, 1, 1))..Mastodon::Snowflake.id_at(DateTime.new(year, 12, 31))) + end +end diff --git a/app/lib/annual_report/time_series.rb b/app/lib/annual_report/time_series.rb new file mode 100644 index 0000000000..a144bac0d1 --- /dev/null +++ b/app/lib/annual_report/time_series.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class AnnualReport::TimeSeries < AnnualReport::Source + def generate + { + time_series: (1..12).map do |month| + { + month: month, + statuses: statuses_per_month[month] || 0, + following: following_per_month[month] || 0, + followers: followers_per_month[month] || 0, + } + end, + } + end + + private + + def statuses_per_month + @statuses_per_month ||= @account.statuses.reorder(nil).where(id: year_as_snowflake_range).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def following_per_month + @following_per_month ||= @account.active_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end + + def followers_per_month + @followers_per_month ||= @account.passive_relationships.where("date_part('year', created_at) = ?", @year).group(:period).pluck(Arel.sql("date_part('month', created_at)::int AS period, count(*)")).to_h + end +end diff --git a/app/lib/annual_report/top_hashtags.rb b/app/lib/annual_report/top_hashtags.rb new file mode 100644 index 0000000000..488dacb1b4 --- /dev/null +++ b/app/lib/annual_report/top_hashtags.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AnnualReport::TopHashtags < AnnualReport::Source + SET_SIZE = 40 + + def generate + { + top_hashtags: top_hashtags.map do |(name, count)| + { + name: name, + count: count, + } + end, + } + end + + private + + def top_hashtags + Tag.joins(:statuses).where(statuses: { id: @account.statuses.where(id: year_as_snowflake_range).reorder(nil).select(:id) }).group(:id).having('count(*) > 1').order(total: :desc).limit(SET_SIZE).pluck(Arel.sql('COALESCE(tags.display_name, tags.name), count(*) AS total')) + end +end diff --git a/app/lib/annual_report/top_statuses.rb b/app/lib/annual_report/top_statuses.rb new file mode 100644 index 0000000000..112e5591ce --- /dev/null +++ b/app/lib/annual_report/top_statuses.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class AnnualReport::TopStatuses < AnnualReport::Source + def generate + top_reblogs = base_scope.order(reblogs_count: :desc).first&.id + top_favourites = base_scope.where.not(id: top_reblogs).order(favourites_count: :desc).first&.id + top_replies = base_scope.where.not(id: [top_reblogs, top_favourites]).order(replies_count: :desc).first&.id + + { + top_statuses: { + by_reblogs: top_reblogs, + by_favourites: top_favourites, + by_replies: top_replies, + }, + } + end + + def base_scope + @account.statuses.with_public_visibility.joins(:status_stat).where(id: year_as_snowflake_range).reorder(nil) + end +end diff --git a/app/lib/annual_report/type_distribution.rb b/app/lib/annual_report/type_distribution.rb new file mode 100644 index 0000000000..fc12a6f1f4 --- /dev/null +++ b/app/lib/annual_report/type_distribution.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AnnualReport::TypeDistribution < AnnualReport::Source + def generate + { + type_distribution: { + total: base_scope.count, + reblogs: base_scope.where.not(reblog_of_id: nil).count, + replies: base_scope.where.not(in_reply_to_id: nil).where.not(in_reply_to_account_id: @account.id).count, + standalone: base_scope.without_replies.without_reblogs.count, + }, + } + end + + private + + def base_scope + @account.statuses.where(id: year_as_snowflake_range) + end +end diff --git a/app/models/generated_annual_report.rb b/app/models/generated_annual_report.rb new file mode 100644 index 0000000000..43c97d7108 --- /dev/null +++ b/app/models/generated_annual_report.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: generated_annual_reports +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# year :integer not null +# data :jsonb not null +# schema_version :integer not null +# viewed_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class GeneratedAnnualReport < ApplicationRecord + belongs_to :account + + scope :pending, -> { where(viewed_at: nil) } + + def viewed? + viewed_at.present? + end + + def view! + update!(viewed_at: Time.now.utc) + end + + def account_ids + data['most_reblogged_accounts'].pluck('account_id') + data['commonly_interacted_with_accounts'].pluck('account_id') + end + + def status_ids + data['top_statuses'].values + end +end diff --git a/app/presenters/annual_reports_presenter.rb b/app/presenters/annual_reports_presenter.rb new file mode 100644 index 0000000000..001e1d37b0 --- /dev/null +++ b/app/presenters/annual_reports_presenter.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class AnnualReportsPresenter + alias read_attribute_for_serialization send + + attr_reader :annual_reports + + def initialize(annual_reports) + @annual_reports = annual_reports + end + + def accounts + @accounts ||= Account.where(id: @annual_reports.flat_map(&:account_ids)).includes(:account_stat, :moved_to_account, user: :role) + end + + def statuses + @statuses ||= Status.where(id: @annual_reports.flat_map(&:status_ids)).with_includes + end + + def self.model_name + @model_name ||= ActiveModel::Name.new(self) + end +end diff --git a/app/serializers/rest/annual_report_serializer.rb b/app/serializers/rest/annual_report_serializer.rb new file mode 100644 index 0000000000..1fb5ddb5c1 --- /dev/null +++ b/app/serializers/rest/annual_report_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class REST::AnnualReportSerializer < ActiveModel::Serializer + attributes :year, :data, :schema_version +end diff --git a/app/serializers/rest/annual_reports_serializer.rb b/app/serializers/rest/annual_reports_serializer.rb new file mode 100644 index 0000000000..ea9572be1b --- /dev/null +++ b/app/serializers/rest/annual_reports_serializer.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class REST::AnnualReportsSerializer < ActiveModel::Serializer + has_many :annual_reports, serializer: REST::AnnualReportSerializer + has_many :accounts, serializer: REST::AccountSerializer + has_many :statuses, serializer: REST::StatusSerializer +end diff --git a/app/workers/generate_annual_report_worker.rb b/app/workers/generate_annual_report_worker.rb new file mode 100644 index 0000000000..7094c1ab9c --- /dev/null +++ b/app/workers/generate_annual_report_worker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class GenerateAnnualReportWorker + include Sidekiq::Worker + + def perform(account_id, year) + AnnualReport.new(Account.find(account_id), year).generate + rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordNotUnique + true + end +end diff --git a/app/workers/scheduler/indexing_scheduler.rb b/app/workers/scheduler/indexing_scheduler.rb index 5c985e25a0..f52d0141d4 100644 --- a/app/workers/scheduler/indexing_scheduler.rb +++ b/app/workers/scheduler/indexing_scheduler.rb @@ -24,6 +24,8 @@ class Scheduler::IndexingScheduler end end + private + def indexes [AccountsIndex, TagsIndex, PublicStatusesIndex, StatusesIndex] end diff --git a/config/routes/api.rb b/config/routes/api.rb index 0fe9f69abc..853a44e0e1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -51,6 +51,12 @@ namespace :api, format: false do resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :preferences, only: [:index] + resources :annual_reports, only: [:index] do + member do + post :read + end + end + resources :announcements, only: [:index] do scope module: :announcements do resources :reactions, only: [:update, :destroy] diff --git a/db/migrate/20240111033014_create_generated_annual_reports.rb b/db/migrate/20240111033014_create_generated_annual_reports.rb new file mode 100644 index 0000000000..2a755fb14e --- /dev/null +++ b/db/migrate/20240111033014_create_generated_annual_reports.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateGeneratedAnnualReports < ActiveRecord::Migration[7.1] + def change + create_table :generated_annual_reports do |t| + t.belongs_to :account, null: false, foreign_key: { on_cascade: :delete }, index: false + t.integer :year, null: false + t.jsonb :data, null: false + t.integer :schema_version, null: false + t.datetime :viewed_at + + t.timestamps + end + + add_index :generated_annual_reports, [:account_id, :year], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index cbe54c1db7..50f4e7189d 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_09_103012) do +ActiveRecord::Schema[7.1].define(version: 2024_01_11_033014) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -516,6 +516,17 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do t.index ["target_account_id"], name: "index_follows_on_target_account_id" end + create_table "generated_annual_reports", force: :cascade do |t| + t.bigint "account_id", null: false + t.integer "year", null: false + t.jsonb "data", null: false + t.integer "schema_version", null: false + t.datetime "viewed_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "year"], name: "index_generated_annual_reports_on_account_id_and_year", unique: true + end + create_table "identities", force: :cascade do |t| t.string "provider", default: "", null: false t.string "uid", default: "", null: false @@ -1226,6 +1237,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_01_09_103012) do add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade + add_foreign_key "generated_annual_reports", "accounts" add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade From 599bc69503e9ef54655626052c8337cedb8519d0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 04:57:32 -0500 Subject: [PATCH 03/18] Simplify `AccountSummary.filtered` query generation (#28868) --- app/models/account_summary.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/account_summary.rb b/app/models/account_summary.rb index 0d8835b83c..2a21d09a8b 100644 --- a/app/models/account_summary.rb +++ b/app/models/account_summary.rb @@ -12,9 +12,11 @@ class AccountSummary < ApplicationRecord self.primary_key = :account_id + has_many :follow_recommendation_suppressions, primary_key: :account_id, foreign_key: :account_id, inverse_of: false + scope :safe, -> { where(sensitive: false) } scope :localized, ->(locale) { where(language: locale) } - scope :filtered, -> { joins(arel_table.join(FollowRecommendationSuppression.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:account_id].eq(FollowRecommendationSuppression.arel_table[:account_id])).join_sources).where(FollowRecommendationSuppression.arel_table[:id].eq(nil)) } + scope :filtered, -> { where.missing(:follow_recommendation_suppressions) } def self.refresh Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false) From 9d413cbaf83f1a030a250806302c1a21435559d7 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 04:57:49 -0500 Subject: [PATCH 04/18] Fix `Rails/WhereExists` cop in app/models (#28863) --- .rubocop_todo.yml | 3 --- app/models/poll.rb | 2 +- app/models/session_activation.rb | 2 +- app/models/status.rb | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bef79e451a..b62dfa72a8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -81,9 +81,6 @@ Rails/WhereExists: - 'app/lib/delivery_failure_tracker.rb' - 'app/lib/feed_manager.rb' - 'app/lib/suspicious_sign_in_detector.rb' - - 'app/models/poll.rb' - - 'app/models/session_activation.rb' - - 'app/models/status.rb' - 'app/policies/status_policy.rb' - 'app/serializers/rest/announcement_serializer.rb' - 'app/workers/move_worker.rb' diff --git a/app/models/poll.rb b/app/models/poll.rb index 37149c3d86..cc4184f80a 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -57,7 +57,7 @@ class Poll < ApplicationRecord end def voted?(account) - account.id == account_id || votes.where(account: account).exists? + account.id == account_id || votes.exists?(account: account) end def own_votes(account) diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index 7f5f0d9a9a..c67180d3ba 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -41,7 +41,7 @@ class SessionActivation < ApplicationRecord class << self def active?(id) - id && where(session_id: id).exists? + id && exists?(session_id: id) end def activate(**options) diff --git a/app/models/status.rb b/app/models/status.rb index 9a2169f995..e3d41cceda 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -263,7 +263,7 @@ class Status < ApplicationRecord end def reported? - @reported ||= Report.where(target_account: account).unresolved.where('? = ANY(status_ids)', id).exists? + @reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id]) end def emojis From a34d27c18f936b63fa1d595b42b69df618793a69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 11:06:56 +0100 Subject: [PATCH 05/18] New Crowdin Translations (automated) (#28875) Co-authored-by: GitHub Actions --- config/locales/ar.yml | 1 + config/locales/bg.yml | 1 + config/locales/da.yml | 6 ++++++ config/locales/devise.ru.yml | 9 +++++++++ config/locales/devise.sq.yml | 9 +++++++++ config/locales/es-MX.yml | 4 ++++ config/locales/es.yml | 4 ++++ config/locales/fy.yml | 6 ++++++ config/locales/gl.yml | 6 ++++++ config/locales/ru.yml | 14 ++++++++++++++ config/locales/simple_form.sk.yml | 1 + config/locales/sk.yml | 3 +++ config/locales/sq.yml | 13 +++++++++++++ config/locales/tr.yml | 2 +- config/locales/vi.yml | 6 ++++++ 15 files changed, 84 insertions(+), 1 deletion(-) diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 3c8c643fe7..e6d653c674 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1934,6 +1934,7 @@ ar: go_to_sso_account_settings: انتقل إلى إعدادات حساب مزود الهوية الخاص بك invalid_otp_token: رمز المصادقة بخطوتين غير صالح otp_lost_help_html: إن فقدتَهُما ، يمكنك الاتصال بـ %{email} + rate_limited: عدد محاولات التحقق كثير جدًا، يرجى المحاولة مرة أخرى لاحقًا. seamless_external_login: لقد قمت بتسجيل الدخول عبر خدمة خارجية، إنّ إعدادات الكلمة السرية و البريد الإلكتروني غير متوفرة. signed_in_as: 'تم تسجيل دخولك بصفة:' verification: diff --git a/config/locales/bg.yml b/config/locales/bg.yml index c3eaa7e4c2..b9a3135448 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1793,6 +1793,7 @@ bg: failed_2fa: details: 'Ето подробности на опита за влизане:' explanation: Някой се опита да влезе в акаунта ви, но предостави невалиден втори фактор за удостоверяване. + further_actions_html: Ако не бяхте вие, то препоръчваме да направите %{action} незабавно, тъй като може да се злепостави. subject: Неуспешен втори фактор за удостоверяване title: Провал на втория фактор за удостоверяване suspicious_sign_in: diff --git a/config/locales/da.yml b/config/locales/da.yml index 58fd723aef..d92d001905 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -1790,6 +1790,12 @@ da: extra: Sikkerhedskopien kan nu downloades! subject: Dit arkiv er klar til download title: Arkiv download + failed_2fa: + details: 'Her er detaljerne om login-forsøget:' + explanation: Nogen har forsøgt at logge ind på kontoen, men har angivet en ugyldig anden godkendelsesfaktor. + further_actions_html: Var dette ikke dig, anbefales det straks at %{action}, da den kan være kompromitteret. + subject: Anden faktor godkendelsesfejl + title: Fejlede på anden faktor godkendelse suspicious_sign_in: change_password: ændrer din adgangskode details: 'Her er nogle detaljer om login-forsøget:' diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml index ccbd13438d..9dd418f2cd 100644 --- a/config/locales/devise.ru.yml +++ b/config/locales/devise.ru.yml @@ -47,14 +47,19 @@ ru: subject: 'Mastodon: Инструкция по сбросу пароля' title: Сброс пароля two_factor_disabled: + explanation: Вход в систему теперь возможен только с использованием адреса электронной почты и пароля. subject: 'Mastodon: Двухфакторная авторизация отключена' + subtitle: Двухфакторная аутентификация для вашей учетной записи была отключена. title: 2ФА отключена two_factor_enabled: + explanation: Для входа в систему потребуется токен, сгенерированный сопряженным приложением TOTP. subject: 'Mastodon: Настроена двухфакторная авторизация' + subtitle: Для вашей учетной записи была включена двухфакторная аутентификация. title: 2ФА включена two_factor_recovery_codes_changed: explanation: Предыдущие резервные коды были аннулированы и созданы новые. subject: 'Mastodon: Резервные коды двуфакторной авторизации обновлены' + subtitle: Предыдущие коды восстановления были аннулированы и сгенерированы новые. title: Коды восстановления 2FA изменены unlock_instructions: subject: 'Mastodon: Инструкция по разблокировке' @@ -68,9 +73,13 @@ ru: subject: 'Мастодон: Ключ Безопасности удален' title: Один из ваших защитных ключей был удален webauthn_disabled: + explanation: Аутентификация с помощью ключей безопасности была отключена для вашей учетной записи. + extra: Теперь вход в систему возможен только с использованием токена, сгенерированного сопряженным приложением TOTP. subject: 'Мастодон: Аутентификация с ключами безопасности отключена' title: Ключи безопасности отключены webauthn_enabled: + explanation: Для вашей учетной записи включена аутентификация по ключу безопасности. + extra: Теперь ваш ключ безопасности можно использовать для входа в систему. subject: 'Мастодон: Включена аутентификация по ключу безопасности' title: Ключи безопасности включены omniauth_callbacks: diff --git a/config/locales/devise.sq.yml b/config/locales/devise.sq.yml index 7cea2f8e2e..32136a0baa 100644 --- a/config/locales/devise.sq.yml +++ b/config/locales/devise.sq.yml @@ -47,14 +47,19 @@ sq: subject: 'Mastodon: Udhëzime ricaktimi fjalëkalimi' title: Ricaktim fjalëkalimi two_factor_disabled: + explanation: Hyrja tanimë është e mundshme duke përdorur vetëm adresë email dhe fjalëkalim. subject: 'Mastodon: U çaktivizua mirëfilltësimi dyfaktorësh' + subtitle: Mirëfilltësimi dyfaktorësh për llogarinë tuaj është çaktivizuar. title: 2FA u çaktivizua two_factor_enabled: + explanation: Për të kryer hyrjen do të kërkohet doemos një token i prodhuar nga aplikacioni TOTP i çiftuar. subject: 'Mastodon: U aktivizua mirëfilltësimi dyfaktorësh' + subtitle: Për llogarinë tuaj është aktivizuar mirëfilltësmi dyfaktorësh. title: 2FA u aktivizua two_factor_recovery_codes_changed: explanation: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj. subject: 'Mastodon: U riprodhuan kode rikthimi dyfaktorësh' + subtitle: Kodet e dikurshëm të rikthimit janë bërë të pavlefshëm dhe janë prodhuar të rinj. title: Kodet e rikthimit 2FA u ndryshuan unlock_instructions: subject: 'Mastodon: Udhëzime shkyçjeje' @@ -68,9 +73,13 @@ sq: subject: 'Mastodon: Fshirje kyçi sigurie' title: Një nga kyçet tuaj të sigurisë është fshirë webauthn_disabled: + explanation: Mirëfilltësimi me kyçe sigurie është çaktivizuar për llogarinë tuaj. + extra: Hyrjet tani janë të mundshme vetëm duke përdorur token-in e prodhuar nga aplikacioni TOTP i çiftuar. subject: 'Mastodon: U çaktivizua mirëfilltësimi me kyçe sigurie' title: U çaktivizuan kyçe sigurie webauthn_enabled: + explanation: Mirëfilltësimi me kyçe sigurie është aktivizuar për këtë llogari. + extra: Kyçi juaj i sigurisë tanimë mund të përdoret për hyrje. subject: 'Mastodon: U aktivizua mirëfilltësim me kyçe sigurie' title: U aktivizuan kyçe sigurie omniauth_callbacks: diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 040d8a9d3c..b84fb7cf96 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -1792,6 +1792,10 @@ es-MX: title: Descargar archivo failed_2fa: details: 'Estos son los detalles del intento de inicio de sesión:' + explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido. + further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometido. + subject: Fallo de autenticación de segundo factor + title: Falló la autenticación de segundo factor suspicious_sign_in: change_password: cambies tu contraseña details: 'Aquí están los detalles del inicio de sesión:' diff --git a/config/locales/es.yml b/config/locales/es.yml index ffe3eb5b00..95816d6bcb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -1792,6 +1792,10 @@ es: title: Descargar archivo failed_2fa: details: 'Estos son los detalles del intento de inicio de sesión:' + explanation: Alguien ha intentado iniciar sesión en tu cuenta pero proporcionó un segundo factor de autenticación inválido. + further_actions_html: Si no fuiste tú, se recomienda %{action} inmediatamente ya que puede estar comprometida. + subject: Fallo de autenticación del segundo factor + title: Fallo en la autenticación del segundo factor suspicious_sign_in: change_password: cambies tu contraseña details: 'Aquí están los detalles del inicio de sesión:' diff --git a/config/locales/fy.yml b/config/locales/fy.yml index f861bc3e4a..c59ad72725 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1790,6 +1790,12 @@ fy: extra: It stiet no klear om download te wurden! subject: Jo argyf stiet klear om download te wurden title: Argyf ophelje + failed_2fa: + details: 'Hjir binne de details fan de oanmeldbesykjen:' + explanation: Ien hat probearre om oan te melden op jo account, mar hat in ûnjildige twaddeferifikaasjefaktor opjûn. + further_actions_html: As jo dit net wiene, rekommandearje wy jo oan daliks %{action}, omdat it kompromitearre wêze kin. + subject: Twaddefaktorautentikaasjeflater + title: Twastapsferifikaasje mislearre suspicious_sign_in: change_password: wizigje jo wachtwurd details: 'Hjir binne de details fan oanmeldbesykjen:' diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 3c43a4e23d..087ed2ec76 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -1790,6 +1790,12 @@ gl: extra: Está preparada para descargala! subject: O teu ficheiro xa está preparado para descargar title: Leve o ficheiro + failed_2fa: + details: 'Detalles do intento de acceso:' + explanation: Alguén intentou acceder á túa conta mais fíxoo cun segundo factor de autenticación non válido. + further_actions_html: Se non foches ti, recomendámosche %{action} inmediatamente xa que a conta podería estar en risco. + subject: Fallo co segundo factor de autenticación + title: Fallou o segundo factor de autenticación suspicious_sign_in: change_password: cambia o teu contrasinal details: 'Estos son os detalles do acceso:' diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 2644275c37..24edbdc75e 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -439,6 +439,7 @@ ru: view: Посмотреть доменные блокировки email_domain_blocks: add_new: Добавить новую + allow_registrations_with_approval: Разрешить регистрацию с одобрением attempts_over_week: few: "%{count} попытки за последнюю неделю" many: "%{count} попыток за последнюю неделю" @@ -1659,6 +1660,7 @@ ru: unknown_browser: Неизвестный браузер weibo: Weibo current_session: Текущая сессия + date: Дата description: "%{browser} на %{platform}" explanation: Здесь отображаются все браузеры, с которых выполнен вход в вашу учётную запись. Авторизованные приложения находятся в секции «Приложения». ip: IP @@ -1837,16 +1839,27 @@ ru: webauthn: Ключи безопасности user_mailer: appeal_approved: + action: Настройки аккаунта explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись снова на хорошем счету. subject: Ваше обжалование от %{date} была одобрено + subtitle: Ваш аккаунт снова с хорошей репутацией. title: Обжалование одобрено appeal_rejected: explanation: Апелляция на разблокировку против вашей учетной записи %{strike_date}, которую вы подали на %{appeal_date}, была одобрена. Ваша учетная запись восстановлена. subject: Ваше обжалование от %{date} отклонено + subtitle: Ваша апелляция отклонена. title: Обжалование отклонено backup_ready: + explanation: Вы запросили полное резервное копирование вашей учетной записи Mastodon. + extra: Теперь он готов к загрузке! subject: Ваш архив готов к загрузке title: Архив ваших данных готов + failed_2fa: + details: 'Вот подробности попытки регистрации:' + explanation: Кто-то пытался войти в вашу учетную запись, но указал неверный второй фактор аутентификации. + further_actions_html: Если это не вы, мы рекомендуем %{action} немедленно принять меры, так как он может быть скомпрометирован. + subject: Сбой двухфакторной аутентификации + title: Сбой двухфакторной аутентификации suspicious_sign_in: change_password: сменить пароль details: 'Подробности о новом входе:' @@ -1900,6 +1913,7 @@ ru: go_to_sso_account_settings: Перейти к настройкам сторонних аккаунтов учетной записи invalid_otp_token: Введен неверный код двухфакторной аутентификации otp_lost_help_html: Если Вы потеряли доступ к обоим, свяжитесь с %{email} + rate_limited: Слишком много попыток аутентификации, повторите попытку позже. seamless_external_login: Вы залогинены через сторонний сервис, поэтому настройки e-mail и пароля недоступны. signed_in_as: 'Выполнен вход под именем:' verification: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index e13a05835f..614812a3a9 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -60,6 +60,7 @@ sk: fields: name: Označenie value: Obsah + unlocked: Automaticky prijímaj nových nasledovateľov account_alias: acct: Adresa starého účtu account_migration: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index c639bbe1a6..e83ae348f6 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -430,6 +430,7 @@ sk: dashboard: instance_accounts_dimension: Najsledovanejšie účty instance_accounts_measure: uložené účty + instance_followers_measure: naši nasledovatelia tam instance_follows_measure: ich sledovatelia tu instance_languages_dimension: Najpopulárnejšie jazyky instance_media_attachments_measure: uložené mediálne prílohy @@ -1257,6 +1258,8 @@ sk: extra: Teraz je pripravená na stiahnutie! subject: Tvoj archív je pripravený na stiahnutie title: Odber archívu + failed_2fa: + details: 'Tu sú podrobnosti o pokuse o prihlásenie:' warning: subject: disable: Tvoj účet %{acct} bol zamrazený diff --git a/config/locales/sq.yml b/config/locales/sq.yml index 1693db7f31..d6e6925c70 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -1604,6 +1604,7 @@ sq: unknown_browser: Shfletues i Panjohur weibo: Weibo current_session: Sesioni i tanishëm + date: Datë description: "%{browser} në %{platform}" explanation: Këta janë shfletuesit e përdorur tani për hyrje te llogaria juaj Mastodon. ip: IP @@ -1770,16 +1771,27 @@ sq: webauthn: Kyçe sigurie user_mailer: appeal_approved: + action: Rregullime Llogarie explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date} është miratuar. Llogaria juaj është sërish në pozita të mira. subject: Apelimi juaj i datës %{date} u miratua + subtitle: Llogaria juaj edhe një herë është e shëndetshme. title: Apelimi u miratua appeal_rejected: explanation: Apelimi i paralajmërimit kundër llogarisë tuaj më %{strike_date}, të cilin e parashtruar më %{appeal_date}, u hodh poshtë. subject: Apelimi juaj prej %{date} është hedhur poshtë + subtitle: Apelimi juaj është hedhur poshtë. title: Apelimi u hodh poshtë backup_ready: + explanation: Kërkuat një kopjeruajtje të plotë të llogarisë tuaj Mastodon. + extra: Tani është gati për shkarkim! subject: Arkivi juaj është gati për shkarkim title: Marrje arkivi me vete + failed_2fa: + details: 'Ja hollësitë e përpjekjes për hyrje:' + explanation: Dikush ka provuar të hyjë në llogarinë tuaj, por dha faktor të dytë mirëfilltësimi. + further_actions_html: Nëse s’qetë ju, rekomandojmë të %{action} menjëherë, ngaqë mund të jetë komprometua. + subject: Dështim faktori të dytë mirëfilltësimesh + title: Dështoi mirëfilltësimi me faktor të dytë suspicious_sign_in: change_password: ndryshoni fjalëkalimin tuaj details: 'Ja hollësitë për hyrjen:' @@ -1833,6 +1845,7 @@ sq: go_to_sso_account_settings: Kaloni te rregullime llogarie te shërbimi juaj i identitetit invalid_otp_token: Kod dyfaktorësh i pavlefshëm otp_lost_help_html: Nëse humbët hyrjen te të dy, mund të lidheni me %{email} + rate_limited: Shumë përpjekje mirëfilltësimi, riprovoni më vonë. seamless_external_login: Jeni futur përmes një shërbimi të jashtëm, ndaj s’ka rregullime fjalëkalimi dhe email. signed_in_as: 'I futur si:' verification: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index fa84d2a96d..2b5b5ad45b 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1791,7 +1791,7 @@ tr: subject: Arşiviniz indirilmeye hazır title: Arşiv paketlemesi failed_2fa: - details: 'Oturum açma denemesinin ayrıntıları şöyledir:' + details: 'İşte oturum açma girişiminin ayrıntıları:' explanation: Birisi hesabınızda oturum açmaya çalıştı ancak hatalı bir iki aşamalı doğrulama kodu kullandı. further_actions_html: Eğer bu kişi siz değilseniz, hemen %{action} yapmanızı öneriyoruz çünkü hesabınız ifşa olmuş olabilir. subject: İki aşamalı doğrulama başarısızlığı diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 3817b18f07..1ece72e154 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1758,6 +1758,12 @@ vi: extra: Hiện nó đã sẵn sàng tải xuống! subject: Dữ liệu cá nhân của bạn đã sẵn sàng để tải về title: Nhận dữ liệu cá nhân + failed_2fa: + details: 'Chi tiết thông tin đăng nhập:' + explanation: Ai đó đã cố đăng nhập vào tài khoản của bạn nhưng cung cấp yếu tố xác thực thứ hai không hợp lệ. + further_actions_html: Nếu không phải bạn, hãy lập tức %{action} vì có thể có rủi ro. + subject: Xác minh hai bước thất bại + title: Xác minh hai bước thất bại suspicious_sign_in: change_password: đổi mật khẩu của bạn details: 'Chi tiết thông tin đăng nhập:' From a11a2fb052ad2903426e7798040f699b8a1f051c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 05:31:31 -0500 Subject: [PATCH 06/18] Add error classes to api/base errors coverage (#28864) --- spec/controllers/api/base_controller_spec.rb | 30 +++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index db1e8777f7..f8e014be2f 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -12,7 +12,7 @@ describe Api::BaseController do head 200 end - def error + def failure FakeService.new end end @@ -30,7 +30,7 @@ describe Api::BaseController do it 'does not protect from forgery' do ActionController::Base.allow_forgery_protection = true - post 'success' + post :success expect(response).to have_http_status(200) end end @@ -50,47 +50,55 @@ describe Api::BaseController do it 'returns http forbidden for unconfirmed accounts' do user.update(confirmed_at: nil) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for pending accounts' do user.update(approved: false) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for disabled accounts' do user.update(disabled: true) - post 'success' + post :success expect(response).to have_http_status(403) end it 'returns http forbidden for suspended accounts' do user.account.suspend! - post 'success' + post :success expect(response).to have_http_status(403) end end describe 'error handling' do before do - routes.draw { get 'error' => 'api/base#error' } + routes.draw { get 'failure' => 'api/base#failure' } end { ActiveRecord::RecordInvalid => 422, - Mastodon::ValidationError => 422, ActiveRecord::RecordNotFound => 404, - Mastodon::UnexpectedResponseError => 503, + ActiveRecord::RecordNotUnique => 422, + Date::Error => 422, HTTP::Error => 503, - OpenSSL::SSL::SSLError => 503, + Mastodon::InvalidParameterError => 400, Mastodon::NotPermittedError => 403, + Mastodon::RaceConditionError => 503, + Mastodon::RateLimitExceededError => 429, + Mastodon::UnexpectedResponseError => 503, + Mastodon::ValidationError => 422, + OpenSSL::SSL::SSLError => 503, + Seahorse::Client::NetworkingError => 503, + Stoplight::Error::RedLight => 503, }.each do |error, code| it "Handles error class of #{error}" do allow(FakeService).to receive(:new).and_raise(error) - get 'error' + get :failure + expect(response).to have_http_status(code) expect(FakeService).to have_received(:new) end From 7a1f087659204e9d0cbba2de37e45b1921cefe20 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 05:32:54 -0500 Subject: [PATCH 07/18] Add `created_before` and `updated_before` scopes to `MediaAttachment` (#28869) --- app/lib/vacuum/media_attachments_vacuum.rb | 10 ++++++++-- app/models/media_attachment.rb | 12 +++++++----- lib/mastodon/cli/statuses.rb | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/lib/vacuum/media_attachments_vacuum.rb b/app/lib/vacuum/media_attachments_vacuum.rb index ab7ea4092f..e558195290 100644 --- a/app/lib/vacuum/media_attachments_vacuum.rb +++ b/app/lib/vacuum/media_attachments_vacuum.rb @@ -27,11 +27,17 @@ class Vacuum::MediaAttachmentsVacuum end def media_attachments_past_retention_period - MediaAttachment.remote.cached.where(MediaAttachment.arel_table[:created_at].lt(@retention_period.ago)).where(MediaAttachment.arel_table[:updated_at].lt(@retention_period.ago)) + MediaAttachment + .remote + .cached + .created_before(@retention_period.ago) + .updated_before(@retention_period.ago) end def orphaned_media_attachments - MediaAttachment.unattached.where(MediaAttachment.arel_table[:created_at].lt(TTL.ago)) + MediaAttachment + .unattached + .created_before(TTL.ago) end def retention_period? diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 1f40e5725e..7ff6a15f5d 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -204,12 +204,14 @@ class MediaAttachment < ApplicationRecord validates :file, presence: true, if: :local? validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? } - scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } - scope :cached, -> { remote.where.not(file_file_name: nil) } - scope :local, -> { where(remote_url: '') } - scope :ordered, -> { order(id: :asc) } - scope :remote, -> { where.not(remote_url: '') } + scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } + scope :cached, -> { remote.where.not(file_file_name: nil) } + scope :created_before, ->(value) { where(arel_table[:created_at].lt(value)) } + scope :local, -> { where(remote_url: '') } + scope :ordered, -> { order(id: :asc) } + scope :remote, -> { where.not(remote_url: '') } scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } + scope :updated_before, ->(value) { where(arel_table[:updated_at].lt(value)) } attr_accessor :skip_download diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb index 7acf3f9b77..48d76e0288 100644 --- a/lib/mastodon/cli/statuses.rb +++ b/lib/mastodon/cli/statuses.rb @@ -120,7 +120,7 @@ module Mastodon::CLI say('Beginning removal of now-orphaned media attachments to free up disk space...') - scope = MediaAttachment.unattached.where('created_at < ?', options[:days].pred.days.ago) + scope = MediaAttachment.unattached.created_before(options[:days].pred.days.ago) processed = 0 removed = 0 progress = create_progress_bar(scope.count) From b19ae521b7d28a76e8e1d8da8157e051e9d8de6c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 24 Jan 2024 11:49:19 +0100 Subject: [PATCH 08/18] Add confirmation when redirecting logged-out requests to permalink (#27792) Co-authored-by: Claire --- .../concerns/web_app_controller_concern.rb | 15 ++++- .../redirect/accounts_controller.rb | 10 +++ app/controllers/redirect/base_controller.rb | 24 +++++++ .../redirect/statuses_controller.rb | 10 +++ .../styles/mastodon/containers.scss | 56 +++++++++++++++++ app/lib/permalink_redirector.rb | 63 +++++++++++-------- app/views/redirects/show.html.haml | 8 +++ config/locales/en.yml | 3 + config/routes.rb | 5 ++ 9 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 app/controllers/redirect/accounts_controller.rb create mode 100644 app/controllers/redirect/base_controller.rb create mode 100644 app/controllers/redirect/statuses_controller.rb create mode 100644 app/views/redirects/show.html.haml diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb index 5687d6e5b6..b8c909877b 100644 --- a/app/controllers/concerns/web_app_controller_concern.rb +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -21,10 +21,19 @@ module WebAppControllerConcern def redirect_unauthenticated_to_permalinks! return if user_signed_in? && current_account.moved_to_account_id.nil? - redirect_path = PermalinkRedirector.new(request.path).redirect_path - return if redirect_path.blank? + permalink_redirector = PermalinkRedirector.new(request.path) + return if permalink_redirector.redirect_path.blank? expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in? - redirect_to(redirect_path) + + respond_to do |format| + format.html do + redirect_to(permalink_redirector.redirect_confirmation_path, allow_other_host: false) + end + + format.json do + redirect_to(permalink_redirector.redirect_uri, allow_other_host: true) + end + end end end diff --git a/app/controllers/redirect/accounts_controller.rb b/app/controllers/redirect/accounts_controller.rb new file mode 100644 index 0000000000..98d2cc2b1f --- /dev/null +++ b/app/controllers/redirect/accounts_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Redirect::AccountsController < ApplicationController + private + + def set_resource + @resource = Account.find(params[:id]) + not_found if @resource.local? + end +end diff --git a/app/controllers/redirect/base_controller.rb b/app/controllers/redirect/base_controller.rb new file mode 100644 index 0000000000..90894ec1ed --- /dev/null +++ b/app/controllers/redirect/base_controller.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Redirect::BaseController < ApplicationController + vary_by 'Accept-Language' + + before_action :set_resource + before_action :set_app_body_class + + def show + @redirect_path = ActivityPub::TagManager.instance.url_for(@resource) + + render 'redirects/show', layout: 'application' + end + + private + + def set_app_body_class + @body_classes = 'app-body' + end + + def set_resource + raise NotImplementedError + end +end diff --git a/app/controllers/redirect/statuses_controller.rb b/app/controllers/redirect/statuses_controller.rb new file mode 100644 index 0000000000..37a938c651 --- /dev/null +++ b/app/controllers/redirect/statuses_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Redirect::StatusesController < Redirect::BaseController + private + + def set_resource + @resource = Status.find(params[:id]) + not_found if @resource.local? || !@resource.distributable? + end +end diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 3d646da239..b6e995787d 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -104,3 +104,59 @@ margin-inline-start: 10px; } } + +.redirect { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + font-size: 14px; + line-height: 18px; + + &__logo { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 30px; + + img { + height: 48px; + } + } + + &__message { + text-align: center; + + h1 { + font-size: 17px; + line-height: 22px; + font-weight: 700; + margin-bottom: 30px; + } + + p { + margin-bottom: 30px; + + &:last-child { + margin-bottom: 0; + } + } + + a { + color: $highlight-text-color; + font-weight: 500; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + } + + &__link { + margin-top: 15px; + } +} diff --git a/app/lib/permalink_redirector.rb b/app/lib/permalink_redirector.rb index 0dd37483e2..f551f69db8 100644 --- a/app/lib/permalink_redirector.rb +++ b/app/lib/permalink_redirector.rb @@ -5,17 +5,46 @@ class PermalinkRedirector def initialize(path) @path = path + @object = nil + end + + def object + @object ||= begin + if at_username_status_request? || statuses_status_request? + status = Status.find_by(id: second_segment) + status if status&.distributable? && !status&.local? + elsif at_username_request? + username, domain = first_segment.delete_prefix('@').split('@') + domain = nil if TagManager.instance.local_domain?(domain) + account = Account.find_remote(username, domain) + account unless account&.local? + elsif accounts_request? && record_integer_id_request? + account = Account.find_by(id: second_segment) + account unless account&.local? + end + end end def redirect_path - if at_username_status_request? || statuses_status_request? - find_status_url_by_id(second_segment) - elsif at_username_request? - find_account_url_by_name(first_segment) - elsif accounts_request? && record_integer_id_request? - find_account_url_by_id(second_segment) - elsif @path.start_with?('/deck') - @path.delete_prefix('/deck') + return ActivityPub::TagManager.instance.url_for(object) if object.present? + + @path.delete_prefix('/deck') if @path.start_with?('/deck') + end + + def redirect_uri + return ActivityPub::TagManager.instance.uri_for(object) if object.present? + + @path.delete_prefix('/deck') if @path.start_with?('/deck') + end + + def redirect_confirmation_path + case object.class.name + when 'Account' + redirect_account_path(object.id) + when 'Status' + redirect_status_path(object.id) + else + @path.delete_prefix('/deck') if @path.start_with?('/deck') end end @@ -56,22 +85,4 @@ class PermalinkRedirector def path_segments @path_segments ||= @path.delete_prefix('/deck').delete_prefix('/').split('/') end - - def find_status_url_by_id(id) - status = Status.find_by(id: id) - ActivityPub::TagManager.instance.url_for(status) if status&.distributable? && !status.account.local? - end - - def find_account_url_by_id(id) - account = Account.find_by(id: id) - ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? - end - - def find_account_url_by_name(name) - username, domain = name.gsub(/\A@/, '').split('@') - domain = nil if TagManager.instance.local_domain?(domain) - account = Account.find_remote(username, domain) - - ActivityPub::TagManager.instance.url_for(account) if account.present? && !account.local? - end end diff --git a/app/views/redirects/show.html.haml b/app/views/redirects/show.html.haml new file mode 100644 index 0000000000..0d09387a9c --- /dev/null +++ b/app/views/redirects/show.html.haml @@ -0,0 +1,8 @@ +.redirect + .redirect__logo + = link_to render_logo, root_path + + .redirect__message + %h1= t('redirects.title', instance: site_hostname) + %p= t('redirects.prompt') + %p= link_to @redirect_path, @redirect_path, rel: 'noreferrer noopener' diff --git a/config/locales/en.yml b/config/locales/en.yml index 83eaaa4552..9d739be07f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1547,6 +1547,9 @@ en: errors: limit_reached: Limit of different reactions reached unrecognized_emoji: is not a recognized emoji + redirects: + prompt: If you trust this link, click it to continue. + title: You are leaving %{instance}. relationships: activity: Account activity confirm_follow_selected_followers: Are you sure you want to follow selected followers? diff --git a/config/routes.rb b/config/routes.rb index 85c3b18556..c4f862acaf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -162,6 +162,11 @@ Rails.application.routes.draw do end end + namespace :redirect do + resources :accounts, only: :show + resources :statuses, only: :show + end + resources :media, only: [:show] do get :player end From 41c2af22705d9a8b265f77dae5da960d2eef2150 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 12:50:41 +0100 Subject: [PATCH 09/18] chore(deps): update dependency rubocop to v1.60.1 (#28731) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 93931d8724..54955000b1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -504,7 +504,7 @@ GEM orm_adapter (0.5.0) ox (2.14.17) parallel (1.24.0) - parser (3.2.2.4) + parser (3.3.0.5) ast (~> 2.4.1) racc parslet (2.0.0) @@ -610,7 +610,7 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.8.3) + regexp_parser (2.9.0) reline (0.4.2) io-console (~> 0.5) request_store (1.5.1) @@ -650,11 +650,11 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.12.1) - rubocop (1.59.0) + rubocop (1.60.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) From 1290fede651b47585de7dbfe4a9b118dc9d59856 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 06:51:09 -0500 Subject: [PATCH 10/18] Fix `Rails/WhereExists` cop in app/lib (#28862) --- .rubocop_todo.yml | 4 ---- app/lib/activitypub/activity/create.rb | 4 ++-- app/lib/delivery_failure_tracker.rb | 2 +- app/lib/feed_manager.rb | 6 +++--- app/lib/suspicious_sign_in_detector.rb | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b62dfa72a8..302c66a16a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -77,10 +77,6 @@ Rails/WhereExists: Exclude: - 'app/controllers/activitypub/inboxes_controller.rb' - 'app/controllers/admin/email_domain_blocks_controller.rb' - - 'app/lib/activitypub/activity/create.rb' - - 'app/lib/delivery_failure_tracker.rb' - - 'app/lib/feed_manager.rb' - - 'app/lib/suspicious_sign_in_detector.rb' - 'app/policies/status_policy.rb' - 'app/serializers/rest/announcement_serializer.rb' - 'app/workers/move_worker.rb' diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 5a2d33c1fa..62c35d4dd3 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -320,7 +320,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity already_voted = true with_redis_lock("vote:#{replied_to_status.poll_id}:#{@account.id}") do - already_voted = poll.votes.where(account: @account).exists? + already_voted = poll.votes.exists?(account: @account) poll.votes.create!(account: @account, choice: poll.options.index(@object['name']), uri: object_uri) end @@ -406,7 +406,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return false if local_usernames.empty? - Account.local.where(username: local_usernames).exists? + Account.local.exists?(username: local_usernames) end def tombstone_exists? diff --git a/app/lib/delivery_failure_tracker.rb b/app/lib/delivery_failure_tracker.rb index d938269829..e17b45d667 100644 --- a/app/lib/delivery_failure_tracker.rb +++ b/app/lib/delivery_failure_tracker.rb @@ -28,7 +28,7 @@ class DeliveryFailureTracker end def available? - !UnavailableDomain.where(domain: @host).exists? + !UnavailableDomain.exists?(domain: @host) end def exhausted_deliveries_days diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 53767486ff..38a177e645 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -420,8 +420,8 @@ class FeedManager check_for_blocks = status.active_mentions.pluck(:account_id) check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil? - should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) - should_filter ||= status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists? # of if the account is silenced and I'm not following them + should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) + should_filter ||= status.account.silenced? && !Follow.exists?(account_id: receiver_id, target_account_id: status.account_id) # Filter if the account is silenced and I'm not following them should_filter end @@ -434,7 +434,7 @@ class FeedManager if status.reply? && status.in_reply_to_account_id != status.account_id should_filter = status.in_reply_to_account_id != list.account_id should_filter &&= !list.show_followed? - should_filter &&= !(list.show_list? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?) + should_filter &&= !(list.show_list? && ListAccount.exists?(list_id: list.id, account_id: status.in_reply_to_account_id)) return !!should_filter end diff --git a/app/lib/suspicious_sign_in_detector.rb b/app/lib/suspicious_sign_in_detector.rb index 1af5188c65..74f49aa558 100644 --- a/app/lib/suspicious_sign_in_detector.rb +++ b/app/lib/suspicious_sign_in_detector.rb @@ -19,7 +19,7 @@ class SuspiciousSignInDetector end def previously_seen_ip?(request) - @user.ips.where('ip <<= ?', masked_ip(request)).exists? + @user.ips.exists?(['ip <<= ?', masked_ip(request)]) end def freshly_signed_up? From 5a838ceaa9a003bc2e2fdee727d4aa87cd53de4f Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 24 Jan 2024 13:37:43 +0100 Subject: [PATCH 11/18] Use active variants for boost icons and increase icon size (#27924) --- app/javascript/mastodon/components/status_action_bar.jsx | 4 +++- .../mastodon/features/status/components/action_bar.jsx | 4 +++- app/javascript/svg-icons/repeat_active.svg | 4 ++++ app/javascript/svg-icons/repeat_disabled.svg | 0 app/javascript/svg-icons/repeat_private.svg | 0 app/javascript/svg-icons/repeat_private_active.svg | 6 ++++++ 6 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 app/javascript/svg-icons/repeat_active.svg mode change 100755 => 100644 app/javascript/svg-icons/repeat_disabled.svg mode change 100755 => 100644 app/javascript/svg-icons/repeat_private.svg create mode 100644 app/javascript/svg-icons/repeat_private_active.svg diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index bfe77a4900..b111a65385 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -18,8 +18,10 @@ import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -366,7 +368,7 @@ class StatusActionBar extends ImmutablePureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 4cb06aac2c..c243a49129 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -17,8 +17,10 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -296,7 +298,7 @@ class ActionBar extends PureComponent { if (status.get('reblogged')) { reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon; + reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; } else if (publicStatus) { reblogTitle = intl.formatMessage(messages.reblog); reblogIconComponent = RepeatIcon; diff --git a/app/javascript/svg-icons/repeat_active.svg b/app/javascript/svg-icons/repeat_active.svg new file mode 100644 index 0000000000..a5bbb8fc4f --- /dev/null +++ b/app/javascript/svg-icons/repeat_active.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/javascript/svg-icons/repeat_disabled.svg b/app/javascript/svg-icons/repeat_disabled.svg old mode 100755 new mode 100644 diff --git a/app/javascript/svg-icons/repeat_private.svg b/app/javascript/svg-icons/repeat_private.svg old mode 100755 new mode 100644 diff --git a/app/javascript/svg-icons/repeat_private_active.svg b/app/javascript/svg-icons/repeat_private_active.svg new file mode 100644 index 0000000000..cf2a05c84e --- /dev/null +++ b/app/javascript/svg-icons/repeat_private_active.svg @@ -0,0 +1,6 @@ + + + + + + From 64993d3f779a6e01c104e1a2024a87c9785bc79d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:37:37 +0100 Subject: [PATCH 12/18] chore(deps): update dependency haml_lint to v0.55.0 (#28856) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 54955000b1..97d2b5f7a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -319,7 +319,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.53.0) + haml_lint (0.55.0) haml (>= 5.0) parallel (~> 1.10) rainbow From ea5397c3735145ad62374798506f57afa122959d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 15:55:06 +0100 Subject: [PATCH 13/18] chore(deps): update dependency selenium-webdriver to v4.17.0 (#28858) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 97d2b5f7a0..77d924f948 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -696,7 +696,8 @@ GEM scenic (1.7.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - selenium-webdriver (4.16.0) + selenium-webdriver (4.17.0) + base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) From 559bbf0aa6ceca1cbf417fcc76ea7ef359e42099 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:51:18 +0100 Subject: [PATCH 14/18] chore(deps): update artifact actions (major) to v4 (major) (#28415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test-ruby.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index ae25648a0b..346703ced4 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -52,7 +52,7 @@ jobs: run: | tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: matrix.mode == 'test' with: path: |- @@ -117,7 +117,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './' name: ${{ github.sha }} @@ -193,7 +193,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './public' name: ${{ github.sha }} @@ -213,14 +213,14 @@ jobs: - run: bundle exec rake spec:system - name: Archive logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: e2e-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: e2e-screenshots @@ -297,7 +297,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './public' name: ${{ github.sha }} @@ -317,14 +317,14 @@ jobs: - run: bin/rspec --tag search - name: Archive logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: test-search-logs-${{ matrix.ruby-version }} path: log/ - name: Archive test screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: test-search-screenshots From 7019af431d2b91a4d31ede9558bb3a17c3875f37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:51:36 +0100 Subject: [PATCH 15/18] fix(deps): update dependency dotenv to v16.4.0 (#28872) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 61f699d19e..53d2caaed9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6901,9 +6901,9 @@ __metadata: linkType: hard "dotenv@npm:^16.0.3": - version: 16.3.2 - resolution: "dotenv@npm:16.3.2" - checksum: a87d62cef0810b670cb477db1a24a42a093b6b428c9e65c185ce1d6368ad7175234b13547718ba08da18df43faae4f814180cc0366e11be1ded2277abc4dd22e + version: 16.4.0 + resolution: "dotenv@npm:16.4.0" + checksum: 70c3b422cefaffdba300aecd9157668590c3b5e66efb3742b7dec207f85023e5997364f04030fc0393fae52bf3a874979632d289ab4fafc1386ff2c68f2f2e8d languageName: node linkType: hard From 9c5be139806ba323f760ceb24476c1261af2ed41 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 17:51:47 +0100 Subject: [PATCH 16/18] chore(deps): update dependency chewy to v7.5.0 (#28730) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 77d924f948..d573debe4b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -180,7 +180,7 @@ GEM activesupport cbor (0.5.9.6) charlock_holmes (0.7.7) - chewy (7.4.0) + chewy (7.5.0) activesupport (>= 5.2) elasticsearch (>= 7.12.0, < 7.14.0) elasticsearch-dsl @@ -445,7 +445,7 @@ GEM mime-types-data (3.2023.1205) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.20.0) + minitest (5.21.1) msgpack (1.7.2) multi_json (1.15.0) multipart-post (2.3.0) From 38f7f8b9096a3c56c676942730184f01e17bd93d Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 24 Jan 2024 12:30:28 -0500 Subject: [PATCH 17/18] Tidy up association declaration in `Instance` model (#28880) --- app/models/instance.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/instance.rb b/app/models/instance.rb index 8f8d87c62a..2dec75d6fe 100644 --- a/app/models/instance.rb +++ b/app/models/instance.rb @@ -13,12 +13,12 @@ class Instance < ApplicationRecord attr_accessor :failure_days - has_many :accounts, foreign_key: :domain, primary_key: :domain, inverse_of: false - with_options foreign_key: :domain, primary_key: :domain, inverse_of: false do belongs_to :domain_block belongs_to :domain_allow - belongs_to :unavailable_domain # skipcq: RB-RL1031 + belongs_to :unavailable_domain + + has_many :accounts, dependent: nil end scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) } From 85bf6a91d0e84581165408d0e63302d79e76ffac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Thu, 25 Jan 2024 19:30:11 +0900 Subject: [PATCH 18/18] Fix test --- .github/workflows/test-ruby.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index f7e34800d7..fcb637d4b0 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -407,7 +407,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: path: './' name: ${{ github.sha }}