class ChatChannel < ApplicationRecord include AlgoliaSearch attr_accessor :current_user, :usernames_string has_many :messages has_many :chat_channel_memberships, dependent: :destroy has_many :users, through: :chat_channel_memberships has_many :active_memberships, -> { where status: "active" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel has_many :pending_memberships, -> { where status: "pending" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel has_many :rejected_memberships, -> { where status: "rejected" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel has_many :mod_memberships, -> { where role: "mod" }, class_name: "ChatChannelMembership", inverse_of: :chat_channel has_many :active_users, through: :active_memberships, class_name: "User", source: :user has_many :pending_users, through: :pending_memberships, class_name: "User", source: :user has_many :rejected_users, through: :rejected_memberships, class_name: "User", source: :user has_many :mod_users, through: :mod_memberships, class_name: "User", source: :user validates :channel_type, presence: true, inclusion: { in: %w[open invite_only direct] } validates :status, presence: true, inclusion: { in: %w[active inactive blocked] } validates :slug, uniqueness: true, presence: true algoliasearch index_name: "SecuredChatChannel_#{Rails.env}" do attribute :id, :viewable_by, :slug, :channel_type, :channel_name, :last_message_at, :status, :messages_count, :channel_human_names, :channel_mod_ids, :pending_users_select_fields, :description searchableAttributes %i[channel_name channel_slug channel_human_names] attributesForFaceting ["filterOnly(viewable_by)", "filterOnly(status)", "filterOnly(channel_type)"] ranking ["desc(last_message_at)"] end before_destroy :remove_from_index!, prepend: true def open? channel_type == "open" end def direct? channel_type == "direct" end def invite_only? channel_type == "invite_only" end def clear_channel messages.destroy_all Pusher.trigger(pusher_channels, "channel-cleared", { chat_channel_id: id }.to_json) true rescue Pusher::Error => e logger.info "PUSHER ERROR: #{e.message}" end def has_member?(user) active_users.include?(user) end def last_opened_at(user = nil) user ||= current_user chat_channel_memberships.where(user_id: user.id).pluck(:last_opened_at).first end def self.create_with_users(users, channel_type = "direct", contrived_name = "New Channel") raise "Invalid direct channel" if users.size != 2 && channel_type == "direct" if channel_type == "direct" usernames = users.map(&:username).sort contrived_name = "Direct chat between " + usernames.join(" and ") slug = usernames.join("/") else slug = contrived_name.to_s.parameterize + "-" + rand(100_000).to_s(26) end channel = ChatChannel.find_by(slug: slug) if channel raise "Blocked channel" if channel.status == "blocked" channel.status = "active" channel.save else channel = create( channel_type: channel_type, channel_name: contrived_name, slug: slug, last_message_at: 1.week.ago, status: "active", ) channel.add_users(users) channel.index! channel.chat_channel_memberships.map(&:index!) end channel end def add_users(users) Array(users).each do |user| ChatChannelMembership.create!(user_id: user.id, chat_channel_id: id) end end def remove_user(user) chat_channel_memberships.where(user: user).destroy_all end def pusher_channels if invite_only? "presence-channel-#{id}" elsif open? "open-channel-#{id}" else chat_channel_memberships.pluck(:user_id).map { |id| "private-message-notifications-#{id}" } end end def adjusted_slug(user = nil, caller_type = "receiver") user ||= current_user if direct? && caller_type == "receiver" "@" + slug.gsub("/#{user.username}", "").gsub("#{user.username}/", "") elsif caller_type == "sender" "@" + user.username else slug end end def viewable_by active_memberships.pluck(:user_id) end def messages_count messages.size end def channel_human_names active_memberships. order("last_opened_at DESC").limit(5).includes(:user).map do |membership| membership.user.name end end def channel_users # Purely for algolia indexing obj = {} active_memberships. order("last_opened_at DESC").includes(:user).each do |membership| obj[membership.user.username] = user_obj(membership) end obj end def channel_mod_ids mod_users.pluck(:id) end def user_obj(membership) { profile_image: ProfileImage.new(membership.user).get(90), darker_color: membership.user.decorate.darker_color, name: membership.user.name, last_opened_at: membership.last_opened_at, username: membership.user.username, id: membership.user_id } end def pending_users_select_fields pending_users.select(:id, :username, :name, :updated_at) end end