about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb32
-rw-r--r--app/models/domain_block.rb2
-rw-r--r--app/models/follow_request.rb2
-rw-r--r--app/models/media_attachment.rb18
-rw-r--r--app/models/notification.rb4
-rw-r--r--app/models/preview_card.rb20
-rw-r--r--app/models/setting.rb50
-rw-r--r--app/models/status.rb11
-rw-r--r--app/models/user.rb7
-rw-r--r--app/models/web.rb7
-rw-r--r--app/models/web/setting.rb7
11 files changed, 135 insertions, 25 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 5c1f6e7c1..c2a41c4c6 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -62,8 +62,8 @@ class Account < ApplicationRecord
   scope :expiring, ->(time) { where(subscription_expires_at: nil).or(where('subscription_expires_at < ?', time)).remote.with_followers }
   scope :silenced, -> { where(silenced: true) }
   scope :suspended, -> { where(suspended: true) }
-  scope :recent, -> { reorder('id desc') }
-  scope :alphabetic, -> { order('domain ASC, username ASC') }
+  scope :recent, -> { reorder(id: :desc) }
+  scope :alphabetic, -> { order(domain: :asc, username: :asc) }
 
   def follow!(other_account)
     active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
@@ -104,7 +104,7 @@ class Account < ApplicationRecord
   end
 
   def subscribed?
-    !subscription_expires_at.nil?
+    !subscription_expires_at.blank?
   end
 
   def favourited?(status)
@@ -125,13 +125,10 @@ class Account < ApplicationRecord
 
   def save_with_optional_avatar!
     save!
-  rescue ActiveRecord::RecordInvalid => invalid
-    if invalid.record.errors[:avatar_file_size] || invalid[:avatar_content_type]
-      self.avatar = nil
-      retry
-    end
-
-    raise invalid
+  rescue ActiveRecord::RecordInvalid
+    self.avatar              = nil
+    self[:avatar_remote_url] = ''
+    save!
   end
 
   def avatar_remote_url=(url)
@@ -159,6 +156,7 @@ class Account < ApplicationRecord
     end
 
     def find_remote!(username, domain)
+      return if username.blank?
       where(arel_table[:username].matches(username.gsub(/[%_]/, '\\\\\0'))).where(domain.nil? ? { domain: nil } : arel_table[:domain].matches(domain.gsub(/[%_]/, '\\\\\0'))).take!
     end
 
@@ -175,19 +173,25 @@ class Account < ApplicationRecord
     end
 
     def following_map(target_account_ids, account_id)
-      Follow.where(target_account_id: target_account_ids).where(account_id: account_id).map { |f| [f.target_account_id, true] }.to_h
+      follow_mapping(Follow.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
 
     def followed_by_map(target_account_ids, account_id)
-      Follow.where(account_id: target_account_ids).where(target_account_id: account_id).map { |f| [f.account_id, true] }.to_h
+      follow_mapping(Follow.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
     end
 
     def blocking_map(target_account_ids, account_id)
-      Block.where(target_account_id: target_account_ids).where(account_id: account_id).map { |b| [b.target_account_id, true] }.to_h
+      follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
 
     def requested_map(target_account_ids, account_id)
-      FollowRequest.where(target_account_id: target_account_ids).where(account_id: account_id).map { |r| [r.target_account_id, true] }.to_h
+      follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
+    end
+
+    private
+
+    def follow_mapping(query, field)
+      query.pluck(field).inject({}) { |mapping, id| mapping[id] = true; mapping }
     end
   end
 
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index 9075b90a0..b4606da60 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class DomainBlock < ApplicationRecord
+  enum severity: [:silence, :suspend]
+
   validates :domain, presence: true, uniqueness: true
 
   def self.blocked?(domain)
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index 8eef3abf4..936ad0691 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -13,7 +13,7 @@ class FollowRequest < ApplicationRecord
 
   def authorize!
     account.follow!(target_account)
-    FeedManager.instance.merge_into_timeline(target_account, account)
+    MergeWorker.perform_async(target_account.id, account.id)
     destroy!
   end
 
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 2a5d23739..ecbed03e3 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -16,6 +16,7 @@ class MediaAttachment < ApplicationRecord
 
   validates :account, presence: true
 
+  scope :local, -> { where(remote_url: '') }
   default_scope { order('id asc') }
 
   def local?
@@ -38,6 +39,12 @@ class MediaAttachment < ApplicationRecord
     image? ? 'image' : 'video'
   end
 
+  def to_param
+    shortcode
+  end
+
+  before_create :set_shortcode
+
   class << self
     private
 
@@ -62,4 +69,15 @@ class MediaAttachment < ApplicationRecord
       end
     end
   end
+
+  private
+
+  def set_shortcode
+    return unless local?
+
+    loop do
+      self.shortcode = SecureRandom.urlsafe_base64(14)
+      break if MediaAttachment.find_by(shortcode: shortcode).nil?
+    end
+  end
 end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index c0b5c45a8..b7e8c9e71 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -39,9 +39,9 @@ class Notification < ApplicationRecord
   def target_status
     case type
     when :reblog
-      activity.reblog
+      activity&.reblog
     when :favourite, :mention
-      activity.status
+      activity&.status
     end
   end
 
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
new file mode 100644
index 000000000..e59b05eb8
--- /dev/null
+++ b/app/models/preview_card.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class PreviewCard < ApplicationRecord
+  IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
+
+  belongs_to :status
+
+  has_attached_file :image, styles: { original: '120x120#' }, convert_options: { all: '-quality 80 -strip' }
+
+  validates :url, presence: true
+  validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES
+  validates_attachment_size :image, less_than: 1.megabytes
+
+  def save_with_optional_image!
+    save!
+  rescue ActiveRecord::RecordInvalid
+    self.image = nil
+    save!
+  end
+end
diff --git a/app/models/setting.rb b/app/models/setting.rb
new file mode 100644
index 000000000..3796253d4
--- /dev/null
+++ b/app/models/setting.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+class Setting < RailsSettings::Base
+  source Rails.root.join('config/settings.yml')
+  namespace Rails.env
+
+  def to_param
+    var
+  end
+
+  class << self
+    def [](key)
+      return super(key) unless rails_initialized?
+
+      val = Rails.cache.fetch(cache_key(key, @object)) do
+        db_val = object(key)
+
+        if db_val
+          default_value = default_settings[key]
+
+          return default_value.with_indifferent_access.merge!(db_val.value) if default_value.is_a?(Hash)
+          db_val.value
+        else
+          default_settings[key]
+        end
+      end
+
+      val
+    end
+
+    def all_as_records
+      vars    = thing_scoped
+      records = vars.map { |r| [r.var, r] }.to_h
+
+      default_settings.each do |key, default_value|
+        next if records.key?(key) || default_value.is_a?(Hash)
+        records[key] = Setting.new(var: key, value: default_value)
+      end
+
+      records
+    end
+
+    private
+
+    def default_settings
+      return {} unless RailsSettings::Default.enabled?
+      RailsSettings::Default.instance
+    end
+  end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index bc595c93b..651d0dbc9 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -1,12 +1,15 @@
 # frozen_string_literal: true
 
 class Status < ApplicationRecord
+  include ActiveModel::Validations
   include Paginable
   include Streamable
   include Cacheable
 
   enum visibility: [:public, :unlisted, :private], _suffix: :visibility
 
+  belongs_to :application, class_name: 'Doorkeeper::Application'
+
   belongs_to :account, inverse_of: :statuses
   belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account'
 
@@ -21,11 +24,12 @@ class Status < ApplicationRecord
   has_and_belongs_to_many :tags
 
   has_one :notification, as: :activity, dependent: :destroy
+  has_one :preview_card, dependent: :destroy
 
   validates :account, presence: true
   validates :uri, uniqueness: true, unless: 'local?'
-  validates :text, presence: true, length: { maximum: 500 }, if: proc { |s| s.local? && !s.reblog? }
-  validates :text, presence: true, if: proc { |s| !s.local? && !s.reblog? }
+  validates :text, presence: true, unless: 'reblog?'
+  validates_with StatusLengthValidator
   validates :reblog, uniqueness: { scope: :account, message: 'of status already exists' }, if: 'reblog?'
 
   default_scope { order('id desc') }
@@ -33,7 +37,7 @@ class Status < ApplicationRecord
   scope :remote, -> { where.not(uri: nil) }
   scope :local, -> { where(uri: nil) }
 
-  cache_associated :account, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
+  cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
 
   def local?
     uri.nil?
@@ -171,6 +175,7 @@ class Status < ApplicationRecord
 
   before_validation do
     text.strip!
+    spoiler_text&.strip!
 
     self.reblog                 = reblog.reblog if reblog? && reblog.reblog?
     self.in_reply_to_account_id = (thread.account_id == account_id && thread.reply? ? thread.in_reply_to_account_id : thread.account_id) if reply?
diff --git a/app/models/user.rb b/app/models/user.rb
index d5a52da06..71d3ee0b8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class User < ApplicationRecord
+  include Settings::Extend
+
   devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :confirmable
 
   belongs_to :account, inverse_of: :user
@@ -14,11 +16,6 @@ class User < ApplicationRecord
   scope :recent,   -> { order('id desc') }
   scope :admins,   -> { where(admin: true) }
 
-  has_settings do |s|
-    s.key :notification_emails, defaults: { follow: false, reblog: false, favourite: false, mention: false, follow_request: true }
-    s.key :interactions, defaults: { must_be_follower: false, must_be_following: false }
-  end
-
   def send_devise_notification(notification, *args)
     devise_mailer.send(notification, self, *args).deliver_later
   end
diff --git a/app/models/web.rb b/app/models/web.rb
new file mode 100644
index 000000000..58654fd77
--- /dev/null
+++ b/app/models/web.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Web
+  def self.table_name_prefix
+    'web_'
+  end
+end
diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb
new file mode 100644
index 000000000..3d601189b
--- /dev/null
+++ b/app/models/web/setting.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class Web::Setting < ApplicationRecord
+  belongs_to :user
+
+  validates :user, uniqueness: true
+end