From 73be8f38c115c279e3d3961b98bd2b82b9706b05 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 6 Dec 2018 17:36:11 +0100 Subject: Add profile directory (#9427) Fix #5578 --- app/models/account.rb | 40 +++++++++++++++++++++++++++++ app/models/account_stat.rb | 12 +++++++-- app/models/account_tag_stat.rb | 24 +++++++++++++++++ app/models/concerns/account_associations.rb | 3 +++ app/models/concerns/account_counters.rb | 1 + app/models/tag.rb | 26 +++++++++++++++++++ 6 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 app/models/account_tag_stat.rb (limited to 'app/models') diff --git a/app/models/account.rb b/app/models/account.rb index fb089de90..20b0b7239 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -43,11 +43,13 @@ # featured_collection_url :string # fields :jsonb # actor_type :string +# discoverable :boolean # class Account < ApplicationRecord USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i + MIN_FOLLOWERS_DISCOVERY = 10 include AccountAssociations include AccountAvatar @@ -89,6 +91,10 @@ class Account < ApplicationRecord scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) } scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) } + scope :discoverable, -> { where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)) } + scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } + scope :popular, -> { order('account_stats.followers_count desc') } + scope :by_recent_status, -> { order('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc') } delegate :email, :unconfirmed_email, @@ -174,6 +180,40 @@ class Account < ApplicationRecord @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end + def tags_as_strings=(tag_names) + tag_names.map! { |name| name.mb_chars.downcase } + tag_names.uniq!(&:to_s) + + # Existing hashtags + hashtags_map = Tag.where(name: tag_names).each_with_object({}) { |tag, h| h[tag.name] = tag } + + # Initialize not yet existing hashtags + tag_names.each do |name| + next if hashtags_map.key?(name) + hashtags_map[name.downcase] = Tag.new(name: name) + end + + # Remove hashtags that are to be deleted + tags.each do |tag| + if hashtags_map.key?(tag.name) + hashtags_map.delete(tag.name) + else + transaction do + tags.delete(tag) + tag.decrement_count!(:accounts_count) + end + end + end + + # Add hashtags that were so far missing + hashtags_map.each_value do |tag| + transaction do + tags << tag + tag.increment_count!(:accounts_count) + end + end + end + def fields (self[:fields] || []).map { |f| Field.new(self, f) } end diff --git a/app/models/account_stat.rb b/app/models/account_stat.rb index d5715268e..9813aa84f 100644 --- a/app/models/account_stat.rb +++ b/app/models/account_stat.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true - # == Schema Information # # Table name: account_stats @@ -11,16 +10,25 @@ # followers_count :bigint(8) default(0), not null # created_at :datetime not null # updated_at :datetime not null +# last_status_at :datetime # class AccountStat < ApplicationRecord belongs_to :account, inverse_of: :account_stat def increment_count!(key) - update(key => public_send(key) + 1) + update(attributes_for_increment(key)) end def decrement_count!(key) update(key => [public_send(key) - 1, 0].max) end + + private + + def attributes_for_increment(key) + attrs = { key => public_send(key) + 1 } + attrs[:last_status_at] = Time.now.utc if key == :statuses_count + attrs + end end diff --git a/app/models/account_tag_stat.rb b/app/models/account_tag_stat.rb new file mode 100644 index 000000000..3c36c155a --- /dev/null +++ b/app/models/account_tag_stat.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +# == Schema Information +# +# Table name: account_tag_stats +# +# id :bigint(8) not null, primary key +# tag_id :bigint(8) not null +# accounts_count :bigint(8) default(0), not null +# hidden :boolean default(FALSE), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class AccountTagStat < ApplicationRecord + belongs_to :tag, inverse_of: :account_tag_stat + + def increment_count!(key) + update(key => public_send(key) + 1) + end + + def decrement_count!(key) + update(key => [public_send(key) - 1, 0].max) + end +end diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb index 0f7482fa6..ae50860ed 100644 --- a/app/models/concerns/account_associations.rb +++ b/app/models/concerns/account_associations.rb @@ -49,5 +49,8 @@ module AccountAssociations # Account migrations belongs_to :moved_to_account, class_name: 'Account', optional: true + + # Hashtags + has_and_belongs_to_many :tags end end diff --git a/app/models/concerns/account_counters.rb b/app/models/concerns/account_counters.rb index fa3ec9a3d..3581df8dd 100644 --- a/app/models/concerns/account_counters.rb +++ b/app/models/concerns/account_counters.rb @@ -16,6 +16,7 @@ module AccountCounters :followers_count=, :increment_count!, :decrement_count!, + :last_status_at, to: :account_stat def account_stat diff --git a/app/models/tag.rb b/app/models/tag.rb index 4f31f796e..b28e2cc18 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -11,12 +11,31 @@ class Tag < ApplicationRecord has_and_belongs_to_many :statuses + has_and_belongs_to_many :accounts + + has_one :account_tag_stat, dependent: :destroy HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_ยท][[:word:]_]*' HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i validates :name, presence: true, uniqueness: true, format: { with: /\A#{HASHTAG_NAME_RE}\z/i } + scope :discoverable, -> { joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).where(account_tag_stats: { hidden: false }).order(name: :asc) } + scope :hidden, -> { where(account_tag_stats: { hidden: true }) } + + delegate :accounts_count, + :accounts_count=, + :increment_count!, + :decrement_count!, + :hidden?, + to: :account_tag_stat + + after_save :save_account_tag_stat + + def account_tag_stat + super || build_account_tag_stat + end + def to_param name end @@ -43,4 +62,11 @@ class Tag < ApplicationRecord Tag.where('lower(name) like lower(?)', pattern).order(:name).limit(limit) end end + + private + + def save_account_tag_stat + return unless account_tag_stat&.changed? + account_tag_stat.save + end end -- cgit