Merge branch 'gs-master' into glitch-theme

This commit is contained in:
David Yip
2017-12-04 11:07:01 -06:00
214 changed files with 5378 additions and 1033 deletions

View File

@@ -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
View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Admin
def self.table_name_prefix
'admin_'
end
end

View 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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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
View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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)