Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/statuses_controller.rb`:
  Minor conflict due to theming system
This commit is contained in:
Thibaut Girka
2020-01-24 14:37:06 +01:00
251 changed files with 2910 additions and 770 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -20,6 +20,7 @@ class CustomFilter < ApplicationRecord
notifications
public
thread
account
).freeze
include Expireable

View File

@ -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) }

View File

@ -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

View File

@ -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