about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb19
-rw-r--r--app/models/account_filter.rb2
-rw-r--r--app/models/admin/status_batch_action.rb16
-rw-r--r--app/models/concerns/account_interactions.rb4
-rw-r--r--app/models/concerns/omniauthable.rb9
-rw-r--r--app/models/form/admin_settings.rb20
-rw-r--r--app/models/media_attachment.rb2
-rw-r--r--app/models/relay.rb5
-rw-r--r--app/models/status.rb31
-rw-r--r--app/models/tag.rb22
-rw-r--r--app/models/user.rb17
-rw-r--r--app/models/webhook.rb1
12 files changed, 104 insertions, 44 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 6b3bd3af5..d9511999b 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -339,9 +339,15 @@ class Account < ApplicationRecord
 
   def save_with_optional_media!
     save!
-  rescue ActiveRecord::RecordInvalid
-    self.avatar = nil
-    self.header = nil
+  rescue ActiveRecord::RecordInvalid => e
+    errors = e.record.errors.errors
+    errors.each do |err|
+      if err.attribute == :avatar
+        self.avatar = nil
+      elsif err.attribute == :header
+        self.header = nil
+      end
+    end
 
     save!
   end
@@ -498,7 +504,8 @@ class Account < ApplicationRecord
         <<-SQL.squish
           SELECT
             accounts.*,
-            (count(f.id) + 1) * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank
+            (count(f.id) + 1) * ts_rank_cd(#{TEXTSEARCH}, to_tsquery('simple', :tsquery), 32) AS rank,
+            count(f.id) AS followed
           FROM accounts
           LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = :id) OR (accounts.id = f.target_account_id AND f.account_id = :id)
           LEFT JOIN users ON accounts.id = users.account_id
@@ -506,8 +513,8 @@ class Account < ApplicationRecord
             AND accounts.suspended_at IS NULL
             AND accounts.moved_to_account_id IS NULL
             AND (accounts.domain IS NOT NULL OR (users.approved = TRUE AND users.confirmed_at IS NOT NULL))
-          GROUP BY accounts.id
-          ORDER BY rank DESC
+          GROUP BY accounts.id, s.id
+          ORDER BY followed DESC, rank DESC
           LIMIT :limit OFFSET :offset
         SQL
       end
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index 3a4ac0492..d27bb46fc 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -81,7 +81,7 @@ class AccountFilter
     when 'suspended'
       Account.suspended
     when 'disabled'
-      accounts_with_users.merge(User.disabled)
+      accounts_with_users.merge(User.disabled).without_suspended
     when 'silenced'
       Account.silenced
     when 'sensitized'
diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb
index 0f019b854..39cd7d0eb 100644
--- a/app/models/admin/status_batch_action.rb
+++ b/app/models/admin/status_batch_action.rb
@@ -73,7 +73,7 @@ class Admin::StatusBatchAction
     # Can't use a transaction here because UpdateStatusService queues
     # Sidekiq jobs
     statuses.includes(:media_attachments, :preview_cards).find_each do |status|
-      next unless status.with_media? || status.with_preview_card?
+      next if status.discarded? || !(status.with_media? || status.with_preview_card?)
 
       authorize([:admin, status], :update?)
 
@@ -89,15 +89,15 @@ class Admin::StatusBatchAction
         report.resolve!(current_account)
         log_action(:resolve, report)
       end
-
-      @warning = target_account.strikes.create!(
-        action: :mark_statuses_as_sensitive,
-        account: current_account,
-        report: report,
-        status_ids: status_ids
-      )
     end
 
+    @warning = target_account.strikes.create!(
+      action: :mark_statuses_as_sensitive,
+      account: current_account,
+      report: report,
+      status_ids: status_ids
+    )
+
     UserMailer.warning(target_account.user, @warning).deliver_later! if warnable?
   end
 
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index 15c49f2fe..de8bf338f 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -44,6 +44,10 @@ module AccountInteractions
       end
     end
 
+    def requested_by_map(target_account_ids, account_id)
+      follow_mapping(FollowRequest.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
+    end
+
     def endorsed_map(target_account_ids, account_id)
       follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id)
     end
diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb
index a90d5d888..feac0a1f5 100644
--- a/app/models/concerns/omniauthable.rb
+++ b/app/models/concerns/omniauthable.rb
@@ -55,7 +55,14 @@ module Omniauthable
 
       user = User.new(user_params_from_auth(email, auth))
 
-      user.account.avatar_remote_url = auth.info.image if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image)
+      begin
+        if /\A#{URI::DEFAULT_PARSER.make_regexp(%w(http https))}\z/.match?(auth.info.image)
+          user.account.avatar_remote_url = auth.info.image
+        end
+      rescue Mastodon::UnexpectedResponseError
+        user.account.avatar_remote_url = nil
+      end
+
       user.skip_confirmation! if email_is_verified
       user.save!
       user
diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb
index b53a82db2..b595529f8 100644
--- a/app/models/form/admin_settings.rb
+++ b/app/models/form/admin_settings.rb
@@ -83,6 +83,7 @@ class Form::AdminSettings
   validates :show_domain_blocks_rationale, inclusion: { in: %w(disabled users all) }, if: -> { defined?(@show_domain_blocks_rationale) }
   validates :media_cache_retention_period, :content_cache_retention_period, :backups_retention_period, numericality: { only_integer: true }, allow_blank: true, if: -> { defined?(@media_cache_retention_period) || defined?(@content_cache_retention_period) || defined?(@backups_retention_period) }
   validates :site_short_description, length: { maximum: 200 }, if: -> { defined?(@site_short_description) }
+  validate :validate_site_uploads
 
   KEYS.each do |key|
     define_method(key) do
@@ -104,11 +105,16 @@ class Form::AdminSettings
     define_method("#{key}=") do |file|
       value = public_send(key)
       value.file = file
+    rescue Mastodon::DimensionsValidationError => e
+      errors.add(key.to_sym, e.message)
     end
   end
 
   def save
-    return false unless valid?
+    # NOTE: Annoyingly, files are processed and can error out before
+    # validations are called, and `valid?` clears errors…
+    # So for now, return early if errors aren't empty.
+    return false unless errors.empty? && valid?
 
     KEYS.each do |key|
       next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?("@#{key}")
@@ -141,4 +147,16 @@ class Form::AdminSettings
       value
     end
   end
+
+  def validate_site_uploads
+    UPLOAD_KEYS.each do |key|
+      next unless instance_variable_defined?("@#{key}")
+      upload = instance_variable_get("@#{key}")
+      next if upload.valid?
+
+      upload.errors.each do |error|
+        errors.import(error, attribute: key)
+      end
+    end
+  end
 end
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index f2b34e4cd..4dd3042ab 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -210,6 +210,8 @@ class MediaAttachment < ApplicationRecord
 
   default_scope { order(id: :asc) }
 
+  attr_accessor :skip_download
+
   def local?
     remote_url.blank?
   end
diff --git a/app/models/relay.rb b/app/models/relay.rb
index d6ddd30ed..c66bfe4ff 100644
--- a/app/models/relay.rb
+++ b/app/models/relay.rb
@@ -18,6 +18,7 @@ class Relay < ApplicationRecord
 
   scope :enabled, -> { accepted }
 
+  before_validation :strip_url
   before_destroy :ensure_disabled
 
   alias enabled? accepted?
@@ -74,4 +75,8 @@ class Relay < ApplicationRecord
   def ensure_disabled
     disable! if enabled?
   end
+
+  def strip_url
+    inbox_url&.strip!
+  end
 end
diff --git a/app/models/status.rb b/app/models/status.rb
index 6cfe19d23..14b7a39fe 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -31,7 +31,7 @@
 #
 
 class Status < ApplicationRecord
-  before_destroy :unlink_from_conversations
+  before_destroy :unlink_from_conversations!
 
   include Discard::Model
   include Paginable
@@ -314,15 +314,14 @@ class Status < ApplicationRecord
   after_create_commit :store_uri, if: :local?
   after_create_commit :update_statistics, if: :local?
 
-  around_create Mastodon::Snowflake::Callbacks
-
-  before_create :set_locality
-
   before_validation :prepare_contents, if: :local?
   before_validation :set_reblog
   before_validation :set_visibility
   before_validation :set_conversation
   before_validation :set_local
+  before_create :set_locality
+
+  around_create Mastodon::Snowflake::Callbacks
 
   after_create :set_poll_id
 
@@ -504,6 +503,17 @@ class Status < ApplicationRecord
     update_attribute(:deleted_at, discard_time)
   end
 
+  def unlink_from_conversations!
+    return unless direct_visibility?
+
+    inbox_owners = mentioned_accounts.local
+    inbox_owners += [account] if account.local?
+
+    inbox_owners.each do |inbox_owner|
+      AccountConversation.remove_status(inbox_owner, self)
+    end
+  end
+
   private
 
   def update_status_stat!(attrs)
@@ -587,15 +597,4 @@ class Status < ApplicationRecord
     reblog&.decrement_count!(:reblogs_count) if reblog?
     thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && distributable?
   end
-
-  def unlink_from_conversations
-    return unless direct_visibility?
-
-    inbox_owners = mentioned_accounts.local
-    inbox_owners += [account] if account.local?
-
-    inbox_owners.each do |inbox_owner|
-      AccountConversation.remove_status(inbox_owner, self)
-    end
-  end
 end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index b66f85423..47a05d00a 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -26,8 +26,12 @@ class Tag < ApplicationRecord
   has_many :featured_tags, dependent: :destroy, inverse_of: :tag
   has_many :followers, through: :passive_relationships, source: :account
 
-  HASHTAG_SEPARATORS = "_\u00B7\u200c"
-  HASHTAG_NAME_PAT = "([[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}][[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_])|([[:word:]_]*[[:alpha:]][[:word:]_]*)"
+  HASHTAG_SEPARATORS = "_\u00B7\u30FB\u200c"
+  HASHTAG_FIRST_SEQUENCE_CHUNK_ONE = "[[:word:]_][[:word:]#{HASHTAG_SEPARATORS}]*[[:alpha:]#{HASHTAG_SEPARATORS}]"
+  HASHTAG_FIRST_SEQUENCE_CHUNK_TWO = "[[:word:]#{HASHTAG_SEPARATORS}]*[[:word:]_]"
+  HASHTAG_FIRST_SEQUENCE = "(#{HASHTAG_FIRST_SEQUENCE_CHUNK_ONE}#{HASHTAG_FIRST_SEQUENCE_CHUNK_TWO})"
+  HASTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)'
+  HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASTAG_LAST_SEQUENCE}"
 
   HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_PAT})/i
   HASHTAG_NAME_RE = /\A(#{HASHTAG_NAME_PAT})\z/i
@@ -45,7 +49,11 @@ class Tag < ApplicationRecord
   scope :listable, -> { where(listable: [true, nil]) }
   scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) }
   scope :not_trendable, -> { where(trendable: false) }
-  scope :recently_used, ->(account) { joins(:statuses).where(statuses: { id: account.statuses.select(:id).limit(1000) }).group(:id).order(Arel.sql('count(*) desc')) }
+  scope :recently_used, ->(account) {
+                          joins(:statuses)
+                            .where(statuses: { id: account.statuses.select(:id).limit(1000) })
+                            .group(:id).order(Arel.sql('count(*) desc'))
+                        }
   scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
 
   update_index('tags', :self)
@@ -105,7 +113,8 @@ class Tag < ApplicationRecord
       names = Array(name_or_names).map { |str| [normalize(str), str] }.uniq(&:first)
 
       names.map do |(normalized_name, display_name)|
-        tag = matching_name(normalized_name).first || create(name: normalized_name, display_name: display_name.gsub(HASHTAG_INVALID_CHARS_RE, ''))
+        tag = matching_name(normalized_name).first || create(name: normalized_name,
+                                                             display_name: display_name.gsub(HASHTAG_INVALID_CHARS_RE, ''))
 
         yield tag if block_given?
 
@@ -154,6 +163,9 @@ class Tag < ApplicationRecord
   end
 
   def validate_display_name_change
-    errors.add(:display_name, I18n.t('tags.does_not_match_previous_name')) unless HashtagNormalizer.new.normalize(display_name).casecmp(name.mb_chars).zero?
+    unless HashtagNormalizer.new.normalize(display_name).casecmp(name.mb_chars).zero?
+      errors.add(:display_name,
+                 I18n.t('tags.does_not_match_previous_name'))
+    end
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 209bfa521..2e3c067ec 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -386,6 +386,15 @@ class User < ApplicationRecord
     super
   end
 
+  def revoke_access!
+    Doorkeeper::AccessGrant.by_resource_owner(self).update_all(revoked_at: Time.now.utc)
+
+    Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
+      batch.update_all(revoked_at: Time.now.utc)
+      Web::PushSubscription.where(access_token_id: batch).delete_all
+    end
+  end
+
   def reset_password!
     # First, change password to something random and deactivate all sessions
     transaction do
@@ -394,12 +403,7 @@ class User < ApplicationRecord
     end
 
     # Then, remove all authorized applications and connected push subscriptions
-    Doorkeeper::AccessGrant.by_resource_owner(self).in_batches.update_all(revoked_at: Time.now.utc)
-
-    Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
-      batch.update_all(revoked_at: Time.now.utc)
-      Web::PushSubscription.where(access_token_id: batch).delete_all
-    end
+    revoke_access!
 
     # Finally, send a reset password prompt to the user
     send_reset_password_instructions
@@ -494,6 +498,7 @@ class User < ApplicationRecord
     BootstrapTimelineWorker.perform_async(account_id)
     ActivityTracker.increment('activity:accounts:local')
     UserMailer.welcome(self).deliver_later
+    TriggerWebhookWorker.perform_async('account.approved', 'Account', account_id)
   end
 
   def prepare_returning_user!
diff --git a/app/models/webhook.rb b/app/models/webhook.rb
index 431edd75d..4aafb1257 100644
--- a/app/models/webhook.rb
+++ b/app/models/webhook.rb
@@ -15,6 +15,7 @@
 
 class Webhook < ApplicationRecord
   EVENTS = %w(
+    account.approved
     account.created
     report.created
   ).freeze