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:
@ -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)
|
||||
|
85
app/models/announcement.rb
Normal file
85
app/models/announcement.rb
Normal 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
|
39
app/models/announcement_filter.rb
Normal file
39
app/models/announcement_filter.rb
Normal 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
|
19
app/models/announcement_mute.rb
Normal file
19
app/models/announcement_mute.rb
Normal 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
|
37
app/models/announcement_reaction.rb
Normal file
37
app/models/announcement_reaction.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,7 @@ class CustomFilter < ApplicationRecord
|
||||
notifications
|
||||
public
|
||||
thread
|
||||
account
|
||||
).freeze
|
||||
|
||||
include Expireable
|
||||
|
@ -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) }
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user