about summary refs log tree commit diff
path: root/app/models
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2020-01-24 14:37:06 +0100
committerThibaut Girka <thib@sitedethib.com>2020-01-24 14:37:06 +0100
commit9adeaf2bfc1c16ca70e611b5288886b21414217b (patch)
tree53372bc9bd6f38d158aad50c768e18773b7d3f3b /app/models
parenta8c109baca4d02cc8aed454e231518c1f8ec1844 (diff)
parent4bae4e972d43f71bffb888ac82c180b2fa3f1ada (diff)
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/statuses_controller.rb`:
  Minor conflict due to theming system
Diffstat (limited to 'app/models')
-rw-r--r--app/models/account.rb6
-rw-r--r--app/models/announcement.rb85
-rw-r--r--app/models/announcement_filter.rb39
-rw-r--r--app/models/announcement_mute.rb19
-rw-r--r--app/models/announcement_reaction.rb37
-rw-r--r--app/models/backup.rb2
-rw-r--r--app/models/bookmark.rb6
-rw-r--r--app/models/concerns/account_interactions.rb1
-rw-r--r--app/models/custom_emoji.rb2
-rw-r--r--app/models/custom_filter.rb1
-rw-r--r--app/models/media_attachment.rb1
-rw-r--r--app/models/relationship_filter.rb109
-rw-r--r--app/models/user.rb2
13 files changed, 304 insertions, 6 deletions
diff --git a/app/models/account.rb b/app/models/account.rb
index 9b9361670..b856d1c76 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -478,6 +478,12 @@ class Account < ApplicationRecord
       records
     end
 
+    def from_text(text)
+      return [] if text.blank?
+
+      text.scan(MENTION_RE).map { |match| match.first.split('@', 2) }.uniq.map { |(username, domain)| EntityCache.instance.mention(username, domain) }
+    end
+
     private
 
     def generate_query_for_search(terms)
diff --git a/app/models/announcement.rb b/app/models/announcement.rb
new file mode 100644
index 000000000..4da9f94d6
--- /dev/null
+++ b/app/models/announcement.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcements
+#
+#  id           :bigint(8)        not null, primary key
+#  text         :text             default(""), not null
+#  published    :boolean          default(FALSE), not null
+#  all_day      :boolean          default(FALSE), not null
+#  scheduled_at :datetime
+#  starts_at    :datetime
+#  ends_at      :datetime
+#  created_at   :datetime         not null
+#  updated_at   :datetime         not null
+#
+
+class Announcement < ApplicationRecord
+  after_commit :queue_publish, on: :create
+
+  scope :unpublished, -> { where(published: false) }
+  scope :published, -> { where(published: true) }
+  scope :without_muted, ->(account) { joins("LEFT OUTER JOIN announcement_mutes ON announcement_mutes.announcement_id = announcements.id AND announcement_mutes.account_id = #{account.id}").where('announcement_mutes.id IS NULL') }
+  scope :chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.created_at) ASC')) }
+
+  has_many :announcement_mutes, dependent: :destroy
+  has_many :announcement_reactions, dependent: :destroy
+
+  validates :text, presence: true
+  validates :starts_at, presence: true, if: -> { ends_at.present? }
+  validates :ends_at, presence: true, if: -> { starts_at.present? }
+
+  before_validation :set_all_day
+  before_validation :set_starts_at, on: :create
+  before_validation :set_ends_at, on: :create
+
+  def time_range?
+    starts_at.present? && ends_at.present?
+  end
+
+  def mentions
+    @mentions ||= Account.from_text(text)
+  end
+
+  def tags
+    @tags ||= Tag.find_or_create_by_names(Extractor.extract_hashtags(text))
+  end
+
+  def emojis
+    @emojis ||= CustomEmoji.from_text(text)
+  end
+
+  def reactions(account = nil)
+    records = begin
+      scope = announcement_reactions.group(:announcement_id, :name, :custom_emoji_id).order(Arel.sql('MIN(created_at) ASC'))
+
+      if account.nil?
+        scope.select('name, custom_emoji_id, count(*) as count, false as me')
+      else
+        scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me")
+      end
+    end
+
+    ActiveRecord::Associations::Preloader.new.preload(records, :custom_emoji)
+    records
+  end
+
+  private
+
+  def set_all_day
+    self.all_day = false if starts_at.blank? || ends_at.blank?
+  end
+
+  def set_starts_at
+    self.starts_at = starts_at.change(hour: 0, min: 0, sec: 0) if all_day? && starts_at.present?
+  end
+
+  def set_ends_at
+    self.ends_at = ends_at.change(hour: 23, min: 59, sec: 59) if all_day? && ends_at.present?
+  end
+
+  def queue_publish
+    PublishScheduledAnnouncementWorker.perform_async(id) if scheduled_at.blank?
+  end
+end
diff --git a/app/models/announcement_filter.rb b/app/models/announcement_filter.rb
new file mode 100644
index 000000000..950852460
--- /dev/null
+++ b/app/models/announcement_filter.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class AnnouncementFilter
+  KEYS = %i(
+    published
+    unpublished
+  ).freeze
+
+  attr_reader :params
+
+  def initialize(params)
+    @params = params
+  end
+
+  def results
+    scope = Announcement.unscoped
+
+    params.each do |key, value|
+      next if key.to_s == 'page'
+
+      scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
+    end
+
+    scope.chronological
+  end
+
+  private
+
+  def scope_for(key, _value)
+    case key.to_s
+    when 'published'
+      Announcement.published
+    when 'unpublished'
+      Announcement.unpublished
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+end
diff --git a/app/models/announcement_mute.rb b/app/models/announcement_mute.rb
new file mode 100644
index 000000000..46fda2f5d
--- /dev/null
+++ b/app/models/announcement_mute.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcement_mutes
+#
+#  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#
+
+class AnnouncementMute < ApplicationRecord
+  belongs_to :account
+  belongs_to :announcement, inverse_of: :announcement_mutes
+
+  validates :account_id, uniqueness: { scope: :announcement_id }
+end
diff --git a/app/models/announcement_reaction.rb b/app/models/announcement_reaction.rb
new file mode 100644
index 000000000..d22771034
--- /dev/null
+++ b/app/models/announcement_reaction.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: announcement_reactions
+#
+#  id              :bigint(8)        not null, primary key
+#  account_id      :bigint(8)
+#  announcement_id :bigint(8)
+#  name            :string           default(""), not null
+#  custom_emoji_id :bigint(8)
+#  created_at      :datetime         not null
+#  updated_at      :datetime         not null
+#
+
+class AnnouncementReaction < ApplicationRecord
+  after_commit :queue_publish
+
+  belongs_to :account
+  belongs_to :announcement, inverse_of: :announcement_reactions
+  belongs_to :custom_emoji, optional: true
+
+  validates :name, presence: true
+  validates_with ReactionValidator
+
+  before_validation :set_custom_emoji
+
+  private
+
+  def set_custom_emoji
+    self.custom_emoji = CustomEmoji.local.find_by(disabled: false, shortcode: name) if name.present?
+  end
+
+  def queue_publish
+    PublishAnnouncementReactionWorker.perform_async(announcement_id, name) unless announcement.destroyed?
+  end
+end
diff --git a/app/models/backup.rb b/app/models/backup.rb
index 8eeb1748a..d242fd62c 100644
--- a/app/models/backup.rb
+++ b/app/models/backup.rb
@@ -7,11 +7,11 @@
 #  user_id           :bigint(8)
 #  dump_file_name    :string
 #  dump_content_type :string
-#  dump_file_size    :bigint
 #  dump_updated_at   :datetime
 #  processed         :boolean          default(FALSE), not null
 #  created_at        :datetime         not null
 #  updated_at        :datetime         not null
+#  dump_file_size    :bigint(8)
 #
 
 class Backup < ApplicationRecord
diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb
index 01dc48ee7..916261a17 100644
--- a/app/models/bookmark.rb
+++ b/app/models/bookmark.rb
@@ -3,11 +3,11 @@
 #
 # Table name: bookmarks
 #
-#  id         :integer          not null, primary key
+#  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
-#  account_id :integer          not null
-#  status_id  :integer          not null
 #
 
 class Bookmark < ApplicationRecord
diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb
index f27d39483..14bcf7bb1 100644
--- a/app/models/concerns/account_interactions.rb
+++ b/app/models/concerns/account_interactions.rb
@@ -84,6 +84,7 @@ module AccountInteractions
     has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
     has_many :conversation_mutes, dependent: :destroy
     has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
+    has_many :announcement_mutes, dependent: :destroy
   end
 
   def follow!(other_account, reblogs: nil, uri: nil)
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index 0dacaf654..d177cf281 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -67,7 +67,7 @@ class CustomEmoji < ApplicationRecord
   end
 
   class << self
-    def from_text(text, domain)
+    def from_text(text, domain = nil)
       return [] if text.blank?
 
       shortcodes = text.scan(SCAN_RE).map(&:first).uniq
diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb
index 382562fb8..8df8a4fbf 100644
--- a/app/models/custom_filter.rb
+++ b/app/models/custom_filter.rb
@@ -20,6 +20,7 @@ class CustomFilter < ApplicationRecord
     notifications
     public
     thread
+    account
   ).freeze
 
   include Expireable
diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb
index b87b1b9d3..6a0b892f6 100644
--- a/app/models/media_attachment.rb
+++ b/app/models/media_attachment.rb
@@ -142,6 +142,7 @@ class MediaAttachment < ApplicationRecord
 
   validates :account, presence: true
   validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
+  validates :file, presence: true, if: :local?
 
   scope :attached,   -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
   scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb
index 51640f494..e6859bf3d 100644
--- a/app/models/relationship_filter.rb
+++ b/app/models/relationship_filter.rb
@@ -7,5 +7,114 @@ class RelationshipFilter
     by_domain
     activity
     order
+    location
   ).freeze
+
+  attr_reader :params, :account
+
+  def initialize(account, params)
+    @account = account
+    @params  = params
+
+    set_defaults!
+  end
+
+  def results
+    scope = scope_for('relationship', params['relationship'].to_s.strip)
+
+    params.each do |key, value|
+      next if key.to_s == 'page'
+
+      scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present?
+    end
+
+    scope
+  end
+
+  private
+
+  def set_defaults!
+    params['relationship'] = 'following' if params['relationship'].blank?
+    params['order']        = 'recent' if params['order'].blank?
+  end
+
+  def scope_for(key, value)
+    case key
+    when 'relationship'
+      relationship_scope(value)
+    when 'by_domain'
+      by_domain_scope(value)
+    when 'location'
+      location_scope(value)
+    when 'status'
+      status_scope(value)
+    when 'order'
+      order_scope(value)
+    when 'activity'
+      activity_scope(value)
+    else
+      raise "Unknown filter: #{key}"
+    end
+  end
+
+  def relationship_scope(value)
+    case value
+    when 'following'
+      account.following.eager_load(:account_stat).reorder(nil)
+    when 'followed_by'
+      account.followers.eager_load(:account_stat).reorder(nil)
+    when 'mutual'
+      account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following))
+    when 'invited'
+      Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil)
+    else
+      raise "Unknown relationship: #{value}"
+    end
+  end
+
+  def by_domain_scope(value)
+    Account.where(domain: value)
+  end
+
+  def location_scope(value)
+    case value
+    when 'local'
+      Account.local
+    when 'remote'
+      Account.remote
+    else
+      raise "Unknown location: #{value}"
+    end
+  end
+
+  def status_scope(value)
+    case value
+    when 'moved'
+      Account.where.not(moved_to_account_id: nil)
+    when 'primary'
+      Account.where(moved_to_account_id: nil)
+    else
+      raise "Unknown status: #{value}"
+    end
+  end
+
+  def order_scope(value)
+    case value
+    when 'active'
+      Account.by_recent_status
+    when 'recent'
+      params[:relationship] == 'invited' ? Account.recent : Follow.recent
+    else
+      raise "Unknown order: #{value}"
+    end
+  end
+
+  def activity_scope(value)
+    case value
+    when 'dormant'
+      AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
+    else
+      raise "Unknown activity: #{value}"
+    end
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 794c2091c..9d5114e74 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -248,7 +248,7 @@ class User < ApplicationRecord
                                  ip: request.remote_ip).session_id
   end
 
-  def exclusive_session(id)
+  def clear_other_sessions(id)
     session_activations.exclusive(id)
   end