diff options
author | David Yip <yipdw@member.fsf.org> | 2017-12-04 11:07:01 -0600 |
---|---|---|
committer | David Yip <yipdw@member.fsf.org> | 2017-12-04 11:07:01 -0600 |
commit | d9800a5647cbc57db7679094b2271f8eb5ec328b (patch) | |
tree | f9210c465de5f9d80e294d9ffa8536f98f9c466e /app/models | |
parent | 1c74ede69e7a9916c19da6f05daa215231eba81c (diff) | |
parent | f2f2f1032082d6212771bd0307136484f671d37e (diff) |
Merge branch 'gs-master' into glitch-theme
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/account.rb | 12 | ||||
-rw-r--r-- | app/models/admin.rb | 7 | ||||
-rw-r--r-- | app/models/admin/action_log.rb | 40 | ||||
-rw-r--r-- | app/models/concerns/account_interactions.rb | 4 | ||||
-rw-r--r-- | app/models/form/admin_settings.rb | 4 | ||||
-rw-r--r-- | app/models/form/migration.rb | 25 | ||||
-rw-r--r-- | app/models/form/status_batch.rb | 8 | ||||
-rw-r--r-- | app/models/glitch/keyword_mute.rb | 72 | ||||
-rw-r--r-- | app/models/invite.rb | 52 | ||||
-rw-r--r-- | app/models/invite_filter.rb | 32 | ||||
-rw-r--r-- | app/models/notification.rb | 10 | ||||
-rw-r--r-- | app/models/status.rb | 3 | ||||
-rw-r--r-- | app/models/user.rb | 22 |
13 files changed, 264 insertions, 27 deletions
diff --git a/app/models/account.rb b/app/models/account.rb index a4b8e1c0b..ffd19fa52 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -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) diff --git a/app/models/admin.rb b/app/models/admin.rb new file mode 100644 index 000000000..d41d18449 --- /dev/null +++ b/app/models/admin.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Admin + def self.table_name_prefix + 'admin_' + end +end diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb new file mode 100644 index 000000000..4e950fbf7 --- /dev/null +++ b/app/models/admin/action_log.rb @@ -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 diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index c41f92581..fdf35a4e3 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -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 diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 2b148c82b..c1d2cf420 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -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 diff --git a/app/models/form/migration.rb b/app/models/form/migration.rb new file mode 100644 index 000000000..b74987337 --- /dev/null +++ b/app/models/form/migration.rb @@ -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 diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb index a97b4aa28..4f08a3049 100644 --- a/app/models/form/status_batch.rb +++ b/app/models/form/status_batch.rb @@ -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 diff --git a/app/models/glitch/keyword_mute.rb b/app/models/glitch/keyword_mute.rb index 009de1880..a2481308f 100644 --- a/app/models/glitch/keyword_mute.rb +++ b/app/models/glitch/keyword_mute.rb @@ -16,51 +16,85 @@ 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 + protected + + def keywords + Glitch::KeywordMute.where(account_id: account_id).pluck(:whole_word, :keyword) end - private + def boundary_regex_for_keyword(keyword) + sb = keyword =~ /\A[[:word:]]/ ? '\b' : '' + eb = keyword =~ /[[:word:]]\Z/ ? '\b' : '' - def keywords - Glitch::KeywordMute.where(account_id: account_id).select(:keyword, :id, :whole_word) + /(?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 - 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) + 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 - def boundary_regex_for_keyword(keyword) - sb = keyword =~ /\A[[:word:]]/ ? '\b' : '' - eb = keyword =~ /[[:word:]]\Z/ ? '\b' : '' + class TagMatcher < RegexpMatcher + def self.cache_key(account_id) + format('keyword_mutes:regex:tag:%s', account_id) + end - /(?mix:#{sb}#{Regexp.escape(keyword)}#{eb})/ + 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 diff --git a/app/models/invite.rb b/app/models/invite.rb new file mode 100644 index 000000000..6907c1f1d --- /dev/null +++ b/app/models/invite.rb @@ -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 diff --git a/app/models/invite_filter.rb b/app/models/invite_filter.rb new file mode 100644 index 000000000..7d89bad4a --- /dev/null +++ b/app/models/invite_filter.rb @@ -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 diff --git a/app/models/notification.rb b/app/models/notification.rb index a3ffb1f45..976963528 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -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 diff --git a/app/models/status.rb b/app/models/status.rb index 172d3a665..70cfdc1c7 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 2458cf298..29bdcbd67 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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) |