Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - `Gemfile.lock`: Not a real conflict, upstream updated dependencies that were too close to glitch-soc-only ones in the file. - `app/controllers/oauth/authorized_applications_controller.rb`: Upstream changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's theming system. Ported upstream changes. - `app/controllers/settings/base_controller.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's theming system. Ported upstream changes. - `app/controllers/settings/sessions_controller.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's theming system. Ported upstream changes. - `app/models/user.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc not preventing moved accounts from logging in. Ported upstream changes while keeping the ability for moved accounts to log in. - `app/policies/status_policy.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's local-only toots. Ported upstream changes. - `app/serializers/rest/account_serializer.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's ability to hide followers count. Ported upstream changes. - `app/services/process_mentions_service.rb`: Upstream refactored and changed the logic surrounding suspended accounts. Minor conflict due to glitch-soc's local-only toots. Ported upstream changes. - `package.json`: Not a real conflict, upstream updated dependencies that were too close to glitch-soc-only ones in the file.
This commit is contained in:
@ -226,23 +226,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?
|
||||
|
@ -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
|
||||
|
||||
|
20
app/models/account_deletion_request.rb
Normal file
20
app/models/account_deletion_request.rb
Normal file
@ -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
|
@ -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
|
||||
|
@ -60,5 +60,8 @@ module AccountAssociations
|
||||
# Hashtags
|
||||
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
|
||||
end
|
||||
end
|
||||
|
@ -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
|
||||
@ -95,25 +97,29 @@ module AccountInteractions
|
||||
has_many :announcement_mutes, 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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
||||
destroy!
|
||||
end
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -93,19 +93,19 @@ class Status < ApplicationRecord
|
||||
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
||||
scope :without_reblogs, -> { where('statuses.reblog_of_id IS NULL') }
|
||||
scope :with_public_visibility, -> { where(visibility: :public) }
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -169,7 +169,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def active_for_authentication?
|
||||
true
|
||||
!account.memorial?
|
||||
end
|
||||
|
||||
def suspicious_sign_in?(ip)
|
||||
@ -177,7 +177,7 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def functional?
|
||||
confirmed? && approved? && !disabled? && !account.suspended?
|
||||
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
|
||||
end
|
||||
|
||||
def unconfirmed_or_pending?
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user