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.rb15
-rw-r--r--app/models/account_domain_block.rb4
-rw-r--r--app/models/account_moderation_note.rb6
-rw-r--r--app/models/admin/action_log.rb6
-rw-r--r--app/models/backup.rb4
-rw-r--r--app/models/block.rb6
-rw-r--r--app/models/concerns/account_interactions.rb13
-rw-r--r--app/models/concerns/attachmentable.rb32
-rw-r--r--app/models/concerns/cacheable.rb15
-rw-r--r--app/models/concerns/remotable.rb2
-rw-r--r--app/models/concerns/status_threading_concern.rb38
-rw-r--r--app/models/conversation.rb2
-rw-r--r--app/models/conversation_mute.rb6
-rw-r--r--app/models/custom_emoji.rb14
-rw-r--r--app/models/domain_block.rb2
-rw-r--r--app/models/email_domain_block.rb2
-rw-r--r--app/models/favourite.rb6
-rw-r--r--app/models/follow.rb6
-rw-r--r--app/models/follow_request.rb6
-rw-r--r--app/models/import.rb4
-rw-r--r--app/models/invite.rb4
-rw-r--r--app/models/list.rb4
-rw-r--r--app/models/list_account.rb8
-rw-r--r--app/models/media_attachment.rb19
-rw-r--r--app/models/mention.rb6
-rw-r--r--app/models/mute.rb6
-rw-r--r--app/models/notification.rb8
-rw-r--r--app/models/preview_card.rb21
-rw-r--r--app/models/report.rb12
-rw-r--r--app/models/report_note.rb6
-rw-r--r--app/models/session_activation.rb8
-rw-r--r--app/models/setting.rb4
-rw-r--r--app/models/site_upload.rb2
-rw-r--r--app/models/status.rb17
-rw-r--r--app/models/status_pin.rb6
-rw-r--r--app/models/stream_entry.rb6
-rw-r--r--app/models/subscription.rb4
-rw-r--r--app/models/tag.rb2
-rw-r--r--app/models/user.rb6
-rw-r--r--app/models/web/push_subscription.rb2
-rw-r--r--app/models/web/setting.rb4
41 files changed, 210 insertions, 134 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index db2171102..c1ce1e99e 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -3,7 +3,7 @@
 #
 # Table name: accounts
 #
-#  id                      :integer          not null, primary key
+#  id                      :bigint(8)        not null, primary key
 #  username                :string           default(""), not null
 #  domain                  :string
 #  secret                  :string           default(""), not null
@@ -42,7 +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
+#  moved_to_account_id     :bigint(8)
 #  featured_collection_url :string
 #  fields                  :jsonb
 #
@@ -120,6 +120,7 @@ class Account < ApplicationRecord
   scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
   scope :silenced, -> { where(silenced: true) }
   scope :suspended, -> { where(suspended: true) }
+  scope :without_suspended, -> { where(suspended: false) }
   scope :recent, -> { reorder(id: :desc) }
   scope :alphabetic, -> { order(domain: :asc, username: :asc) }
   scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') }
@@ -275,6 +276,10 @@ class Account < ApplicationRecord
       @value   = attr['value']
       @errors  = {}
     end
+
+    def to_h
+      { name: @name, value: @value }
+    end
   end
 
   class << self
@@ -393,7 +398,7 @@ class Account < ApplicationRecord
   end
 
   def emojis
-    CustomEmoji.from_text(note, domain)
+    @emojis ||= CustomEmoji.from_text(note, domain)
   end
 
   before_create :generate_keys
@@ -408,9 +413,9 @@ class Account < ApplicationRecord
   end
 
   def generate_keys
-    return unless local?
+    return unless local? && !Rails.env.test?
 
-    keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 512 : 2048)
+    keypair = OpenSSL::PKey::RSA.new(2048)
     self.private_key = keypair.to_pem
     self.public_key  = keypair.public_key.to_pem
   end
diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb
index bc00b4f32..e352000c3 100644
--- a/app/models/account_domain_block.rb
+++ b/app/models/account_domain_block.rb
@@ -3,11 +3,11 @@
 #
 # Table name: account_domain_blocks
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  domain     :string
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :integer
+#  account_id :bigint(8)
 #
 
 class AccountDomainBlock < ApplicationRecord
diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb
index 3ac9b1ac1..22e312bb2 100644
--- a/app/models/account_moderation_note.rb
+++ b/app/models/account_moderation_note.rb
@@ -3,10 +3,10 @@
 #
 # Table name: account_moderation_notes
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  content           :text             not null
-#  account_id        :integer          not null
-#  target_account_id :integer          not null
+#  account_id        :bigint(8)        not null
+#  target_account_id :bigint(8)        not null
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
 #
diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb
index 81f278e07..1d1db1b7a 100644
--- a/app/models/admin/action_log.rb
+++ b/app/models/admin/action_log.rb
@@ -3,11 +3,11 @@
 #
 # Table name: admin_action_logs
 #
-#  id               :integer          not null, primary key
-#  account_id       :integer
+#  id               :bigint(8)        not null, primary key
+#  account_id       :bigint(8)
 #  action           :string           default(""), not null
 #  target_type      :string
-#  target_id        :integer
+#  target_id        :bigint(8)
 #  recorded_changes :text             default(""), not null
 #  created_at       :datetime         not null
 #  updated_at       :datetime         not null
diff --git a/app/models/backup.rb b/app/models/backup.rb
index 5a7e6a14d..c2651313b 100644
--- a/app/models/backup.rb
+++ b/app/models/backup.rb
@@ -3,8 +3,8 @@
 #
 # Table name: backups
 #
-#  id                :integer          not null, primary key
-#  user_id           :integer
+#  id                :bigint(8)        not null, primary key
+#  user_id           :bigint(8)
 #  dump_file_name    :string
 #  dump_content_type :string
 #  dump_file_size    :integer
diff --git a/app/models/block.rb b/app/models/block.rb
index d6ecabd3b..df4a6bbac 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -3,11 +3,11 @@
 #
 # Table name: blocks
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
-#  account_id        :integer          not null
-#  target_account_id :integer          not null
+#  account_id        :bigint(8)        not null
+#  target_account_id :bigint(8)        not null
 #
 
 class Block < ApplicationRecord
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index 3830ba9b0..20fc74ba6 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -20,6 +20,10 @@ module AccountInteractions
       follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
     end
 
+    def blocked_by_map(target_account_ids, account_id)
+      follow_mapping(Block.where(account_id: target_account_ids, target_account_id: account_id), :account_id)
+    end
+
     def muting_map(target_account_ids, account_id)
       Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping|
         mapping[mute.target_account_id] = {
@@ -38,8 +42,12 @@ module AccountInteractions
 
     def domain_blocking_map(target_account_ids, account_id)
       accounts_map    = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
-      blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain)
-      accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h
+      blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
+      accounts_map.map { |id, domain| [id, blocked_domains[domain]] }.to_h
+    end
+
+    def domain_blocking_map_by_domain(target_domains, account_id)
+      follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
     end
 
     private
@@ -93,6 +101,7 @@ module AccountInteractions
     if mute.hide_notifications? != notifications
       mute.update!(hide_notifications: notifications)
     end
+    mute
   end
 
   def mute_conversation!(conversation)
diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb
index 90ce88463..6f8489b89 100644
--- a/app/models/concerns/attachmentable.rb
+++ b/app/models/concerns/attachmentable.rb
@@ -1,10 +1,15 @@
 # frozen_string_literal: true
 
+require 'mime/types'
+
 module Attachmentable
   extend ActiveSupport::Concern
 
+  MAX_MATRIX_LIMIT = 16_777_216 # 4096x4096px or approx. 16MB
+
   included do
     before_post_process :set_file_extensions
+    before_post_process :check_image_dimensions
   end
 
   private
@@ -12,10 +17,31 @@ module Attachmentable
   def set_file_extensions
     self.class.attachment_definitions.each_key do |attachment_name|
       attachment = send(attachment_name)
+
       next if attachment.blank?
-      extension = Paperclip::Interpolations.content_type_extension(attachment, :original)
-      basename  = Paperclip::Interpolations.basename(attachment, :original)
-      attachment.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.')
+
+      attachment.instance_write :file_name, [Paperclip::Interpolations.basename(attachment, :original), appropriate_extension(attachment)].delete_if(&:blank?).join('.')
+    end
+  end
+
+  def check_image_dimensions
+    self.class.attachment_definitions.each_key do |attachment_name|
+      attachment = send(attachment_name)
+
+      next if attachment.blank? || !attachment.content_type.match?(/image.*/) || attachment.queued_for_write[:original].blank?
+
+      width, height = FastImage.size(attachment.queued_for_write[:original].path)
+
+      raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height >= MAX_MATRIX_LIMIT)
     end
   end
+
+  def appropriate_extension(attachment)
+    mime_type = MIME::Types[attachment.content_type]
+
+    extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions
+    original_extension       = Paperclip::Interpolations.extension(attachment, :original)
+
+    extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first
+  end
 end
diff --git a/app/models/concerns/cacheable.rb b/app/models/concerns/cacheable.rb
index 51451d260..d7524cdfd 100644
--- a/app/models/concerns/cacheable.rb
+++ b/app/models/concerns/cacheable.rb
@@ -3,14 +3,19 @@
 module Cacheable
   extend ActiveSupport::Concern
 
-  class_methods do
+  module ClassMethods
+    @cache_associated = []
+
     def cache_associated(*associations)
       @cache_associated = associations
     end
-  end
 
-  included do
-    scope :with_includes, -> { includes(@cache_associated) }
-    scope :cache_ids, -> { select(:id, :updated_at) }
+    def with_includes
+      includes(@cache_associated)
+    end
+
+    def cache_ids
+      select(:id, :updated_at)
+    end
   end
 end
diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb
index 3b8c507c3..7f1ef5191 100644
--- a/app/models/concerns/remotable.rb
+++ b/app/models/concerns/remotable.rb
@@ -38,7 +38,7 @@ module Remotable
 
             self[attribute_name] = url if has_attribute?(attribute_name)
           end
-        rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError => e
+        rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
           Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
           nil
         end
diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb
index fffc095ee..8e817be00 100644
--- a/app/models/concerns/status_threading_concern.rb
+++ b/app/models/concerns/status_threading_concern.rb
@@ -7,8 +7,8 @@ module StatusThreadingConcern
     find_statuses_from_tree_path(ancestor_ids(limit), account)
   end
 
-  def descendants(account = nil)
-    find_statuses_from_tree_path(descendant_ids, account)
+  def descendants(limit, account = nil, max_child_id = nil, since_child_id = nil, depth = nil)
+    find_statuses_from_tree_path(descendant_ids(limit, max_child_id, since_child_id, depth), account)
   end
 
   private
@@ -46,34 +46,46 @@ module StatusThreadingConcern
     SQL
   end
 
-  def descendant_ids
-    descendant_statuses.pluck(:id)
+  def descendant_ids(limit, max_child_id, since_child_id, depth)
+    descendant_statuses(limit, max_child_id, since_child_id, depth).pluck(:id)
   end
 
-  def descendant_statuses
-    Status.find_by_sql([<<-SQL.squish, id: id])
+  def descendant_statuses(limit, max_child_id, since_child_id, depth)
+    Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth])
       WITH RECURSIVE search_tree(id, path)
       AS (
         SELECT id, ARRAY[id]
         FROM statuses
-        WHERE in_reply_to_id = :id
+        WHERE in_reply_to_id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE)
         UNION ALL
         SELECT statuses.id, path || statuses.id
         FROM search_tree
         JOIN statuses ON statuses.in_reply_to_id = search_tree.id
-        WHERE NOT statuses.id = ANY(path)
+        WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path)
       )
       SELECT id
       FROM search_tree
       ORDER BY path
+      LIMIT :limit
     SQL
   end
 
   def find_statuses_from_tree_path(ids, account)
-    statuses = statuses_with_accounts(ids).to_a
+    statuses    = statuses_with_accounts(ids).to_a
+    account_ids = statuses.map(&:account_id).uniq
+    domains     = statuses.map(&:account_domain).compact.uniq
+
+    relations = if account.present?
+                  {
+                    blocking: Account.blocking_map(account_ids, account.id),
+                    blocked_by: Account.blocked_by_map(account_ids, account.id),
+                    muting: Account.muting_map(account_ids, account.id),
+                    following: Account.following_map(account_ids, account.id),
+                    domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
+                  }
+                end
 
-    # FIXME: n+1 bonanza
-    statuses.reject! { |status| filter_from_context?(status, account) }
+    statuses.reject! { |status| filter_from_context?(status, account, relations) }
 
     # Order ancestors/descendants by tree path
     statuses.sort_by! { |status| ids.index(status.id) }
@@ -83,7 +95,7 @@ module StatusThreadingConcern
     Status.where(id: ids).includes(:account)
   end
 
-  def filter_from_context?(status, account)
-    StatusFilter.new(status, account).filtered?
+  def filter_from_context?(status, account, relations)
+    StatusFilter.new(status, account, relations).filtered?
   end
 end
diff --git a/app/models/conversation.rb b/app/models/conversation.rb
index 08c1ce945..4dfaea889 100644
--- a/app/models/conversation.rb
+++ b/app/models/conversation.rb
@@ -3,7 +3,7 @@
 #
 # Table name: conversations
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  uri        :string
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
diff --git a/app/models/conversation_mute.rb b/app/models/conversation_mute.rb
index 272eb81af..52c1a33e0 100644
--- a/app/models/conversation_mute.rb
+++ b/app/models/conversation_mute.rb
@@ -3,9 +3,9 @@
 #
 # Table name: conversation_mutes
 #
-#  id              :integer          not null, primary key
-#  conversation_id :integer          not null
-#  account_id      :integer          not null
+#  id              :bigint(8)        not null, primary key
+#  conversation_id :bigint(8)        not null
+#  account_id      :bigint(8)        not null
 #
 
 class ConversationMute < ApplicationRecord
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 1ec21d1a0..b99ed01f0 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -3,7 +3,7 @@
 #
 # Table name: custom_emojis
 #
-#  id                 :integer          not null, primary key
+#  id                 :bigint(8)        not null, primary key
 #  shortcode          :string           default(""), not null
 #  domain             :string
 #  image_file_name    :string
@@ -40,6 +40,10 @@ class CustomEmoji < ApplicationRecord
 
   remotable_attachment :image, LIMIT
 
+  include Attachmentable
+
+  after_commit :remove_entity_cache
+
   def local?
     domain.nil?
   end
@@ -56,11 +60,17 @@ class CustomEmoji < ApplicationRecord
 
       return [] if shortcodes.empty?
 
-      where(shortcode: shortcodes, domain: domain, disabled: false)
+      EntityCache.instance.emoji(shortcodes, domain)
     end
 
     def search(shortcode)
       where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%")
     end
   end
+
+  private
+
+  def remove_entity_cache
+    Rails.cache.delete(EntityCache.instance.to_key(:emoji, shortcode, domain))
+  end
 end
diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb
index aea8919af..93658793b 100644
--- a/app/models/domain_block.rb
+++ b/app/models/domain_block.rb
@@ -3,7 +3,7 @@
 #
 # Table name: domain_blocks
 #
-#  id           :integer          not null, primary key
+#  id           :bigint(8)        not null, primary key
 #  domain       :string           default(""), not null
 #  created_at   :datetime         not null
 #  updated_at   :datetime         not null
diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb
index a104810d1..10490375b 100644
--- a/app/models/email_domain_block.rb
+++ b/app/models/email_domain_block.rb
@@ -3,7 +3,7 @@
 #
 # Table name: email_domain_blocks
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  domain     :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
index fa1884b86..c998a67eb 100644
--- a/app/models/favourite.rb
+++ b/app/models/favourite.rb
@@ -3,11 +3,11 @@
 #
 # Table name: favourites
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :integer          not null
-#  status_id  :integer          not null
+#  account_id :bigint(8)        not null
+#  status_id  :bigint(8)        not null
 #
 
 class Favourite < ApplicationRecord
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 8e6fe537a..2ca42ff70 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -3,11 +3,11 @@
 #
 # Table name: follows
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
-#  account_id        :integer          not null
-#  target_account_id :integer          not null
+#  account_id        :bigint(8)        not null
+#  target_account_id :bigint(8)        not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #
 
diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb
index cde26ceed..d559a8f62 100644
--- a/app/models/follow_request.rb
+++ b/app/models/follow_request.rb
@@ -3,11 +3,11 @@
 #
 # Table name: follow_requests
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
-#  account_id        :integer          not null
-#  target_account_id :integer          not null
+#  account_id        :bigint(8)        not null
+#  target_account_id :bigint(8)        not null
 #  show_reblogs      :boolean          default(TRUE), not null
 #
 
diff --git a/app/models/import.rb b/app/models/import.rb
index fdb4c6b80..55e970b0d 100644
--- a/app/models/import.rb
+++ b/app/models/import.rb
@@ -3,7 +3,7 @@
 #
 # Table name: imports
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  type              :integer          not null
 #  approved          :boolean          default(FALSE), not null
 #  created_at        :datetime         not null
@@ -12,7 +12,7 @@
 #  data_content_type :string
 #  data_file_size    :integer
 #  data_updated_at   :datetime
-#  account_id        :integer          not null
+#  account_id        :bigint(8)        not null
 #
 
 class Import < ApplicationRecord
diff --git a/app/models/invite.rb b/app/models/invite.rb
index 4ba5432d2..2250e588e 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -3,8 +3,8 @@
 #
 # Table name: invites
 #
-#  id         :integer          not null, primary key
-#  user_id    :integer          not null
+#  id         :bigint(8)        not null, primary key
+#  user_id    :bigint(8)        not null
 #  code       :string           default(""), not null
 #  expires_at :datetime
 #  max_uses   :integer
diff --git a/app/models/list.rb b/app/models/list.rb
index a2ec7e84a..c9c94fca1 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -3,8 +3,8 @@
 #
 # Table name: lists
 #
-#  id         :integer          not null, primary key
-#  account_id :integer          not null
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)        not null
 #  title      :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
diff --git a/app/models/list_account.rb b/app/models/list_account.rb
index da46cf032..87b498224 100644
--- a/app/models/list_account.rb
+++ b/app/models/list_account.rb
@@ -3,10 +3,10 @@
 #
 # Table name: list_accounts
 #
-#  id         :integer          not null, primary key
-#  list_id    :integer          not null
-#  account_id :integer          not null
-#  follow_id  :integer          not null
+#  id         :bigint(8)        not null, primary key
+#  list_id    :bigint(8)        not null
+#  account_id :bigint(8)        not null
+#  follow_id  :bigint(8)        not null
 #
 
 class ListAccount < ApplicationRecord
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index 3b16944ce..62abc876e 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -3,8 +3,8 @@
 #
 # Table name: media_attachments
 #
-#  id                :integer          not null, primary key
-#  status_id         :integer
+#  id                :bigint(8)        not null, primary key
+#  status_id         :bigint(8)
 #  file_file_name    :string
 #  file_content_type :string
 #  file_file_size    :integer
@@ -15,12 +15,10 @@
 #  shortcode         :string
 #  type              :integer          default("image"), not null
 #  file_meta         :json
-#  account_id        :integer
+#  account_id        :bigint(8)
 #  description       :text
 #
 
-require 'mime/types'
-
 class MediaAttachment < ApplicationRecord
   self.inheritance_column = nil
 
@@ -90,6 +88,8 @@ class MediaAttachment < ApplicationRecord
   validates_attachment_size :file, less_than: LIMIT
   remotable_attachment :file, LIMIT
 
+  include Attachmentable
+
   validates :account, presence: true
   validates :description, length: { maximum: 420 }, if: :local?
 
@@ -247,13 +247,4 @@ class MediaAttachment < ApplicationRecord
       bitrate: movie.bitrate,
     }
   end
-
-  def appropriate_extension
-    mime_type = MIME::Types[file.content_type]
-
-    extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions
-    original_extension       = Paperclip::Interpolations.extension(file, :original)
-
-    extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first
-  end
 end
diff --git a/app/models/mention.rb b/app/models/mention.rb
index f864bf8e1..8ab886b18 100644
--- a/app/models/mention.rb
+++ b/app/models/mention.rb
@@ -3,11 +3,11 @@
 #
 # Table name: mentions
 #
-#  id         :integer          not null, primary key
-#  status_id  :integer
+#  id         :bigint(8)        not null, primary key
+#  status_id  :bigint(8)
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  account_id :integer
+#  account_id :bigint(8)
 #
 
 class Mention < ApplicationRecord
diff --git a/app/models/mute.rb b/app/models/mute.rb
index ebb3818c7..639120f7d 100644
--- a/app/models/mute.rb
+++ b/app/models/mute.rb
@@ -3,12 +3,12 @@
 #
 # Table name: mutes
 #
-#  id                 :integer          not null, primary key
+#  id                 :bigint(8)        not null, primary key
 #  created_at         :datetime         not null
 #  updated_at         :datetime         not null
 #  hide_notifications :boolean          default(TRUE), not null
-#  account_id         :integer          not null
-#  target_account_id  :integer          not null
+#  account_id         :bigint(8)        not null
+#  target_account_id  :bigint(8)        not null
 #
 
 class Mute < ApplicationRecord
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 0b0f01aa8..4f6ec8e8e 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -3,13 +3,13 @@
 #
 # Table name: notifications
 #
-#  id              :integer          not null, primary key
-#  activity_id     :integer          not null
+#  id              :bigint(8)        not null, primary key
+#  activity_id     :bigint(8)        not null
 #  activity_type   :string           not null
 #  created_at      :datetime         not null
 #  updated_at      :datetime         not null
-#  account_id      :integer          not null
-#  from_account_id :integer          not null
+#  account_id      :bigint(8)        not null
+#  from_account_id :bigint(8)        not null
 #
 
 class Notification < ApplicationRecord
diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb
index 0c82f06ce..a792b352b 100644
--- a/app/models/preview_card.rb
+++ b/app/models/preview_card.rb
@@ -3,7 +3,7 @@
 #
 # Table name: preview_cards
 #
-#  id                 :integer          not null, primary key
+#  id                 :bigint(8)        not null, primary key
 #  url                :string           default(""), not null
 #  title              :string           default(""), not null
 #  description        :string           default(""), not null
@@ -34,7 +34,7 @@ class PreviewCard < ApplicationRecord
 
   has_and_belongs_to_many :statuses
 
-  has_attached_file :image, styles: { original: { geometry: '400x400>', file_geometry_parser: FastGeometryParser } }, convert_options: { all: '-quality 80 -strip' }
+  has_attached_file :image, styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' }
 
   include Attachmentable
 
@@ -52,6 +52,23 @@ class PreviewCard < ApplicationRecord
     save!
   end
 
+  class << self
+    private
+
+    def image_styles(f)
+      styles = {
+        original: {
+          geometry: '400x400>',
+          file_geometry_parser: FastGeometryParser,
+          convert_options: '-coalesce -strip',
+        },
+      }
+
+      styles[:original][:format] = 'jpg' if f.instance.image_content_type == 'image/gif'
+      styles
+    end
+  end
+
   private
 
   def extract_dimensions
diff --git a/app/models/report.rb b/app/models/report.rb
index 5b90c7bce..efe385b2d 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -3,16 +3,16 @@
 #
 # Table name: reports
 #
-#  id                         :integer          not null, primary key
-#  status_ids                 :integer          default([]), not null, is an Array
+#  id                         :bigint(8)        not null, primary key
+#  status_ids                 :bigint(8)        default([]), not null, is an Array
 #  comment                    :text             default(""), not null
 #  action_taken               :boolean          default(FALSE), not null
 #  created_at                 :datetime         not null
 #  updated_at                 :datetime         not null
-#  account_id                 :integer          not null
-#  action_taken_by_account_id :integer
-#  target_account_id          :integer          not null
-#  assigned_account_id        :integer
+#  account_id                 :bigint(8)        not null
+#  action_taken_by_account_id :bigint(8)
+#  target_account_id          :bigint(8)        not null
+#  assigned_account_id        :bigint(8)
 #
 
 class Report < ApplicationRecord
diff --git a/app/models/report_note.rb b/app/models/report_note.rb
index 6d9dec80a..54b416577 100644
--- a/app/models/report_note.rb
+++ b/app/models/report_note.rb
@@ -3,10 +3,10 @@
 #
 # Table name: report_notes
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  content    :text             not null
-#  report_id  :integer          not null
-#  account_id :integer          not null
+#  report_id  :bigint(8)        not null
+#  account_id :bigint(8)        not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #
diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb
index d364f03df..34d25c83d 100644
--- a/app/models/session_activation.rb
+++ b/app/models/session_activation.rb
@@ -3,15 +3,15 @@
 #
 # Table name: session_activations
 #
-#  id                       :integer          not null, primary key
+#  id                       :bigint(8)        not null, primary key
 #  session_id               :string           not null
 #  created_at               :datetime         not null
 #  updated_at               :datetime         not null
 #  user_agent               :string           default(""), not null
 #  ip                       :inet
-#  access_token_id          :integer
-#  user_id                  :integer          not null
-#  web_push_subscription_id :integer
+#  access_token_id          :bigint(8)
+#  user_id                  :bigint(8)        not null
+#  web_push_subscription_id :bigint(8)
 #
 
 class SessionActivation < ApplicationRecord
diff --git a/app/models/setting.rb b/app/models/setting.rb
index df93590ce..033d09fd5 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -3,13 +3,13 @@
 #
 # Table name: settings
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  var        :string           not null
 #  value      :text
 #  thing_type :string
 #  created_at :datetime
 #  updated_at :datetime
-#  thing_id   :integer
+#  thing_id   :bigint(8)
 #
 
 class Setting < RailsSettings::Base
diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb
index 641128adf..14d683767 100644
--- a/app/models/site_upload.rb
+++ b/app/models/site_upload.rb
@@ -3,7 +3,7 @@
 #
 # Table name: site_uploads
 #
-#  id                :integer          not null, primary key
+#  id                :bigint(8)        not null, primary key
 #  var               :string           default(""), not null
 #  file_file_name    :string
 #  file_content_type :string
diff --git a/app/models/status.rb b/app/models/status.rb
index 952661169..44238ca6b 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -3,13 +3,13 @@
 #
 # Table name: statuses
 #
-#  id                     :integer          not null, primary key
+#  id                     :bigint(8)        not null, primary key
 #  uri                    :string
 #  text                   :text             default(""), not null
 #  created_at             :datetime         not null
 #  updated_at             :datetime         not null
-#  in_reply_to_id         :integer
-#  reblog_of_id           :integer
+#  in_reply_to_id         :bigint(8)
+#  reblog_of_id           :bigint(8)
 #  url                    :string
 #  sensitive              :boolean          default(FALSE), not null
 #  visibility             :integer          default("public"), not null
@@ -18,11 +18,11 @@
 #  favourites_count       :integer          default(0), not null
 #  reblogs_count          :integer          default(0), not null
 #  language               :string
-#  conversation_id        :integer
+#  conversation_id        :bigint(8)
 #  local                  :boolean
-#  account_id             :integer          not null
-#  application_id         :integer
-#  in_reply_to_account_id :integer
+#  account_id             :bigint(8)        not null
+#  application_id         :bigint(8)
+#  in_reply_to_account_id :bigint(8)
 #  local_only             :boolean
 #  full_status_text       :text             default(""), not null
 #
@@ -62,6 +62,7 @@ class Status < ApplicationRecord
   validates :uri, uniqueness: true, presence: true, unless: :local?
   validates :text, presence: true, unless: -> { with_media? || reblog? }
   validates_with StatusLengthValidator
+  validates_with DisallowedHashtagsValidator
   validates :reblog, uniqueness: { scope: :account }, if: :reblog?
 
   default_scope { recent }
@@ -164,7 +165,7 @@ class Status < ApplicationRecord
   end
 
   def emojis
-    CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain)
+    @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain)
   end
 
   after_create_commit :store_uri, if: :local?
diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb
index d3a98d8bd..afc76bded 100644
--- a/app/models/status_pin.rb
+++ b/app/models/status_pin.rb
@@ -3,9 +3,9 @@
 #
 # Table name: status_pins
 #
-#  id         :integer          not null, primary key
-#  account_id :integer          not null
-#  status_id  :integer          not null
+#  id         :bigint(8)        not null, primary key
+#  account_id :bigint(8)        not null
+#  status_id  :bigint(8)        not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
 #
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index 36fe487dc..dd383eb81 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -3,13 +3,13 @@
 #
 # Table name: stream_entries
 #
-#  id            :integer          not null, primary key
-#  activity_id   :integer
+#  id            :bigint(8)        not null, primary key
+#  activity_id   :bigint(8)
 #  activity_type :string
 #  created_at    :datetime         not null
 #  updated_at    :datetime         not null
 #  hidden        :boolean          default(FALSE), not null
-#  account_id    :integer
+#  account_id    :bigint(8)
 #
 
 class StreamEntry < ApplicationRecord
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index ea1173160..79b81828d 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -3,7 +3,7 @@
 #
 # Table name: subscriptions
 #
-#  id                          :integer          not null, primary key
+#  id                          :bigint(8)        not null, primary key
 #  callback_url                :string           default(""), not null
 #  secret                      :string
 #  expires_at                  :datetime
@@ -12,7 +12,7 @@
 #  updated_at                  :datetime         not null
 #  last_successful_delivery_at :datetime
 #  domain                      :string
-#  account_id                  :integer          not null
+#  account_id                  :bigint(8)        not null
 #
 
 class Subscription < ApplicationRecord
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 9fa9405d7..8b1b02412 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -3,7 +3,7 @@
 #
 # Table name: tags
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  name       :string           default(""), not null
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
diff --git a/app/models/user.rb b/app/models/user.rb
index 803eb8a33..24beb77b2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -3,7 +3,7 @@
 #
 # Table name: users
 #
-#  id                        :integer          not null, primary key
+#  id                        :bigint(8)        not null, primary key
 #  email                     :string           default(""), not null
 #  created_at                :datetime         not null
 #  updated_at                :datetime         not null
@@ -30,10 +30,10 @@
 #  last_emailed_at           :datetime
 #  otp_backup_codes          :string           is an Array
 #  filtered_languages        :string           default([]), not null, is an Array
-#  account_id                :integer          not null
+#  account_id                :bigint(8)        not null
 #  disabled                  :boolean          default(FALSE), not null
 #  moderator                 :boolean          default(FALSE), not null
-#  invite_id                 :integer
+#  invite_id                 :bigint(8)
 #  remember_token            :string
 #
 
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index 5aee92d27..1736106f7 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -3,7 +3,7 @@
 #
 # Table name: web_push_subscriptions
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  endpoint   :string           not null
 #  key_p256dh :string           not null
 #  key_auth   :string           not null
diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb
index 0a5129d17..99588d26c 100644
--- a/app/models/web/setting.rb
+++ b/app/models/web/setting.rb
@@ -3,11 +3,11 @@
 #
 # Table name: web_settings
 #
-#  id         :integer          not null, primary key
+#  id         :bigint(8)        not null, primary key
 #  data       :json
 #  created_at :datetime         not null
 #  updated_at :datetime         not null
-#  user_id    :integer          not null
+#  user_id    :bigint(8)        not null
 #
 
 class Web::Setting < ApplicationRecord