discourse/plugins/chat/app/models/chat_channel.rb
Martin Brennan c6764d8c74
FIX: Automatically generate category channel slugs (#18879)
This commit automatically ensures that category channels
have slugs when they are created or updated based on the
channel name, category name, or existing slug. The behaviour
has been copied from the Category model.

We also include a backfill here with a simplified version
of Slug.for with deduplication to fill the slugs for already
created Category chat channels.

The channel slug is also now used for chat notifications,
and for the UI and navigation for chat. `slugifyChannel`
is still used, but now does the following fallback:

* Uses channel.slug if it is present
* Uses channel.escapedTitle if it is present
* Uses channel.title if it is present

In future we may want to remove this altogether
and always rely on the slug being present, but this
is currently not possible because we are not generating
slugs for DM channels at this point.
2022-11-09 10:28:31 +10:00

154 lines
4.2 KiB
Ruby

# frozen_string_literal: true
class ChatChannel < ActiveRecord::Base
include Trashable
belongs_to :chatable, polymorphic: true
belongs_to :direct_message,
-> { where(chat_channels: { chatable_type: "DirectMessage" }) },
foreign_key: "chatable_id"
has_many :chat_messages
has_many :user_chat_channel_memberships
has_one :chat_channel_archive
enum :status, { open: 0, read_only: 1, closed: 2, archived: 3 }, scopes: false
validates :name,
length: {
maximum: Proc.new { SiteSetting.max_topic_title_length },
},
presence: true,
allow_nil: true
validate :ensure_slug_ok
before_validation :generate_auto_slug
scope :public_channels,
-> {
where(chatable_type: public_channel_chatable_types).where(
"categories.id IS NOT NULL",
).joins(
"LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'",
)
}
delegate :empty?, to: :chat_messages, prefix: true
class << self
def public_channel_chatable_types
["Category"]
end
def chatable_types
public_channel_chatable_types << "DirectMessage"
end
end
statuses.keys.each do |status|
define_method("#{status}!") { |acting_user| change_status(acting_user, status.to_sym) }
end
%i[
category_channel?
direct_message_channel?
public_channel?
chatable_has_custom_fields?
read_restricted?
].each { |name| define_method(name) { false } }
%i[allowed_user_ids allowed_group_ids chatable_url].each { |name| define_method(name) { nil } }
def membership_for(user)
user_chat_channel_memberships.find_by(user: user)
end
def add(user)
Chat::ChatChannelMembershipManager.new(self).follow(user)
end
def remove(user)
Chat::ChatChannelMembershipManager.new(self).unfollow(user)
end
def status_name
I18n.t("chat.channel.statuses.#{self.status}")
end
def url
"#{Discourse.base_url}#{relative_url}"
end
def relative_url
"/chat/channel/#{self.id}/#{self.slug || "-"}"
end
def public_channel_title
chatable.name
end
private
def change_status(acting_user, target_status)
return if !Guardian.new(acting_user).can_change_channel_status?(self, target_status)
self.update!(status: target_status)
log_channel_status_change(acting_user: acting_user)
end
def log_channel_status_change(acting_user:)
DiscourseEvent.trigger(
:chat_channel_status_change,
channel: self,
old_status: status_previously_was,
new_status: status,
)
StaffActionLogger.new(acting_user).log_custom(
"chat_channel_status_change",
{
chat_channel_id: self.id,
chat_channel_name: self.name,
previous_value: status_previously_was,
new_value: status,
},
)
ChatPublisher.publish_channel_status(self)
end
def duplicate_slug?
ChatChannel.where(slug: self.slug).where.not(id: self.id).any?
end
end
# == Schema Information
#
# Table name: chat_channels
#
# id :bigint not null, primary key
# chatable_id :integer not null
# deleted_at :datetime
# deleted_by_id :integer
# featured_in_category_id :integer
# delete_after_seconds :integer
# chatable_type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# name :string
# description :text
# status :integer default("open"), not null
# user_count :integer default(0), not null
# last_message_sent_at :datetime not null
# auto_join_users :boolean default(FALSE), not null
# user_count_stale :boolean default(FALSE), not null
# slug :string
# type :string
#
# Indexes
#
# index_chat_channels_on_chatable_id (chatable_id)
# index_chat_channels_on_chatable_id_and_chatable_type (chatable_id,chatable_type)
# index_chat_channels_on_slug (slug)
# index_chat_channels_on_status (status)
#