discourse/app/models/group.rb
Sam 03ea0bfe22 FEATURE: allow users to archive messages
Messages are now in 3 buckets

- Inbox for all new messages
- Sent for all sent messages
- Archive for all messages you are done with

You can select messages from your Inbox or Sent and move them to your Archive,
you can move messages out of your Archive similarly

Similar concept applied to group messages, except that archiving and unarchiving
will apply to all group members
2015-12-23 11:09:30 +11:00

449 lines
13 KiB
Ruby

class Group < ActiveRecord::Base
include HasCustomFields
has_many :category_groups, dependent: :destroy
has_many :group_users, dependent: :destroy
has_many :group_mentions, dependent: :destroy
has_many :group_archived_messages, dependent: :destroy
has_many :categories, through: :category_groups
has_many :users, through: :group_users
before_save :downcase_incoming_email
after_save :destroy_deletions
after_save :automatic_group_membership
after_save :update_primary_group
after_save :update_title
after_save :expire_cache
after_destroy :expire_cache
def expire_cache
ApplicationSerializer.expire_cache_fragment!("group_names")
end
validate :name_format_validator
validates_uniqueness_of :name, case_sensitive: false
validate :automatic_membership_email_domains_format_validator
validate :incoming_email_validator
AUTO_GROUPS = {
:everyone => 0,
:admins => 1,
:moderators => 2,
:staff => 3,
:trust_level_0 => 10,
:trust_level_1 => 11,
:trust_level_2 => 12,
:trust_level_3 => 13,
:trust_level_4 => 14
}
AUTO_GROUP_IDS = Hash[*AUTO_GROUPS.to_a.flatten.reverse]
ALIAS_LEVELS = {
:nobody => 0,
:only_admins => 1,
:mods_and_admins => 2,
:members_mods_and_admins => 3,
:everyone => 99
}
validates :alias_level, inclusion: { in: ALIAS_LEVELS.values}
scope :mentionable, lambda {|user|
levels = [ALIAS_LEVELS[:everyone]]
if user && user.admin?
levels = [ALIAS_LEVELS[:everyone],
ALIAS_LEVELS[:only_admins],
ALIAS_LEVELS[:mods_and_admins],
ALIAS_LEVELS[:members_mods_and_admins]]
elsif user && user.moderator?
levels = [ALIAS_LEVELS[:everyone],
ALIAS_LEVELS[:mods_and_admins],
ALIAS_LEVELS[:members_mods_and_admins]]
end
where("alias_level in (:levels) OR
(
alias_level = #{ALIAS_LEVELS[:members_mods_and_admins]} AND id in (
SELECT group_id FROM group_users WHERE user_id = :user_id)
)", levels: levels, user_id: user && user.id )
}
def downcase_incoming_email
self.incoming_email = (incoming_email || "").strip.downcase.presence
end
def incoming_email_validator
return if self.automatic || self.incoming_email.blank?
unless Email.is_valid?(incoming_email)
self.errors.add(:base, I18n.t('groups.errors.invalid_incoming_email', incoming_email: incoming_email))
end
end
def posts_for(guardian, before_post_id=nil)
user_ids = group_users.map { |gu| gu.user_id }
result = Post.includes(:user, :topic, topic: :category)
.references(:posts, :topics, :category)
.where(user_id: user_ids)
.where('topics.archetype <> ?', Archetype.private_message)
.where(post_type: Post.types[:regular])
result = guardian.filter_allowed_categories(result)
result = result.where('posts.id < ?', before_post_id) if before_post_id
result.order('posts.created_at desc')
end
def messages_for(guardian, before_post_id=nil)
result = Post.includes(:user, :topic, topic: :category)
.references(:posts, :topics, :category)
.where('topics.archetype = ?', Archetype.private_message)
.where(post_type: Post.types[:regular])
.where('topics.id IN (SELECT topic_id FROM topic_allowed_groups WHERE group_id = ?)', self.id)
result = guardian.filter_allowed_categories(result)
result = result.where('posts.id < ?', before_post_id) if before_post_id
result.order('posts.created_at desc')
end
def mentioned_posts_for(guardian, before_post_id=nil)
result = Post.joins(:group_mentions)
.includes(:user, :topic, topic: :category)
.references(:posts, :topics, :category)
.where('topics.archetype <> ?', Archetype.private_message)
.where(post_type: Post.types[:regular])
.where('group_mentions.group_id = ?', self.id)
result = guardian.filter_allowed_categories(result)
result = result.where('posts.id < ?', before_post_id) if before_post_id
result.order('posts.created_at desc')
end
def self.trust_group_ids
(10..19).to_a
end
def self.refresh_automatic_group!(name)
return unless id = AUTO_GROUPS[name]
unless group = self.lookup_group(name)
group = Group.new(name: name.to_s, automatic: true)
group.id = id
group.save!
end
group.name = I18n.t("groups.default_names.#{name}")
# don't allow shoddy localization to break this
validator = UsernameValidator.new(group.name)
unless validator.valid_format?
group.name = name
end
# the everyone group is special, it can include non-users so there is no
# way to have the membership in a table
if name == :everyone
group.save!
return group
end
# Remove people from groups they don't belong in.
#
# BEWARE: any of these subqueries could match ALL the user records,
# so they can't be used in IN clauses.
remove_user_subquery = case name
when :admins
"SELECT u.id FROM users u WHERE NOT u.admin"
when :moderators
"SELECT u.id FROM users u WHERE NOT u.moderator"
when :staff
"SELECT u.id FROM users u WHERE NOT u.admin AND NOT u.moderator"
when :trust_level_0, :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4
"SELECT u.id FROM users u WHERE u.trust_level < #{id - 10}"
end
remove_ids = exec_sql("SELECT gu.id id
FROM group_users gu,
(#{remove_user_subquery}) u
WHERE gu.group_id = #{group.id}
AND gu.user_id = u.id").map {|x| x['id']}
if remove_ids.length > 0
remove_ids.each_slice(100) do |ids|
GroupUser.where(id: ids).delete_all
end
end
# Add people to groups
real_ids = case name
when :admins
"SELECT u.id FROM users u WHERE u.admin"
when :moderators
"SELECT u.id FROM users u WHERE u.moderator"
when :staff
"SELECT u.id FROM users u WHERE u.moderator OR u.admin"
when :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4
"SELECT u.id FROM users u WHERE u.trust_level >= #{id-10}"
when :trust_level_0
"SELECT u.id FROM users u"
end
missing_users = GroupUser
.joins("RIGHT JOIN (#{real_ids}) X ON X.id = user_id AND group_id = #{group.id}")
.where("user_id IS NULL")
.select("X.id")
missing_users.each do |u|
group.group_users.build(user_id: u.id)
end
group.save!
# we want to ensure consistency
Group.reset_counters(group.id, :group_users)
group
end
def self.refresh_automatic_groups!(*args)
if args.length == 0
args = AUTO_GROUPS.keys
end
args.each do |group|
refresh_automatic_group!(group)
end
end
def self.ensure_automatic_groups!
AUTO_GROUPS.each_key do |name|
refresh_automatic_group!(name) unless lookup_group(name)
end
end
def self.[](name)
lookup_group(name) || refresh_automatic_group!(name)
end
def self.search_group(name)
Group.where(visible: true).where("name ILIKE :term_like", term_like: "#{name}%")
end
def self.lookup_group(name)
if id = AUTO_GROUPS[name]
Group.find_by(id: id)
else
unless group = Group.find_by(name: name)
raise ArgumentError, "unknown group"
end
group
end
end
def self.lookup_group_ids(opts)
if group_ids = opts[:group_ids]
group_ids = group_ids.split(",").map(&:to_i)
group_ids = Group.where(id: group_ids).pluck(:id)
end
group_ids ||= []
if group_names = opts[:group_names]
group_names = group_names.split(",")
if group_names.present?
group_ids += Group.where(name: group_names).pluck(:id)
end
end
group_ids
end
def self.desired_trust_level_groups(trust_level)
trust_group_ids.keep_if do |id|
id == AUTO_GROUPS[:trust_level_0] || (trust_level + 10) >= id
end
end
def self.user_trust_level_change!(user_id, trust_level)
desired = desired_trust_level_groups(trust_level)
undesired = trust_group_ids - desired
GroupUser.where(group_id: undesired, user_id: user_id).delete_all
desired.each do |id|
if group = find_by(id: id)
unless GroupUser.where(group_id: id, user_id: user_id).exists?
group.group_users.create!(user_id: user_id)
end
else
name = AUTO_GROUP_IDS[trust_level]
refresh_automatic_group!(name)
end
end
end
def self.builtin
Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2)
end
def usernames=(val)
current = usernames.split(",")
expected = val.split(",")
additions = expected - current
deletions = current - expected
map = Hash[*User.where(username: additions+deletions)
.select('id,username')
.map{|u| [u.username,u.id]}.flatten]
deletions = Set.new(deletions.map{|d| map[d]})
@deletions = []
group_users.each do |gu|
@deletions << gu if deletions.include?(gu.user_id)
end
additions.each do |a|
group_users.build(user_id: map[a])
end
end
def usernames
users.pluck(:username).join(",")
end
def add(user)
self.users.push(user)
end
def remove(user)
self.group_users.where(user: user).each(&:destroy)
user.update_columns(primary_group_id: nil) if user.primary_group_id == self.id
end
def add_owner(user)
self.group_users.create(user_id: user.id, owner: true)
end
def self.find_by_email(email)
self.find_by(incoming_email: Email.downcase(email))
end
protected
def name_format_validator
UsernameValidator.perform_validation(self, 'name')
end
def automatic_membership_email_domains_format_validator
return if self.automatic_membership_email_domains.blank?
domains = self.automatic_membership_email_domains.split("|")
domains.each do |domain|
domain.sub!(/^https?:\/\//, '')
domain.sub!(/\/.*$/, '')
self.errors.add :base, (I18n.t('groups.errors.invalid_domain', domain: domain)) unless domain =~ /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i
end
self.automatic_membership_email_domains = domains.join("|")
end
# hack around AR
def destroy_deletions
if @deletions
@deletions.each do |gu|
gu.destroy
User.where('id = ? AND primary_group_id = ?', gu.user_id, gu.group_id).update_all 'primary_group_id = NULL'
end
end
@deletions = nil
end
def automatic_group_membership
if self.automatic_membership_retroactive
Jobs.enqueue(:automatic_group_membership, group_id: self.id)
end
end
def update_title
return if new_record? && !self.title.present?
if self.title_changed?
sql = <<SQL
UPDATE users SET title = :title
WHERE (title = :title_was OR
title = '' OR
title IS NULL) AND
COALESCE(title,'') <> COALESCE(:title,'') AND
id IN (
SELECT user_id
FROM group_users
WHERE group_id = :id
)
SQL
self.class.exec_sql(sql,
title: title,
title_was: title_was,
id: id
)
end
end
def update_primary_group
return if new_record? && !self.primary_group?
if self.primary_group_changed?
sql = <<SQL
UPDATE users
/*set*/
/*where*/
SQL
builder = SqlBuilder.new(sql)
builder.where("
id IN (
SELECT user_id
FROM group_users
WHERE group_id = :id
)", id: id)
if primary_group
builder.set("primary_group_id = :id")
else
builder.set("primary_group_id = NULL")
builder.where("primary_group_id = :id")
end
builder.exec
end
end
end
# == Schema Information
#
# Table name: groups
#
# id :integer not null, primary key
# name :string(255) not null
# created_at :datetime not null
# updated_at :datetime not null
# automatic :boolean default(FALSE), not null
# user_count :integer default(0), not null
# alias_level :integer default(0)
# visible :boolean default(TRUE), not null
# automatic_membership_email_domains :text
# automatic_membership_retroactive :boolean default(FALSE)
# primary_group :boolean default(FALSE), not null
# title :string(255)
# grant_trust_level :integer
#
# Indexes
#
# index_groups_on_name (name) UNIQUE
#