mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-15 16:59:41 +00:00
Merge branch 'gs-master' into glitch-theme
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
# followers_url :string default(""), not null
|
||||
# protocol :integer default("ostatus"), not null
|
||||
# memorial :boolean default(FALSE), not null
|
||||
# moved_to_account_id :integer
|
||||
#
|
||||
|
||||
class Account < ApplicationRecord
|
||||
@@ -102,6 +103,9 @@ class Account < ApplicationRecord
|
||||
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
||||
has_many :lists, through: :list_accounts
|
||||
|
||||
# Account migrations
|
||||
belongs_to :moved_to_account, class_name: 'Account'
|
||||
|
||||
scope :remote, -> { where.not(domain: nil) }
|
||||
scope :local, -> { where(domain: nil) }
|
||||
scope :without_followers, -> { where(followers_count: 0) }
|
||||
@@ -135,6 +139,10 @@ class Account < ApplicationRecord
|
||||
domain.nil?
|
||||
end
|
||||
|
||||
def moved?
|
||||
moved_to_account_id.present?
|
||||
end
|
||||
|
||||
def acct
|
||||
local? ? username : "#{username}@#{domain}"
|
||||
end
|
||||
@@ -208,6 +216,10 @@ class Account < ApplicationRecord
|
||||
Rails.cache.fetch("exclude_domains_for:#{id}") { domain_blocks.pluck(:domain) }
|
||||
end
|
||||
|
||||
def preferred_inbox_url
|
||||
shared_inbox_url.presence || inbox_url
|
||||
end
|
||||
|
||||
class << self
|
||||
def readonly_attributes
|
||||
super - %w(statuses_count following_count followers_count)
|
||||
|
||||
7
app/models/admin.rb
Normal file
7
app/models/admin.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
def self.table_name_prefix
|
||||
'admin_'
|
||||
end
|
||||
end
|
||||
40
app/models/admin/action_log.rb
Normal file
40
app/models/admin/action_log.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: admin_action_logs
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# account_id :integer
|
||||
# action :string default(""), not null
|
||||
# target_type :string
|
||||
# target_id :integer
|
||||
# recorded_changes :text default(""), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class Admin::ActionLog < ApplicationRecord
|
||||
serialize :recorded_changes
|
||||
|
||||
belongs_to :account, required: true
|
||||
belongs_to :target, required: true, polymorphic: true
|
||||
|
||||
default_scope -> { order('id desc') }
|
||||
|
||||
def action
|
||||
super.to_sym
|
||||
end
|
||||
|
||||
before_validation :set_changes
|
||||
|
||||
private
|
||||
|
||||
def set_changes
|
||||
case action
|
||||
when :destroy, :create
|
||||
self.recorded_changes = target.attributes
|
||||
when :update, :promote, :demote
|
||||
self.recorded_changes = target.previous_changes
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ module AccountInteractions
|
||||
def following_map(target_account_ids, account_id)
|
||||
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?
|
||||
reblogs: follow.show_reblogs?,
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -31,7 +31,7 @@ module AccountInteractions
|
||||
def requested_map(target_account_ids, account_id)
|
||||
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?
|
||||
reblogs: follow_request.show_reblogs?,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,8 +24,12 @@ class Form::AdminSettings
|
||||
:open_deletion=,
|
||||
:timeline_preview,
|
||||
:timeline_preview=,
|
||||
:show_staff_badge,
|
||||
:show_staff_badge=,
|
||||
:bootstrap_timeline_accounts,
|
||||
:bootstrap_timeline_accounts=,
|
||||
:min_invite_role,
|
||||
:min_invite_role=,
|
||||
to: Setting
|
||||
)
|
||||
end
|
||||
|
||||
25
app/models/form/migration.rb
Normal file
25
app/models/form/migration.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Form::Migration
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_accessor :acct, :account
|
||||
|
||||
def initialize(attrs = {})
|
||||
@account = attrs[:account]
|
||||
@acct = attrs[:account].acct unless @account.nil?
|
||||
@acct = attrs[:acct].gsub(/\A@/, '').strip unless attrs[:acct].nil?
|
||||
end
|
||||
|
||||
def valid?
|
||||
return false unless super
|
||||
set_account
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
self.account = (ResolveRemoteAccountService.new.call(acct) if account.nil? && acct.present?)
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
class Form::StatusBatch
|
||||
include ActiveModel::Model
|
||||
include AccountableConcern
|
||||
|
||||
attr_accessor :status_ids, :action
|
||||
attr_accessor :status_ids, :action, :current_account
|
||||
|
||||
ACTION_TYPE = %w(nsfw_on nsfw_off delete).freeze
|
||||
|
||||
@@ -20,11 +21,14 @@ class Form::StatusBatch
|
||||
|
||||
def change_sensitive(sensitive)
|
||||
media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id)
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
Status.where(id: media_attached_status_ids).find_each do |status|
|
||||
status.update!(sensitive: sensitive)
|
||||
log_action :update, status
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
false
|
||||
@@ -33,7 +37,9 @@ class Form::StatusBatch
|
||||
def delete_statuses
|
||||
Status.where(id: status_ids).find_each do |status|
|
||||
RemovalWorker.perform_async(status.id)
|
||||
log_action :destroy, status
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,44 +16,37 @@ class Glitch::KeywordMute < ApplicationRecord
|
||||
|
||||
validates_presence_of :keyword
|
||||
|
||||
after_commit :invalidate_cached_matcher
|
||||
after_commit :invalidate_cached_matchers
|
||||
|
||||
def self.matcher_for(account_id)
|
||||
Matcher.new(account_id)
|
||||
def self.text_matcher_for(account_id)
|
||||
TextMatcher.new(account_id)
|
||||
end
|
||||
|
||||
def self.tag_matcher_for(account_id)
|
||||
TagMatcher.new(account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invalidate_cached_matcher
|
||||
Rails.cache.delete("keyword_mutes:regex:#{account_id}")
|
||||
def invalidate_cached_matchers
|
||||
Rails.cache.delete(TextMatcher.cache_key(account_id))
|
||||
Rails.cache.delete(TagMatcher.cache_key(account_id))
|
||||
end
|
||||
|
||||
class Matcher
|
||||
class RegexpMatcher
|
||||
attr_reader :account_id
|
||||
attr_reader :regex
|
||||
|
||||
def initialize(account_id)
|
||||
@account_id = account_id
|
||||
regex_text = Rails.cache.fetch("keyword_mutes:regex:#{account_id}") { regex_text_for_account }
|
||||
regex_text = Rails.cache.fetch(self.class.cache_key(account_id)) { make_regex_text }
|
||||
@regex = /#{regex_text}/
|
||||
end
|
||||
|
||||
def =~(str)
|
||||
regex =~ str
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
def keywords
|
||||
Glitch::KeywordMute.where(account_id: account_id).select(:keyword, :id, :whole_word)
|
||||
end
|
||||
|
||||
def regex_text_for_account
|
||||
kws = keywords.find_each.with_object([]) do |kw, a|
|
||||
a << (kw.whole_word ? boundary_regex_for_keyword(kw.keyword) : kw.keyword)
|
||||
end
|
||||
|
||||
Regexp.union(kws).source
|
||||
Glitch::KeywordMute.where(account_id: account_id).pluck(:whole_word, :keyword)
|
||||
end
|
||||
|
||||
def boundary_regex_for_keyword(keyword)
|
||||
@@ -63,4 +56,45 @@ class Glitch::KeywordMute < ApplicationRecord
|
||||
/(?mix:#{sb}#{Regexp.escape(keyword)}#{eb})/
|
||||
end
|
||||
end
|
||||
|
||||
class TextMatcher < RegexpMatcher
|
||||
def self.cache_key(account_id)
|
||||
format('keyword_mutes:regex:text:%s', account_id)
|
||||
end
|
||||
|
||||
def matches?(str)
|
||||
!!(regex =~ str)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_regex_text
|
||||
kws = keywords.map! do |whole_word, keyword|
|
||||
whole_word ? boundary_regex_for_keyword(keyword) : keyword
|
||||
end
|
||||
|
||||
Regexp.union(kws).source
|
||||
end
|
||||
end
|
||||
|
||||
class TagMatcher < RegexpMatcher
|
||||
def self.cache_key(account_id)
|
||||
format('keyword_mutes:regex:tag:%s', account_id)
|
||||
end
|
||||
|
||||
def matches?(tags)
|
||||
tags.pluck(:name).any? { |n| regex =~ n }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_regex_text
|
||||
kws = keywords.map! do |whole_word, keyword|
|
||||
term = (Tag::HASHTAG_RE =~ keyword) ? $1 : keyword
|
||||
whole_word ? boundary_regex_for_keyword(term) : term
|
||||
end
|
||||
|
||||
Regexp.union(kws).source
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
52
app/models/invite.rb
Normal file
52
app/models/invite.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: invites
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer
|
||||
# code :string default(""), not null
|
||||
# expires_at :datetime
|
||||
# max_uses :integer
|
||||
# uses :integer default(0), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class Invite < ApplicationRecord
|
||||
belongs_to :user, required: true
|
||||
has_many :users, inverse_of: :invite
|
||||
|
||||
scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
|
||||
scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
|
||||
|
||||
before_validation :set_code
|
||||
|
||||
attr_reader :expires_in
|
||||
|
||||
def expires_in=(interval)
|
||||
self.expires_at = interval.to_i.seconds.from_now unless interval.blank?
|
||||
@expires_in = interval
|
||||
end
|
||||
|
||||
def valid_for_use?
|
||||
(max_uses.nil? || uses < max_uses) && !expired?
|
||||
end
|
||||
|
||||
def expire!
|
||||
touch(:expires_at)
|
||||
end
|
||||
|
||||
def expired?
|
||||
!expires_at.nil? && expires_at < Time.now.utc
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_code
|
||||
loop do
|
||||
self.code = ([*('a'..'z'), *('A'..'Z'), *('0'..'9')] - %w(0 1 I l O)).sample(8).join
|
||||
break if Invite.find_by(code: code).nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/models/invite_filter.rb
Normal file
32
app/models/invite_filter.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class InviteFilter
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def results
|
||||
scope = Invite.order(created_at: :desc)
|
||||
|
||||
params.each do |key, value|
|
||||
scope.merge!(scope_for(key, value)) if value.present?
|
||||
end
|
||||
|
||||
scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope_for(key, _value)
|
||||
case key.to_s
|
||||
when 'available'
|
||||
Invite.available
|
||||
when 'expired'
|
||||
Invite.expired
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,7 @@ class Notification < ApplicationRecord
|
||||
favourite: 'Favourite',
|
||||
}.freeze
|
||||
|
||||
STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :media_attachments, :tags, mentions: :account]].freeze
|
||||
STATUS_INCLUDES = [:account, :application, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :application, :media_attachments, :tags, mentions: :account]].freeze
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :from_account, class_name: 'Account'
|
||||
@@ -55,9 +55,11 @@ class Notification < ApplicationRecord
|
||||
def target_status
|
||||
case type
|
||||
when :reblog
|
||||
activity&.reblog
|
||||
when :favourite, :mention
|
||||
activity&.status
|
||||
status&.reblog
|
||||
when :favourite
|
||||
favourite&.status
|
||||
when :mention
|
||||
mention&.status
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ class Status < ApplicationRecord
|
||||
end
|
||||
|
||||
def reblogs_map(status_ids, account_id)
|
||||
select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).map { |s| [s.reblog_of_id, true] }.to_h
|
||||
select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).map { |s| [s.reblog_of_id, true] }.to_h
|
||||
end
|
||||
|
||||
def mutes_map(conversation_ids, account_id)
|
||||
@@ -291,6 +291,7 @@ class Status < ApplicationRecord
|
||||
|
||||
def set_visibility
|
||||
self.visibility = (account.locked? ? :private : :public) if visibility.nil?
|
||||
self.visibility = reblog.visibility if reblog?
|
||||
self.sensitive = false if sensitive.nil?
|
||||
end
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# account_id :integer not null
|
||||
# disabled :boolean default(FALSE), not null
|
||||
# moderator :boolean default(FALSE), not null
|
||||
# invite_id :integer
|
||||
#
|
||||
|
||||
class User < ApplicationRecord
|
||||
@@ -47,6 +48,7 @@ class User < ApplicationRecord
|
||||
otp_number_of_backup_codes: 10
|
||||
|
||||
belongs_to :account, inverse_of: :user, required: true
|
||||
belongs_to :invite, counter_cache: :uses
|
||||
accepts_nested_attributes_for :account
|
||||
|
||||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||
@@ -77,6 +79,8 @@ class User < ApplicationRecord
|
||||
:reduce_motion, :system_font_ui, :noindex, :flavour, :skin,
|
||||
to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
attr_accessor :invite_code
|
||||
|
||||
def confirmed?
|
||||
confirmed_at.present?
|
||||
end
|
||||
@@ -95,6 +99,19 @@ class User < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def role?(role)
|
||||
case role
|
||||
when 'user'
|
||||
true
|
||||
when 'moderator'
|
||||
staff?
|
||||
when 'admin'
|
||||
admin?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def disable!
|
||||
update!(disabled: true,
|
||||
last_sign_in_at: current_sign_in_at,
|
||||
@@ -169,6 +186,11 @@ class User < ApplicationRecord
|
||||
session.web_push_subscription.nil? ? nil : session.web_push_subscription.as_payload
|
||||
end
|
||||
|
||||
def invite_code=(code)
|
||||
self.invite = Invite.find_by(code: code) unless code.blank?
|
||||
@invite_code = code
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def send_devise_notification(notification, *args)
|
||||
|
||||
Reference in New Issue
Block a user