about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
authorFire Demon <firedemon@creature.cafe>2020-09-30 12:25:13 -0500
committerFire Demon <firedemon@creature.cafe>2020-09-30 12:25:13 -0500
commit22e7a7947900cc974715b0c9d1d6b1a44d2ed1eb (patch)
tree918ab95c4f465c9c1fbfcd6706483e3310a38ce5 /app/models
parent63046c8cb9df8e797d53566f2050313d757b089b (diff)
parentb5edf30160eab3776e44b34325a4ea00d9f71dc5 (diff)
Merge remote-tracking branch 'upstream/master' into merge-glitch
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb9
-rw-r--r--app/models/account_conversation.rb5
-rw-r--r--app/models/account_deletion_request.rb20
-rw-r--r--app/models/admin/account_action.rb4
-rw-r--r--app/models/concerns/account_associations.rb3
-rw-r--r--app/models/concerns/account_interactions.rb26
-rw-r--r--app/models/concerns/paginable.rb5
-rw-r--r--app/models/feed.rb4
-rw-r--r--app/models/follow.rb3
-rw-r--r--app/models/follow_request.rb3
-rw-r--r--app/models/form/account_batch.rb2
-rw-r--r--app/models/invite.rb2
-rw-r--r--app/models/notification.rb47
-rw-r--r--app/models/status.rb17
-rw-r--r--app/models/tag_feed.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/models/webauthn_credential.rb2
17 files changed, 101 insertions, 57 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index c7bf7bf80..228e36755 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -233,23 +233,20 @@ class Account < ApplicationRecord
 
   def suspend!(date = Time.now.utc)
     transaction do
-      user&.disable! if local?
+      create_deletion_request!
       update!(suspended_at: date)
     end
   end
 
   def unsuspend!
     transaction do
-      user&.enable! if local?
+      deletion_request&.destroy!
       update!(suspended_at: nil)
     end
   end
 
   def memorialize!
-    transaction do
-      user&.disable! if local?
-      update!(memorial: true)
-    end
+    update!(memorial: true)
   end
 
   def sign?
diff --git a/app/models/account_conversation.rb b/app/models/account_conversation.rb
index 5e2ddd083..56fd13543 100644
--- a/app/models/account_conversation.rb
+++ b/app/models/account_conversation.rb
@@ -38,15 +38,16 @@ class AccountConversation < ApplicationRecord
   class << self
     def to_a_paginated_by_id(limit, options = {})
       if options[:min_id]
-        paginate_by_min_id(limit, options[:min_id]).reverse
+        paginate_by_min_id(limit, options[:min_id], options[:max_id]).reverse
       else
         paginate_by_max_id(limit, options[:max_id], options[:since_id]).to_a
       end
     end
 
-    def paginate_by_min_id(limit, min_id = nil)
+    def paginate_by_min_id(limit, min_id = nil, max_id = nil)
       query = order(arel_table[:last_status_id].asc).limit(limit)
       query = query.where(arel_table[:last_status_id].gt(min_id)) if min_id.present?
+      query = query.where(arel_table[:last_status_id].lt(max_id)) if max_id.present?
       query
     end
 
diff --git a/app/models/account_deletion_request.rb b/app/models/account_deletion_request.rb
new file mode 100644
index 000000000..7d0c346cc
--- /dev/null
+++ b/app/models/account_deletion_request.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: account_deletion_requests
+#
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)
+#  created_at :datetime         not null
+#  updated_at :datetime         not null
+#
+class AccountDeletionRequest < ApplicationRecord
+  DELAY_TO_DELETION = 30.days.freeze
+
+  belongs_to :account
+
+  def due_at
+    created_at + DELAY_TO_DELETION
+  end
+end
diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb
index b30a82369..c4ac09520 100644
--- a/app/models/admin/account_action.rb
+++ b/app/models/admin/account_action.rb
@@ -134,7 +134,7 @@ class Admin::AccountAction
   end
 
   def process_email!
-    UserMailer.warning(target_account.user, warning, status_ids).deliver_now! if warnable?
+    UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable?
   end
 
   def warnable?
@@ -142,7 +142,7 @@ class Admin::AccountAction
   end
 
   def status_ids
-    @report.status_ids if @report && include_statuses
+    report.status_ids if report && include_statuses
   end
 
   def reports
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index 3b5e106fb..ec4e18699 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -61,6 +61,9 @@ module AccountAssociations
     has_and_belongs_to_many :tags
     has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
 
+    # Account deletion requests
+    has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
+
     # Domain permissions
     has_many :domain_permissions, class_name: 'AccountDomainPermission', inverse_of: :account, dependent: :destroy
 
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index cf8e638e7..184064e94 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -8,6 +8,7 @@ module AccountInteractions
       Follow.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow, mapping|
         mapping[follow.target_account_id] = {
           reblogs: follow.show_reblogs?,
+          notify: follow.notify?,
         }
       end
     end
@@ -36,6 +37,7 @@ module AccountInteractions
       FollowRequest.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |follow_request, mapping|
         mapping[follow_request.target_account_id] = {
           reblogs: follow_request.show_reblogs?,
+          notify: follow_request.notify?,
         }
       end
     end
@@ -96,25 +98,29 @@ module AccountInteractions
     has_many :status_mutes, inverse_of: :account, dependent: :destroy
   end
 
-  def follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
-    reblogs = true if reblogs.nil?
-
-    rel = active_relationships.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)
+  def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false)
+    rel = active_relationships.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit)
                               .find_or_create_by!(target_account: other_account)
 
-    rel.update!(show_reblogs: reblogs)
+    rel.show_reblogs = reblogs unless reblogs.nil?
+    rel.notify       = notify  unless notify.nil?
+
+    rel.save! if rel.changed?
+
     remove_potential_friendship(other_account)
 
     rel
   end
 
-  def request_follow!(other_account, reblogs: nil, uri: nil, rate_limit: false)
-    reblogs = true if reblogs.nil?
-
-    rel = follow_requests.create_with(show_reblogs: reblogs, uri: uri, rate_limit: rate_limit)
+  def request_follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false)
+    rel = follow_requests.create_with(show_reblogs: reblogs.nil? ? true : reblogs, notify: notify.nil? ? false : notify, uri: uri, rate_limit: rate_limit)
                          .find_or_create_by!(target_account: other_account)
 
-    rel.update!(show_reblogs: reblogs)
+    rel.show_reblogs = reblogs unless reblogs.nil?
+    rel.notify       = notify  unless notify.nil?
+
+    rel.save! if rel.changed?
+
     remove_potential_friendship(other_account)
 
     rel
diff --git a/app/models/concerns/paginable.rb b/app/models/concerns/paginable.rb
index 760cc3df4..62e39f671 100644
--- a/app/models/concerns/paginable.rb
+++ b/app/models/concerns/paginable.rb
@@ -14,15 +14,16 @@ module Paginable
     # Differs from :paginate_by_max_id in that it gives the results immediately following min_id,
     # whereas since_id gives the items with largest id, but with since_id as a cutoff.
     # Results will be in ascending order by id.
-    scope :paginate_by_min_id, ->(limit, min_id = nil) {
+    scope :paginate_by_min_id, ->(limit, min_id = nil, max_id = nil) {
       query = reorder(arel_table[:id]).limit(limit)
       query = query.where(arel_table[:id].gt(min_id)) if min_id.present?
+      query = query.where(arel_table[:id].lt(max_id)) if max_id.present?
       query
     }
 
     def self.to_a_paginated_by_id(limit, options = {})
       if options[:min_id].present?
-        paginate_by_min_id(limit, options[:min_id]).reverse
+        paginate_by_min_id(limit, options[:min_id], options[:max_id]).reverse
       else
         paginate_by_max_id(limit, options[:max_id], options[:since_id]).to_a
       end
diff --git a/app/models/feed.rb b/app/models/feed.rb
index 36e0c1e0a..f51dcfab1 100644
--- a/app/models/feed.rb
+++ b/app/models/feed.rb
@@ -20,12 +20,12 @@ class Feed
   protected
 
   def from_redis(limit, max_id, since_id, min_id)
+    max_id = '+inf' if max_id.blank?
     if min_id.blank?
-      max_id     = '+inf' if max_id.blank?
       since_id   = '-inf' if since_id.blank?
       unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
     else
-      unhydrated = redis.zrangebyscore(key, "(#{min_id}", '+inf', limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
+      unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
     end
 
     Status.where(id: unhydrated).cache_ids
diff --git a/app/models/follow.rb b/app/models/follow.rb
index f3e48a2ed..0b4ddbf3f 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -10,6 +10,7 @@
 #  target_account_id :bigint(8)        not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #  uri               :string
+#  notify            :boolean          default(FALSE), not null
 #
 
 class Follow < ApplicationRecord
@@ -34,7 +35,7 @@ class Follow < ApplicationRecord
   end
 
   def revoke_request!
-    FollowRequest.create!(account: account, target_account: target_account, show_reblogs: show_reblogs, uri: uri)
+    FollowRequest.create!(account: account, target_account: target_account, show_reblogs: show_reblogs, notify: notify, uri: uri)
     destroy!
   end
 
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index cdf0f4bda..5899a7f0e 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -10,6 +10,7 @@
 #  target_account_id :bigint(8)        not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #  uri               :string
+#  notify            :boolean          default(FALSE), not null
 #
 
 class FollowRequest < ApplicationRecord
@@ -28,7 +29,7 @@ class FollowRequest < ApplicationRecord
   validates_with FollowLimitValidator, on: :create
 
   def authorize!
-    account.follow!(target_account, reblogs: show_reblogs, uri: uri)
+    account.follow!(target_account, reblogs: show_reblogs, notify: notify, uri: uri)
     if account.local?
       MergeWorker.perform_async(target_account.id, account.id)
       ActivityPub::SyncAccountWorker.perform_async(target_account.id, every_page: true) unless target_account.local?
diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb
index 0b285fde9..7b9e40f68 100644
--- a/app/models/form/account_batch.rb
+++ b/app/models/form/account_batch.rb
@@ -69,6 +69,6 @@ class Form::AccountBatch
     records = accounts.includes(:user)
 
     records.each { |account| authorize(account.user, :reject?) }
-           .each { |account| SuspendAccountService.new.call(account, reserve_email: false, reserve_username: false) }
+           .each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) }
   end
 end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 4695b4ebb..d60866ad6 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -28,7 +28,7 @@ class Invite < ApplicationRecord
   before_validation :set_code
 
   def valid_for_use?
-    (max_uses.nil? || uses < max_uses) && !expired? && !(user.nil? || user.disabled?)
+    (max_uses.nil? || uses < max_uses) && !expired? && user&.functional?
   end
 
   private
diff --git a/app/models/notification.rb b/app/models/notification.rb
index ad7528f50..e83123c97 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -10,21 +10,34 @@
 #  updated_at      :datetime         not null
 #  account_id      :bigint(8)        not null
 #  from_account_id :bigint(8)        not null
+#  type            :string
 #
 
 class Notification < ApplicationRecord
+  self.inheritance_column = nil
+
   include Paginable
   include Cacheable
 
-  TYPE_CLASS_MAP = {
-    mention:        'Mention',
-    reblog:         'Status',
-    follow:         'Follow',
-    follow_request: 'FollowRequest',
-    favourite:      'Favourite',
-    poll:           'Poll',
+  LEGACY_TYPE_CLASS_MAP = {
+    'Mention'       => :mention,
+    'Status'        => :reblog,
+    'Follow'        => :follow,
+    'FollowRequest' => :follow_request,
+    'Favourite'     => :favourite,
+    'Poll'          => :poll,
   }.freeze
 
+  TYPES = %i(
+    mention
+    status
+    reblog
+    follow
+    follow_request
+    favourite
+    poll
+  ).freeze
+
   STATUS_INCLUDES = [:account, :application, :preloadable_poll, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, :tags, active_mentions: :account]].freeze
 
   belongs_to :account, optional: true
@@ -38,26 +51,30 @@ class Notification < ApplicationRecord
   belongs_to :favourite,      foreign_type: 'Favourite',     foreign_key: 'activity_id', optional: true
   belongs_to :poll,           foreign_type: 'Poll',          foreign_key: 'activity_id', optional: true
 
-  validates :account_id, uniqueness: { scope: [:activity_type, :activity_id] }
-  validates :activity_type, inclusion: { in: TYPE_CLASS_MAP.values }
+  validates :type, inclusion: { in: TYPES }
+
+  scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
 
   scope :browserable, ->(exclude_types = [], account_id = nil) {
-    types = TYPE_CLASS_MAP.values - activity_types_from_types(exclude_types)
+    types = TYPES - exclude_types.map(&:to_sym)
+
     if account_id.nil?
-      where(activity_type: types)
+      where(type: types)
     else
-      where(activity_type: types, from_account_id: account_id)
+      where(type: types, from_account_id: account_id)
     end
   }
 
   cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account, follow_request: :account, poll: [status: STATUS_INCLUDES]
 
   def type
-    @type ||= TYPE_CLASS_MAP.invert[activity_type].to_sym
+    @type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
   end
 
   def target_status
     case type
+    when :status
+      status
     when :reblog
       status&.reblog
     when :favourite
@@ -86,10 +103,6 @@ class Notification < ApplicationRecord
         item.target_status.account = accounts[item.target_status.account_id] if item.target_status
       end
     end
-
-    def activity_types_from_types(types)
-      types.map { |type| TYPE_CLASS_MAP[type.to_sym] }.compact
-    end
   end
 
   after_initialize :set_from_account
diff --git a/app/models/status.rb b/app/models/status.rb
index 8c9a74902..dd7fad4fd 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -22,6 +22,7 @@
 #  application_id         :bigint(8)
 #  in_reply_to_account_id :bigint(8)
 #  local_only             :boolean          default(FALSE), not null
+#  full_status_text       :text             default(""), not null
 #  poll_id                :bigint(8)
 #  content_type           :string
 #  deleted_at             :datetime
@@ -62,7 +63,7 @@ class Status < ApplicationRecord
   belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
 
   belongs_to :account, inverse_of: :statuses
-  belongs_to :in_reply_to_account, class_name: 'Account', optional: true
+  belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true
   belongs_to :conversation, optional: true
   belongs_to :preloadable_poll, class_name: 'Poll', foreign_key: 'poll_id', optional: true
 
@@ -111,19 +112,19 @@ class Status < ApplicationRecord
   scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
   scope :with_public_visibility, -> { where(visibility: :public, published: true) }
   scope :distributable, -> { where(visibility: [:public, :unlisted], published: true) }
-  scope :tagged_with, ->(tag) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag }) }
+  scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
   scope :in_chosen_languages, ->(account) { where(language: nil).or where(language: account.chosen_languages) }
   scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
   scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
   scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
   scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) }
-  scope :tagged_with_all, ->(tags) {
-    Array(tags).map(&:id).map(&:to_i).reduce(self) do |result, id|
+  scope :tagged_with_all, ->(tag_ids) {
+    Array(tag_ids).reduce(self) do |result, id|
       result.joins("INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}")
     end
   }
-  scope :tagged_with_none, ->(tags) {
-    Array(tags).map(&:id).map(&:to_i).reduce(self) do |result, id|
+  scope :tagged_with_none, ->(tag_ids) {
+    Array(tag_ids).reduce(self) do |result, id|
       result.joins("LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}")
             .where("t#{id}.tag_id IS NULL")
     end
@@ -249,7 +250,7 @@ class Status < ApplicationRecord
   end
 
   def hidden?
-    !published? || !distributable?
+    !(published? || distributable?)
   end
 
   def distributable?
@@ -486,7 +487,7 @@ class Status < ApplicationRecord
 
       return if account_ids.empty?
 
-      accounts = Account.where(id: account_ids).includes(:account_stat).index_by(&:id)
+      accounts = Account.where(id: account_ids).includes(:account_stat).each_with_object({}) { |a, h| h[a.id] = a }
 
       cached_items.each do |item|
         item.account = accounts[item.account_id]
diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb
index baff55020..a7d583a7e 100644
--- a/app/models/tag_feed.rb
+++ b/app/models/tag_feed.rb
@@ -53,6 +53,6 @@ class TagFeed < PublicFeed
   end
 
   def tags_for(names)
-    Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
+    Tag.matching_name(Array(names).take(LIMIT_PER_MODE)).pluck(:id) if names.present?
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index fa16f91b8..07fb9e1bb 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -178,7 +178,7 @@ class User < ApplicationRecord
   end
 
   def active_for_authentication?
-    true
+    !account.memorial?
   end
 
   def suspicious_sign_in?(ip)
@@ -186,7 +186,7 @@ class User < ApplicationRecord
   end
 
   def functional?
-    confirmed? && approved? && !disabled? && !account.suspended?
+    confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
   end
 
   def unconfirmed_or_pending?
diff --git a/app/models/webauthn_credential.rb b/app/models/webauthn_credential.rb
index 4129ce539..7d423e38d 100644
--- a/app/models/webauthn_credential.rb
+++ b/app/models/webauthn_credential.rb
@@ -18,5 +18,5 @@ class WebauthnCredential < ApplicationRecord
   validates :external_id, uniqueness: true
   validates :nickname, uniqueness: { scope: :user_id }
   validates :sign_count,
-            numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**32 - 1 }
+            numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**63 - 1 }
 end