2017-07-04 03:26:46 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-07-10 17:05:55 +08:00
|
|
|
require "net/imap"
|
|
|
|
|
2013-04-17 15:08:21 +08:00
|
|
|
class Group < ActiveRecord::Base
|
2020-05-26 03:13:50 +08:00
|
|
|
# TODO(2021-05-26): remove
|
2020-04-23 02:27:01 +08:00
|
|
|
self.ignored_columns = %w[flair_url]
|
|
|
|
|
2014-04-28 16:31:51 +08:00
|
|
|
include HasCustomFields
|
2016-12-22 13:46:22 +08:00
|
|
|
include AnonCacheInvalidator
|
2018-10-05 16:53:59 +08:00
|
|
|
include HasDestroyedWebHook
|
2021-02-19 04:24:44 +08:00
|
|
|
include GlobalPath
|
2014-04-25 21:14:05 +08:00
|
|
|
|
2017-09-29 23:04:05 +08:00
|
|
|
cattr_accessor :preloaded_custom_field_names
|
|
|
|
self.preloaded_custom_field_names = Set.new
|
2017-08-08 21:45:27 +08:00
|
|
|
|
2014-09-01 04:10:38 +08:00
|
|
|
has_many :category_groups, dependent: :destroy
|
2013-05-09 09:33:56 +08:00
|
|
|
has_many :group_users, dependent: :destroy
|
2019-03-27 19:30:59 +08:00
|
|
|
has_many :group_requests, dependent: :destroy
|
2015-12-01 13:52:43 +08:00
|
|
|
has_many :group_mentions, dependent: :destroy
|
2021-12-09 20:30:27 +08:00
|
|
|
has_many :group_associated_groups, dependent: :destroy
|
2013-04-29 14:33:24 +08:00
|
|
|
|
2015-12-23 08:09:17 +08:00
|
|
|
has_many :group_archived_messages, dependent: :destroy
|
|
|
|
|
2013-04-29 14:33:24 +08:00
|
|
|
has_many :categories, through: :category_groups
|
|
|
|
has_many :users, through: :group_users
|
2019-03-27 19:30:59 +08:00
|
|
|
has_many :requesters, through: :group_requests, source: :user
|
2016-12-11 23:36:15 +08:00
|
|
|
has_many :group_histories, dependent: :destroy
|
2019-04-18 05:12:32 +08:00
|
|
|
has_many :category_reviews,
|
|
|
|
class_name: "Category",
|
|
|
|
foreign_key: :reviewable_by_group_id,
|
|
|
|
dependent: :nullify
|
|
|
|
has_many :reviewables, foreign_key: :reviewable_by_group_id, dependent: :nullify
|
2020-08-07 00:27:27 +08:00
|
|
|
has_many :group_category_notification_defaults, dependent: :destroy
|
|
|
|
has_many :group_tag_notification_defaults, dependent: :destroy
|
2021-12-09 20:30:27 +08:00
|
|
|
has_many :associated_groups, through: :group_associated_groups, dependent: :destroy
|
2013-04-29 14:33:24 +08:00
|
|
|
|
2020-05-25 13:38:47 +08:00
|
|
|
belongs_to :flair_upload, class_name: "Upload"
|
2022-06-09 07:24:30 +08:00
|
|
|
has_many :upload_references, as: :target, dependent: :destroy
|
|
|
|
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
belongs_to :smtp_updated_by, class_name: "User"
|
|
|
|
belongs_to :imap_updated_by, class_name: "User"
|
2020-05-25 13:38:47 +08:00
|
|
|
|
2016-06-16 01:49:57 +08:00
|
|
|
has_and_belongs_to_many :web_hooks
|
|
|
|
|
2015-12-07 19:39:28 +08:00
|
|
|
before_save :downcase_incoming_email
|
2016-12-05 16:18:24 +08:00
|
|
|
before_save :cook_bio
|
2015-12-07 19:39:28 +08:00
|
|
|
|
2013-05-09 09:33:56 +08:00
|
|
|
after_save :destroy_deletions
|
2015-04-10 10:17:28 +08:00
|
|
|
after_save :update_primary_group
|
|
|
|
after_save :update_title
|
2013-05-09 09:33:56 +08:00
|
|
|
|
2017-01-18 13:39:34 +08:00
|
|
|
after_save :enqueue_update_mentions_job,
|
2017-08-31 12:06:56 +08:00
|
|
|
if: Proc.new { |g| g.name_before_last_save && g.saved_change_to_name? }
|
2017-01-18 13:39:34 +08:00
|
|
|
|
2022-06-09 07:24:30 +08:00
|
|
|
after_save do
|
|
|
|
if saved_change_to_flair_upload_id?
|
|
|
|
UploadReference.ensure_exist!(upload_ids: [self.flair_upload_id], target: self)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-28 14:43:38 +08:00
|
|
|
after_save :expire_cache
|
|
|
|
after_destroy :expire_cache
|
|
|
|
|
2018-09-20 11:24:01 +08:00
|
|
|
after_commit :automatic_group_membership, on: %i[create update]
|
2018-03-27 14:23:35 +08:00
|
|
|
after_commit :trigger_group_created_event, on: :create
|
2018-05-21 17:29:19 +08:00
|
|
|
after_commit :trigger_group_updated_event, on: :update
|
2018-03-27 14:23:35 +08:00
|
|
|
after_commit :trigger_group_destroyed_event, on: :destroy
|
2020-08-07 00:27:27 +08:00
|
|
|
after_commit :set_default_notifications, on: %i[create update]
|
2018-03-27 14:23:35 +08:00
|
|
|
|
2015-09-28 14:43:38 +08:00
|
|
|
def expire_cache
|
|
|
|
ApplicationSerializer.expire_cache_fragment!("group_names")
|
2018-11-27 05:49:57 +08:00
|
|
|
SvgSprite.expire_cache
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
expire_imap_mailbox_cache
|
|
|
|
end
|
|
|
|
|
|
|
|
def expire_imap_mailbox_cache
|
2020-07-10 17:05:55 +08:00
|
|
|
Discourse.cache.delete("group_imap_mailboxes_#{self.id}")
|
2015-09-28 14:43:38 +08:00
|
|
|
end
|
|
|
|
|
2019-04-18 05:12:32 +08:00
|
|
|
def remove_review_groups
|
|
|
|
Category.where(review_group_id: self.id).update_all(review_group_id: nil)
|
|
|
|
end
|
|
|
|
|
2018-04-03 00:44:04 +08:00
|
|
|
validate :name_format_validator
|
2018-04-02 18:17:06 +08:00
|
|
|
validates :name, presence: true
|
2015-11-27 13:35:16 +08:00
|
|
|
validate :automatic_membership_email_domains_format_validator
|
2015-12-07 19:39:28 +08:00
|
|
|
validate :incoming_email_validator
|
2017-07-27 14:39:47 +08:00
|
|
|
validate :can_allow_membership_requests, if: :allow_membership_requests
|
2018-04-06 17:11:00 +08:00
|
|
|
validate :validate_grant_trust_level, if: :will_save_change_to_grant_trust_level?
|
2013-05-09 15:37:34 +08:00
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
AUTO_GROUPS = {
|
2013-07-14 09:24:16 +08:00
|
|
|
everyone: 0,
|
2013-05-06 12:49:56 +08:00
|
|
|
admins: 1,
|
|
|
|
moderators: 2,
|
|
|
|
staff: 3,
|
2014-06-17 16:13:07 +08:00
|
|
|
trust_level_0: 10,
|
2013-05-06 12:49:56 +08:00
|
|
|
trust_level_1: 11,
|
|
|
|
trust_level_2: 12,
|
|
|
|
trust_level_3: 13,
|
2014-07-10 10:17:13 +08:00
|
|
|
trust_level_4: 14,
|
2013-05-06 12:49:56 +08:00
|
|
|
}
|
|
|
|
|
2014-07-10 10:17:13 +08:00
|
|
|
AUTO_GROUP_IDS = Hash[*AUTO_GROUPS.to_a.flatten.reverse]
|
2016-03-24 04:20:24 +08:00
|
|
|
STAFF_GROUPS = %i[admins moderators staff]
|
2014-06-17 08:46:30 +08:00
|
|
|
|
2022-08-11 22:33:41 +08:00
|
|
|
AUTO_GROUPS_ADD = "add"
|
|
|
|
AUTO_GROUPS_REMOVE = "remove"
|
|
|
|
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
IMAP_SETTING_ATTRIBUTES = %w[
|
|
|
|
imap_server
|
|
|
|
imap_port
|
|
|
|
imap_ssl
|
2021-06-17 06:21:06 +08:00
|
|
|
imap_mailbox_name
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
email_username
|
|
|
|
email_password
|
|
|
|
]
|
|
|
|
|
|
|
|
SMTP_SETTING_ATTRIBUTES = %w[
|
|
|
|
imap_server
|
|
|
|
imap_port
|
|
|
|
imap_ssl
|
|
|
|
email_username
|
2022-02-07 11:52:01 +08:00
|
|
|
email_password
|
|
|
|
email_from_alias
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
]
|
|
|
|
|
2014-01-08 00:47:01 +08:00
|
|
|
ALIAS_LEVELS = {
|
|
|
|
nobody: 0,
|
|
|
|
only_admins: 1,
|
|
|
|
mods_and_admins: 2,
|
|
|
|
members_mods_and_admins: 3,
|
2019-07-09 05:12:09 +08:00
|
|
|
owners_mods_and_admins: 4,
|
2014-01-08 00:47:01 +08:00
|
|
|
everyone: 99,
|
|
|
|
}
|
|
|
|
|
2020-05-26 13:40:09 +08:00
|
|
|
VALID_DOMAIN_REGEX = /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i
|
|
|
|
|
2017-07-04 03:26:46 +08:00
|
|
|
def self.visibility_levels
|
|
|
|
@visibility_levels = Enum.new(public: 0, logged_on_users: 1, members: 2, staff: 3, owners: 4)
|
|
|
|
end
|
|
|
|
|
2017-08-29 00:32:08 +08:00
|
|
|
validates :mentionable_level, inclusion: { in: ALIAS_LEVELS.values }
|
|
|
|
validates :messageable_level, inclusion: { in: ALIAS_LEVELS.values }
|
2014-01-08 00:47:01 +08:00
|
|
|
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
scope :with_imap_configured, -> { where(imap_enabled: true).where.not(imap_mailbox_name: "") }
|
2022-01-04 08:14:33 +08:00
|
|
|
scope :with_smtp_configured, -> { where(smtp_enabled: true) }
|
2020-08-14 10:01:31 +08:00
|
|
|
|
2019-02-15 07:24:29 +08:00
|
|
|
scope :visible_groups,
|
|
|
|
Proc.new { |user, order, opts|
|
2022-07-26 08:13:38 +08:00
|
|
|
groups = self
|
|
|
|
groups = groups.order(order) if order
|
|
|
|
groups = groups.order("groups.name ASC") unless order&.include?("name")
|
2019-02-15 07:24:29 +08:00
|
|
|
|
|
|
|
groups = groups.where("groups.id > 0") if !opts || !opts[:include_everyone]
|
2017-02-03 16:51:32 +08:00
|
|
|
|
2020-01-15 18:21:58 +08:00
|
|
|
if !user&.admin
|
2020-09-11 10:50:13 +08:00
|
|
|
is_staff = !!user&.staff?
|
|
|
|
|
|
|
|
if user.blank?
|
|
|
|
sql = "groups.visibility_level = :public"
|
|
|
|
elsif is_staff
|
|
|
|
sql = "groups.visibility_level IN (:public, :logged_on_users, :members, :staff)"
|
|
|
|
else
|
|
|
|
sql = <<~SQL
|
|
|
|
groups.id IN (
|
|
|
|
SELECT id
|
|
|
|
FROM groups
|
|
|
|
WHERE visibility_level IN (:public, :logged_on_users)
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT g.id
|
|
|
|
FROM groups g
|
|
|
|
JOIN group_users gu ON gu.group_id = g.id AND gu.user_id = :user_id
|
|
|
|
WHERE g.visibility_level = :members
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT g.id
|
|
|
|
FROM groups g
|
|
|
|
JOIN group_users gu ON gu.group_id = g.id AND gu.user_id = :user_id AND gu.owner
|
|
|
|
WHERE g.visibility_level IN (:staff, :owners)
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff)
|
2020-01-15 18:21:58 +08:00
|
|
|
groups = groups.where(sql, params)
|
2017-02-03 16:51:32 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
groups
|
|
|
|
}
|
|
|
|
|
2019-08-14 21:30:04 +08:00
|
|
|
scope :members_visible_groups,
|
|
|
|
Proc.new { |user, order, opts|
|
|
|
|
groups = self.order(order || "name ASC")
|
|
|
|
|
|
|
|
groups = groups.where("groups.id > 0") if !opts || !opts[:include_everyone]
|
|
|
|
|
2020-01-15 18:21:58 +08:00
|
|
|
if !user&.admin
|
2020-09-11 10:50:13 +08:00
|
|
|
is_staff = !!user&.staff?
|
|
|
|
|
|
|
|
if user.blank?
|
|
|
|
sql = "groups.members_visibility_level = :public"
|
|
|
|
elsif is_staff
|
|
|
|
sql =
|
|
|
|
"groups.members_visibility_level IN (:public, :logged_on_users, :members, :staff)"
|
|
|
|
else
|
|
|
|
sql = <<~SQL
|
|
|
|
groups.id IN (
|
|
|
|
SELECT id
|
|
|
|
FROM groups
|
|
|
|
WHERE members_visibility_level IN (:public, :logged_on_users)
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT g.id
|
|
|
|
FROM groups g
|
|
|
|
JOIN group_users gu ON gu.group_id = g.id AND gu.user_id = :user_id
|
|
|
|
WHERE g.members_visibility_level = :members
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
SELECT g.id
|
|
|
|
FROM groups g
|
|
|
|
JOIN group_users gu ON gu.group_id = g.id AND gu.user_id = :user_id AND gu.owner
|
|
|
|
WHERE g.members_visibility_level IN (:staff, :owners)
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
|
|
|
params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff)
|
2020-01-15 18:21:58 +08:00
|
|
|
groups = groups.where(sql, params)
|
2019-08-14 21:30:04 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
groups
|
|
|
|
}
|
|
|
|
|
2019-12-12 19:13:40 +08:00
|
|
|
scope :mentionable,
|
|
|
|
lambda { |user, include_public: true|
|
|
|
|
where(
|
|
|
|
self.mentionable_sql_clause(include_public: include_public),
|
2018-11-22 16:01:03 +08:00
|
|
|
levels: alias_levels(user),
|
|
|
|
user_id: user&.id,
|
|
|
|
)
|
2017-08-29 00:32:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
scope :messageable,
|
|
|
|
lambda { |user|
|
|
|
|
where(
|
|
|
|
"messageable_level in (:levels) OR
|
|
|
|
(
|
|
|
|
messageable_level = #{ALIAS_LEVELS[:members_mods_and_admins]} AND id in (
|
|
|
|
SELECT group_id FROM group_users WHERE user_id = :user_id)
|
2019-07-09 05:12:09 +08:00
|
|
|
) OR (
|
|
|
|
messageable_level = #{ALIAS_LEVELS[:owners_mods_and_admins]} AND id in (
|
|
|
|
SELECT group_id FROM group_users WHERE user_id = :user_id AND owner IS TRUE)
|
2017-08-29 00:32:08 +08:00
|
|
|
)",
|
|
|
|
levels: alias_levels(user),
|
|
|
|
user_id: user && user.id,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-12-12 19:13:40 +08:00
|
|
|
def self.mentionable_sql_clause(include_public: true)
|
|
|
|
clause = +<<~SQL
|
|
|
|
mentionable_level in (:levels)
|
|
|
|
OR (
|
|
|
|
mentionable_level = #{ALIAS_LEVELS[:members_mods_and_admins]}
|
|
|
|
AND id in (
|
|
|
|
SELECT group_id FROM group_users WHERE user_id = :user_id)
|
|
|
|
) OR (
|
|
|
|
mentionable_level = #{ALIAS_LEVELS[:owners_mods_and_admins]}
|
|
|
|
AND id in (
|
|
|
|
SELECT group_id FROM group_users WHERE user_id = :user_id AND owner IS TRUE)
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
|
|
|
|
clause << "OR visibility_level = #{Group.visibility_levels[:public]}" if include_public
|
|
|
|
|
|
|
|
clause
|
2018-11-22 16:01:03 +08:00
|
|
|
end
|
|
|
|
|
2017-08-29 00:32:08 +08:00
|
|
|
def self.alias_levels(user)
|
2020-01-15 18:21:58 +08:00
|
|
|
if user&.admin?
|
|
|
|
[
|
|
|
|
ALIAS_LEVELS[:everyone],
|
|
|
|
ALIAS_LEVELS[:only_admins],
|
|
|
|
ALIAS_LEVELS[:mods_and_admins],
|
|
|
|
ALIAS_LEVELS[:members_mods_and_admins],
|
|
|
|
ALIAS_LEVELS[:owners_mods_and_admins],
|
|
|
|
]
|
|
|
|
elsif user&.moderator?
|
|
|
|
[
|
|
|
|
ALIAS_LEVELS[:everyone],
|
|
|
|
ALIAS_LEVELS[:mods_and_admins],
|
|
|
|
ALIAS_LEVELS[:members_mods_and_admins],
|
|
|
|
ALIAS_LEVELS[:owners_mods_and_admins],
|
|
|
|
]
|
|
|
|
else
|
|
|
|
[ALIAS_LEVELS[:everyone]]
|
2015-11-30 14:03:47 +08:00
|
|
|
end
|
2017-08-29 00:32:08 +08:00
|
|
|
end
|
2015-11-30 14:03:47 +08:00
|
|
|
|
2022-02-07 11:52:01 +08:00
|
|
|
def smtp_from_address
|
|
|
|
self.email_from_alias.present? ? self.email_from_alias : self.email_username
|
|
|
|
end
|
|
|
|
|
2015-12-07 19:39:28 +08:00
|
|
|
def downcase_incoming_email
|
|
|
|
self.incoming_email = (incoming_email || "").strip.downcase.presence
|
|
|
|
end
|
|
|
|
|
2016-12-05 16:18:24 +08:00
|
|
|
def cook_bio
|
2019-06-12 14:18:38 +08:00
|
|
|
if self.bio_raw.present?
|
2016-12-05 16:18:24 +08:00
|
|
|
self.bio_cooked = PrettyText.cook(self.bio_raw)
|
2019-06-12 14:18:38 +08:00
|
|
|
else
|
|
|
|
self.bio_cooked = nil
|
2016-12-05 16:18:24 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
def record_email_setting_changes!(user)
|
|
|
|
if (self.previous_changes.keys & IMAP_SETTING_ATTRIBUTES).any?
|
|
|
|
self.imap_updated_at = Time.zone.now
|
|
|
|
self.imap_updated_by_id = user.id
|
|
|
|
end
|
|
|
|
|
|
|
|
if (self.previous_changes.keys & SMTP_SETTING_ATTRIBUTES).any?
|
|
|
|
self.smtp_updated_at = Time.zone.now
|
|
|
|
self.smtp_updated_by_id = user.id
|
|
|
|
end
|
|
|
|
|
|
|
|
self.smtp_enabled = [
|
|
|
|
self.smtp_port,
|
|
|
|
self.smtp_server,
|
|
|
|
self.email_password,
|
|
|
|
self.email_username,
|
|
|
|
].all?(&:present?)
|
|
|
|
self.imap_enabled = [
|
|
|
|
self.imap_port,
|
|
|
|
self.imap_server,
|
|
|
|
self.email_password,
|
2020-08-04 12:19:57 +08:00
|
|
|
self.email_username,
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
].all?(&:present?)
|
|
|
|
|
|
|
|
self.save
|
|
|
|
end
|
|
|
|
|
2015-12-07 19:39:28 +08:00
|
|
|
def incoming_email_validator
|
|
|
|
return if self.automatic || self.incoming_email.blank?
|
2016-07-28 23:57:30 +08:00
|
|
|
|
2016-02-25 02:47:58 +08:00
|
|
|
incoming_email
|
|
|
|
.split("|")
|
|
|
|
.each do |email|
|
2016-07-28 23:57:30 +08:00
|
|
|
escaped = Rack::Utils.escape_html(email)
|
2016-03-09 03:52:04 +08:00
|
|
|
if !Email.is_valid?(email)
|
2017-05-05 19:13:49 +08:00
|
|
|
self.errors.add(:base, I18n.t("groups.errors.invalid_incoming_email", email: escaped))
|
2016-03-09 03:52:04 +08:00
|
|
|
elsif group = Group.where.not(id: self.id).find_by_email(email)
|
2016-07-28 23:57:30 +08:00
|
|
|
self.errors.add(
|
|
|
|
:base,
|
|
|
|
I18n.t(
|
|
|
|
"groups.errors.email_already_used_in_group",
|
|
|
|
email: escaped,
|
|
|
|
group_name: Rack::Utils.escape_html(group.name),
|
2023-01-09 20:20:10 +08:00
|
|
|
),
|
2016-07-28 23:57:30 +08:00
|
|
|
)
|
2016-03-09 03:52:04 +08:00
|
|
|
elsif category = Category.find_by_email(email)
|
2016-07-28 23:57:30 +08:00
|
|
|
self.errors.add(
|
|
|
|
:base,
|
|
|
|
I18n.t(
|
|
|
|
"groups.errors.email_already_used_in_category",
|
|
|
|
email: escaped,
|
|
|
|
category_name: Rack::Utils.escape_html(category.name),
|
2023-01-09 20:20:10 +08:00
|
|
|
),
|
2016-07-28 23:57:30 +08:00
|
|
|
)
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2016-02-25 02:47:58 +08:00
|
|
|
end
|
2015-12-07 19:39:28 +08:00
|
|
|
end
|
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
def posts_for(guardian, opts = nil)
|
|
|
|
opts ||= {}
|
2018-11-12 10:26:41 +08:00
|
|
|
result =
|
|
|
|
Post
|
|
|
|
.joins(:topic, user: :groups, topic: :category)
|
|
|
|
.preload(:topic, user: :groups, topic: :category)
|
2015-12-08 06:19:33 +08:00
|
|
|
.references(:posts, :topics, :category)
|
2018-11-12 08:44:20 +08:00
|
|
|
.where(groups: { id: id })
|
2014-03-14 02:06:22 +08:00
|
|
|
.where("topics.archetype <> ?", Archetype.private_message)
|
2018-03-01 03:39:11 +08:00
|
|
|
.where("topics.visible")
|
2020-12-11 03:55:53 +08:00
|
|
|
.where(post_type: [Post.types[:regular], Post.types[:moderator_action]])
|
2014-02-07 23:44:03 +08:00
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
if opts[:category_id].present?
|
|
|
|
result = result.where("topics.category_id = ?", opts[:category_id].to_i)
|
|
|
|
end
|
|
|
|
|
2015-02-13 00:52:59 +08:00
|
|
|
result = guardian.filter_allowed_categories(result)
|
2017-11-01 04:47:47 +08:00
|
|
|
result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id]
|
2015-12-01 13:52:43 +08:00
|
|
|
result.order("posts.created_at desc")
|
|
|
|
end
|
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
def messages_for(guardian, opts = nil)
|
|
|
|
opts ||= {}
|
|
|
|
|
2015-12-08 06:19:33 +08:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
if opts[:category_id].present?
|
|
|
|
result = result.where("topics.category_id = ?", opts[:category_id].to_i)
|
|
|
|
end
|
|
|
|
|
2015-12-08 06:19:33 +08:00
|
|
|
result = guardian.filter_allowed_categories(result)
|
2017-11-01 04:47:47 +08:00
|
|
|
result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id]
|
2015-12-08 06:19:33 +08:00
|
|
|
result.order("posts.created_at desc")
|
|
|
|
end
|
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
def mentioned_posts_for(guardian, opts = nil)
|
|
|
|
opts ||= {}
|
2015-12-01 13:52:43 +08:00
|
|
|
result =
|
|
|
|
Post
|
|
|
|
.joins(:group_mentions)
|
2015-12-08 06:19:33 +08:00
|
|
|
.includes(:user, :topic, topic: :category)
|
2015-12-01 13:52:43 +08:00
|
|
|
.references(:posts, :topics, :category)
|
|
|
|
.where("topics.archetype <> ?", Archetype.private_message)
|
|
|
|
.where(post_type: Post.types[:regular])
|
2015-12-02 13:16:19 +08:00
|
|
|
.where("group_mentions.group_id = ?", self.id)
|
2015-12-01 13:52:43 +08:00
|
|
|
|
2017-11-01 04:47:47 +08:00
|
|
|
if opts[:category_id].present?
|
|
|
|
result = result.where("topics.category_id = ?", opts[:category_id].to_i)
|
|
|
|
end
|
|
|
|
|
2015-12-01 13:52:43 +08:00
|
|
|
result = guardian.filter_allowed_categories(result)
|
2017-11-01 04:47:47 +08:00
|
|
|
result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id]
|
2014-02-13 03:00:45 +08:00
|
|
|
result.order("posts.created_at desc")
|
2014-02-07 23:44:03 +08:00
|
|
|
end
|
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
def self.trust_group_ids
|
|
|
|
(10..19).to_a
|
|
|
|
end
|
|
|
|
|
2019-08-16 16:23:51 +08:00
|
|
|
def set_message_default_notification_levels!(topic, ignore_existing: false)
|
|
|
|
group_users
|
|
|
|
.pluck(:user_id, :notification_level)
|
|
|
|
.each do |user_id, notification_level|
|
|
|
|
next if user_id == -1
|
|
|
|
next if user_id == topic.user_id
|
|
|
|
next if ignore_existing && TopicUser.where(user_id: user_id, topic_id: topic.id).exists?
|
2023-01-09 20:20:10 +08:00
|
|
|
|
2019-08-16 16:23:51 +08:00
|
|
|
action =
|
|
|
|
case notification_level
|
|
|
|
when TopicUser.notification_levels[:tracking]
|
|
|
|
"track!"
|
|
|
|
when TopicUser.notification_levels[:regular]
|
|
|
|
"regular!"
|
|
|
|
when TopicUser.notification_levels[:muted]
|
|
|
|
"mute!"
|
|
|
|
when TopicUser.notification_levels[:watching]
|
|
|
|
"watch!"
|
|
|
|
else
|
|
|
|
"track!"
|
|
|
|
end
|
2023-01-09 20:20:10 +08:00
|
|
|
|
2019-08-16 16:23:51 +08:00
|
|
|
topic.notifier.public_send(action, user_id)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-07 00:27:27 +08:00
|
|
|
def self.set_category_and_tag_default_notification_levels!(user, group_name)
|
|
|
|
if group = lookup_group(group_name)
|
|
|
|
GroupUser.set_category_notifications(group, user)
|
|
|
|
GroupUser.set_tag_notifications(group, user)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
def self.refresh_automatic_group!(name)
|
2015-12-08 06:19:33 +08:00
|
|
|
return unless id = AUTO_GROUPS[name]
|
2013-05-06 12:49:56 +08:00
|
|
|
|
2013-05-09 15:37:34 +08:00
|
|
|
unless group = self.lookup_group(name)
|
|
|
|
group = Group.new(name: name.to_s, automatic: true)
|
2017-12-14 10:53:21 +08:00
|
|
|
|
|
|
|
if AUTO_GROUPS[:moderators] == id
|
|
|
|
group.default_notification_level = 2
|
|
|
|
group.messageable_level = ALIAS_LEVELS[:everyone]
|
|
|
|
end
|
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
group.id = id
|
|
|
|
group.save!
|
|
|
|
end
|
|
|
|
|
2013-06-23 12:44:16 +08:00
|
|
|
# don't allow shoddy localization to break this
|
2021-07-13 23:08:51 +08:00
|
|
|
localized_name = I18n.t("groups.default_names.#{name}", locale: SiteSetting.default_locale)
|
2016-10-24 16:53:31 +08:00
|
|
|
validator = UsernameValidator.new(localized_name)
|
2017-01-18 12:20:23 +08:00
|
|
|
|
2019-02-20 05:31:03 +08:00
|
|
|
group.name = localized_name if validator.valid_format? && !User.username_exists?(localized_name)
|
2013-06-23 12:44:16 +08:00
|
|
|
|
2014-09-05 06:15:37 +08:00
|
|
|
# the everyone group is special, it can include non-users so there is no
|
|
|
|
# way to have the membership in a table
|
2017-12-19 10:13:58 +08:00
|
|
|
case name
|
|
|
|
when :everyone
|
2019-03-06 14:44:08 +08:00
|
|
|
group.visibility_level = Group.visibility_levels[:staff]
|
2014-09-05 06:15:37 +08:00
|
|
|
group.save!
|
|
|
|
return group
|
2017-12-19 10:13:58 +08:00
|
|
|
when :moderators
|
|
|
|
group.update!(messageable_level: ALIAS_LEVELS[:everyone])
|
2014-09-05 06:15:37 +08:00
|
|
|
end
|
|
|
|
|
2019-07-09 03:09:50 +08:00
|
|
|
if group.visibility_level == Group.visibility_levels[:public]
|
|
|
|
group.update!(visibility_level: Group.visibility_levels[:logged_on_users])
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2019-07-09 03:09:50 +08:00
|
|
|
|
2014-08-12 01:30:51 +08:00
|
|
|
# Remove people from groups they don't belong in.
|
2017-04-25 04:48:32 +08:00
|
|
|
remove_subquery =
|
|
|
|
case name
|
|
|
|
when :admins
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id <= 0 OR NOT admin OR staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :moderators
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id <= 0 OR NOT moderator OR staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :staff
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id <= 0 OR (NOT admin AND NOT moderator) OR staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :trust_level_0, :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id <= 0 OR trust_level < #{id - 10} OR staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
end
|
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
removed_user_ids = DB.query_single <<-SQL
|
2017-04-25 04:48:32 +08:00
|
|
|
DELETE FROM group_users
|
|
|
|
USING (#{remove_subquery}) X
|
|
|
|
WHERE group_id = #{group.id}
|
|
|
|
AND user_id = X.id
|
2022-07-27 22:34:08 +08:00
|
|
|
RETURNING group_users.user_id
|
2017-04-25 04:48:32 +08:00
|
|
|
SQL
|
2014-08-12 01:30:51 +08:00
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
if removed_user_ids.present?
|
|
|
|
Jobs.enqueue(
|
|
|
|
:publish_group_membership_updates,
|
2022-08-11 22:33:41 +08:00
|
|
|
user_ids: removed_user_ids,
|
|
|
|
group_id: group.id,
|
|
|
|
type: AUTO_GROUPS_REMOVE,
|
2022-07-27 22:34:08 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2014-08-12 01:30:51 +08:00
|
|
|
# Add people to groups
|
2017-04-25 04:48:32 +08:00
|
|
|
insert_subquery =
|
|
|
|
case name
|
|
|
|
when :admins
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id > 0 AND admin AND NOT staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :moderators
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id > 0 AND moderator AND NOT staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :staff
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id > 0 AND (moderator OR admin) AND NOT staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :trust_level_1, :trust_level_2, :trust_level_3, :trust_level_4
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id > 0 AND trust_level >= #{id - 10} AND NOT staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
when :trust_level_0
|
2019-06-17 13:10:47 +08:00
|
|
|
"SELECT id FROM users WHERE id > 0 AND NOT staged"
|
2017-04-25 04:48:32 +08:00
|
|
|
end
|
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
added_user_ids = DB.query_single <<-SQL
|
2017-04-25 04:48:32 +08:00
|
|
|
INSERT INTO group_users (group_id, user_id, created_at, updated_at)
|
|
|
|
SELECT #{group.id}, X.id, now(), now()
|
|
|
|
FROM group_users
|
|
|
|
RIGHT JOIN (#{insert_subquery}) X ON X.id = user_id AND group_id = #{group.id}
|
|
|
|
WHERE user_id IS NULL
|
2022-07-27 22:34:08 +08:00
|
|
|
RETURNING group_users.user_id
|
2017-04-25 04:48:32 +08:00
|
|
|
SQL
|
2013-05-06 12:49:56 +08:00
|
|
|
|
|
|
|
group.save!
|
2013-05-08 13:20:38 +08:00
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
if added_user_ids.present?
|
|
|
|
Jobs.enqueue(
|
|
|
|
:publish_group_membership_updates,
|
2022-08-11 22:33:41 +08:00
|
|
|
user_ids: added_user_ids,
|
|
|
|
group_id: group.id,
|
|
|
|
type: AUTO_GROUPS_ADD,
|
2022-07-27 22:34:08 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2013-05-08 13:20:38 +08:00
|
|
|
# we want to ensure consistency
|
2022-11-30 22:52:08 +08:00
|
|
|
Group.reset_user_count(group)
|
2013-05-09 15:37:34 +08:00
|
|
|
|
|
|
|
group
|
2013-05-06 12:49:56 +08:00
|
|
|
end
|
|
|
|
|
2016-04-04 23:03:18 +08:00
|
|
|
def self.ensure_consistency!
|
2016-04-05 05:41:49 +08:00
|
|
|
reset_all_counters!
|
|
|
|
refresh_automatic_groups!
|
2017-09-29 00:34:19 +08:00
|
|
|
refresh_has_messages!
|
2016-04-04 23:03:18 +08:00
|
|
|
end
|
|
|
|
|
2022-11-30 22:52:08 +08:00
|
|
|
def self.reset_user_count(group)
|
|
|
|
reset_groups_user_count!(only_group_ids: [group.id])
|
|
|
|
end
|
|
|
|
|
2016-04-05 05:41:49 +08:00
|
|
|
def self.reset_all_counters!
|
2022-11-30 22:52:08 +08:00
|
|
|
reset_groups_user_count!
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.reset_groups_user_count!(only_group_ids: [])
|
|
|
|
where_sql = ""
|
|
|
|
|
|
|
|
if only_group_ids.present?
|
|
|
|
where_sql = "WHERE group_id IN (#{only_group_ids.map(&:to_i).join(",")})"
|
|
|
|
end
|
|
|
|
|
2018-06-19 14:13:14 +08:00
|
|
|
DB.exec <<-SQL
|
2017-04-25 04:48:32 +08:00
|
|
|
WITH X AS (
|
2022-11-30 22:52:08 +08:00
|
|
|
SELECT
|
|
|
|
group_id,
|
|
|
|
COUNT(user_id) users
|
|
|
|
FROM group_users
|
|
|
|
#{where_sql}
|
2017-04-25 04:48:32 +08:00
|
|
|
GROUP BY group_id
|
|
|
|
)
|
|
|
|
UPDATE groups
|
|
|
|
SET user_count = X.users
|
|
|
|
FROM X
|
|
|
|
WHERE id = X.group_id
|
|
|
|
AND user_count <> X.users
|
|
|
|
SQL
|
2016-04-04 23:03:18 +08:00
|
|
|
end
|
2022-11-30 22:52:08 +08:00
|
|
|
private_class_method :reset_groups_user_count!
|
2016-04-04 23:03:18 +08:00
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
def self.refresh_automatic_groups!(*args)
|
2017-04-25 04:48:32 +08:00
|
|
|
args = AUTO_GROUPS.keys if args.empty?
|
|
|
|
args.each { |group| refresh_automatic_group!(group) }
|
2013-05-06 12:49:56 +08:00
|
|
|
end
|
|
|
|
|
2017-09-29 00:34:19 +08:00
|
|
|
def self.refresh_has_messages!
|
2018-06-19 14:13:14 +08:00
|
|
|
DB.exec <<-SQL
|
2017-09-29 00:34:19 +08:00
|
|
|
UPDATE groups g SET has_messages = false
|
|
|
|
WHERE NOT EXISTS (SELECT tg.id
|
|
|
|
FROM topic_allowed_groups tg
|
|
|
|
INNER JOIN topics t ON t.id = tg.topic_id
|
|
|
|
WHERE tg.group_id = g.id
|
|
|
|
AND t.deleted_at IS NULL)
|
|
|
|
AND g.has_messages = true
|
|
|
|
SQL
|
|
|
|
end
|
|
|
|
|
2013-11-18 09:53:14 +08:00
|
|
|
def self.ensure_automatic_groups!
|
|
|
|
AUTO_GROUPS.each_key { |name| refresh_automatic_group!(name) unless lookup_group(name) }
|
|
|
|
end
|
|
|
|
|
2013-05-06 12:49:56 +08:00
|
|
|
def self.[](name)
|
2013-05-17 07:03:30 +08:00
|
|
|
lookup_group(name) || refresh_automatic_group!(name)
|
2013-05-09 15:37:34 +08:00
|
|
|
end
|
2013-05-06 12:49:56 +08:00
|
|
|
|
2022-03-03 13:57:52 +08:00
|
|
|
def self.search_groups(name, groups: nil, custom_scope: {}, sort: :none)
|
2021-09-06 08:18:51 +08:00
|
|
|
groups ||= Group
|
|
|
|
|
2022-03-03 13:57:52 +08:00
|
|
|
relation =
|
2017-11-03 21:39:55 +08:00
|
|
|
groups.where("name ILIKE :term_like OR full_name ILIKE :term_like", term_like: "%#{name}%")
|
2022-03-03 13:57:52 +08:00
|
|
|
|
|
|
|
if sort == :auto
|
|
|
|
prefix = "#{name.gsub("_", "\\_")}%"
|
|
|
|
relation =
|
|
|
|
relation.reorder(
|
|
|
|
DB.sql_fragment(
|
|
|
|
"CASE WHEN name ILIKE :like OR full_name ILIKE :like THEN 0 ELSE 1 END ASC, name ASC",
|
|
|
|
like: prefix,
|
2023-01-09 20:20:10 +08:00
|
|
|
),
|
2022-03-03 13:57:52 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
relation
|
2013-12-23 22:46:00 +08:00
|
|
|
end
|
|
|
|
|
2013-05-09 15:37:34 +08:00
|
|
|
def self.lookup_group(name)
|
2013-11-23 02:18:45 +08:00
|
|
|
if id = AUTO_GROUPS[name]
|
2014-05-06 21:41:59 +08:00
|
|
|
Group.find_by(id: id)
|
2013-07-23 08:10:36 +08:00
|
|
|
else
|
2014-05-06 21:41:59 +08:00
|
|
|
unless group = Group.find_by(name: name)
|
2013-11-23 02:18:45 +08:00
|
|
|
raise ArgumentError, "unknown group"
|
2013-07-23 08:10:36 +08:00
|
|
|
end
|
|
|
|
group
|
|
|
|
end
|
2013-05-06 12:49:56 +08:00
|
|
|
end
|
|
|
|
|
2017-07-21 14:12:24 +08:00
|
|
|
def self.lookup_groups(group_ids: [], group_names: [])
|
|
|
|
if group_ids.present?
|
2020-07-13 20:06:49 +08:00
|
|
|
group_ids = group_ids.split(",") if group_ids.is_a?(String)
|
2017-07-21 14:12:24 +08:00
|
|
|
group_ids.map!(&:to_i)
|
|
|
|
groups = Group.where(id: group_ids) if group_ids.present?
|
2014-05-09 16:22:15 +08:00
|
|
|
end
|
|
|
|
|
2017-07-21 14:12:24 +08:00
|
|
|
if group_names.present?
|
2014-05-09 16:22:15 +08:00
|
|
|
group_names = group_names.split(",")
|
2017-07-21 14:12:24 +08:00
|
|
|
groups = (groups || Group).where(name: group_names) if group_names.present?
|
2014-05-09 16:22:15 +08:00
|
|
|
end
|
|
|
|
|
2017-07-21 14:12:24 +08:00
|
|
|
groups || []
|
2014-05-09 16:22:15 +08:00
|
|
|
end
|
|
|
|
|
2014-06-17 08:46:30 +08:00
|
|
|
def self.desired_trust_level_groups(trust_level)
|
2014-06-17 16:13:07 +08:00
|
|
|
trust_group_ids.keep_if { |id| id == AUTO_GROUPS[:trust_level_0] || (trust_level + 10) >= id }
|
2014-06-17 08:46:30 +08:00
|
|
|
end
|
2013-05-06 12:49:56 +08:00
|
|
|
|
|
|
|
def self.user_trust_level_change!(user_id, trust_level)
|
2014-06-17 08:46:30 +08:00
|
|
|
desired = desired_trust_level_groups(trust_level)
|
|
|
|
undesired = trust_group_ids - desired
|
2013-05-06 12:49:56 +08:00
|
|
|
|
2014-06-17 08:46:30 +08:00
|
|
|
GroupUser.where(group_id: undesired, user_id: user_id).delete_all
|
2013-05-06 12:49:56 +08:00
|
|
|
|
2014-06-17 08:46:30 +08:00
|
|
|
desired.each do |id|
|
|
|
|
if group = find_by(id: id)
|
|
|
|
unless GroupUser.where(group_id: id, user_id: user_id).exists?
|
2021-06-01 16:34:41 +08:00
|
|
|
group_user = group.group_users.create!(user_id: user_id)
|
2022-07-27 22:34:08 +08:00
|
|
|
group.trigger_user_added_event(group_user.user, true)
|
2014-06-17 08:46:30 +08:00
|
|
|
end
|
|
|
|
else
|
|
|
|
name = AUTO_GROUP_IDS[trust_level]
|
|
|
|
refresh_automatic_group!(name)
|
|
|
|
end
|
2013-05-06 12:49:56 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-27 05:04:55 +08:00
|
|
|
# given something that might be a group name, id, or record, return the group id
|
|
|
|
def self.group_id_from_param(group_param)
|
|
|
|
return group_param.id if group_param.is_a?(Group)
|
|
|
|
return group_param if group_param.is_a?(Integer)
|
|
|
|
|
|
|
|
# subtle, using Group[] ensures the group exists in the DB
|
|
|
|
Group[group_param.to_sym].id
|
|
|
|
end
|
|
|
|
|
2013-04-17 15:08:21 +08:00
|
|
|
def self.builtin
|
|
|
|
Enum.new(:moderators, :admins, :trust_level_1, :trust_level_2)
|
|
|
|
end
|
2013-04-29 14:33:24 +08:00
|
|
|
|
2013-05-09 09:33:56 +08:00
|
|
|
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 { |gu| @deletions << gu if deletions.include?(gu.user_id) }
|
|
|
|
|
|
|
|
additions.each { |a| group_users.build(user_id: map[a]) }
|
|
|
|
end
|
|
|
|
|
|
|
|
def usernames
|
2013-05-18 03:11:37 +08:00
|
|
|
users.pluck(:username).join(",")
|
2013-05-09 09:33:56 +08:00
|
|
|
end
|
|
|
|
|
2017-08-16 10:38:30 +08:00
|
|
|
PUBLISH_CATEGORIES_LIMIT = 10
|
|
|
|
|
2019-09-11 00:58:08 +08:00
|
|
|
def add(user, notify: false, automatic: false)
|
2020-05-26 21:28:03 +08:00
|
|
|
return self if self.users.include?(user)
|
|
|
|
|
|
|
|
self.users.push(user)
|
2017-06-02 16:38:14 +08:00
|
|
|
|
2019-08-06 18:29:46 +08:00
|
|
|
if notify
|
|
|
|
Notification.create!(
|
|
|
|
notification_type: Notification.types[:membership_request_accepted],
|
|
|
|
user_id: user.id,
|
2020-05-26 21:28:03 +08:00
|
|
|
data: { group_id: id, group_name: name }.to_json,
|
2019-08-06 18:29:46 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2017-08-16 10:38:30 +08:00
|
|
|
if self.categories.count < PUBLISH_CATEGORIES_LIMIT
|
|
|
|
MessageBus.publish(
|
|
|
|
"/categories",
|
|
|
|
{ categories: ActiveModel::ArraySerializer.new(self.categories).as_json },
|
|
|
|
user_ids: [user.id],
|
|
|
|
)
|
|
|
|
else
|
|
|
|
Discourse.request_refresh!(user_ids: [user.id])
|
|
|
|
end
|
2017-06-02 16:38:14 +08:00
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
trigger_user_added_event(user, automatic)
|
2019-09-11 00:58:08 +08:00
|
|
|
|
2016-12-11 23:36:15 +08:00
|
|
|
self
|
2013-04-29 14:33:24 +08:00
|
|
|
end
|
2014-08-18 19:04:08 +08:00
|
|
|
|
2014-11-21 01:29:56 +08:00
|
|
|
def remove(user)
|
2021-04-08 23:46:34 +08:00
|
|
|
group_user = self.group_users.find_by(user: user)
|
|
|
|
return false if group_user.blank?
|
|
|
|
|
|
|
|
has_webhooks = WebHook.active_web_hooks(:group_user)
|
|
|
|
payload =
|
|
|
|
WebHook.generate_payload(:group_user, group_user, WebHookGroupUserSerializer) if has_webhooks
|
|
|
|
group_user.destroy
|
2022-07-27 22:34:08 +08:00
|
|
|
trigger_user_removed_event(user)
|
2021-04-08 23:46:34 +08:00
|
|
|
if has_webhooks
|
|
|
|
WebHook.enqueue_hooks(
|
|
|
|
:group_user,
|
|
|
|
:user_removed_from_group,
|
|
|
|
id: group_user.id,
|
|
|
|
payload: payload,
|
|
|
|
)
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2020-03-11 05:25:00 +08:00
|
|
|
true
|
2014-11-21 01:29:56 +08:00
|
|
|
end
|
|
|
|
|
2022-07-27 22:34:08 +08:00
|
|
|
def trigger_user_added_event(user, automatic)
|
|
|
|
DiscourseEvent.trigger(:user_added_to_group, user, self, automatic: automatic)
|
|
|
|
end
|
|
|
|
|
|
|
|
def trigger_user_removed_event(user)
|
|
|
|
DiscourseEvent.trigger(:user_removed_from_group, user, self)
|
|
|
|
end
|
|
|
|
|
2015-11-09 21:52:04 +08:00
|
|
|
def add_owner(user)
|
2016-11-29 16:25:02 +08:00
|
|
|
if group_user = self.group_users.find_by(user: user)
|
2017-07-27 14:39:47 +08:00
|
|
|
group_user.update!(owner: true) if !group_user.owner
|
2016-11-29 16:25:02 +08:00
|
|
|
else
|
2017-07-27 14:39:47 +08:00
|
|
|
self.group_users.create!(user: user, owner: true)
|
2016-11-29 16:25:02 +08:00
|
|
|
end
|
2015-01-09 07:35:52 +08:00
|
|
|
end
|
|
|
|
|
2015-12-08 00:01:08 +08:00
|
|
|
def self.find_by_email(email)
|
2021-06-03 12:47:32 +08:00
|
|
|
self.where(
|
2022-02-07 11:52:01 +08:00
|
|
|
"email_username = :email OR
|
|
|
|
string_to_array(incoming_email, '|') @> ARRAY[:email] OR
|
|
|
|
email_from_alias = :email",
|
2021-06-03 12:47:32 +08:00
|
|
|
email: Email.downcase(email),
|
|
|
|
).first
|
2015-12-08 00:01:08 +08:00
|
|
|
end
|
|
|
|
|
2016-02-19 03:03:00 +08:00
|
|
|
def bulk_add(user_ids)
|
2017-07-27 14:39:47 +08:00
|
|
|
return unless user_ids.present?
|
|
|
|
|
|
|
|
Group.transaction do
|
2017-07-26 16:10:13 +08:00
|
|
|
sql = <<~SQL
|
|
|
|
INSERT INTO group_users
|
|
|
|
(group_id, user_id, created_at, updated_at)
|
|
|
|
SELECT
|
|
|
|
#{self.id},
|
|
|
|
u.id,
|
|
|
|
CURRENT_TIMESTAMP,
|
|
|
|
CURRENT_TIMESTAMP
|
|
|
|
FROM users AS u
|
|
|
|
WHERE u.id IN (:user_ids)
|
|
|
|
AND NOT EXISTS (
|
|
|
|
SELECT 1 FROM group_users AS gu
|
|
|
|
WHERE gu.user_id = u.id AND
|
|
|
|
gu.group_id = :group_id
|
|
|
|
)
|
|
|
|
SQL
|
|
|
|
|
2018-06-19 14:13:14 +08:00
|
|
|
DB.exec(sql, group_id: self.id, user_ids: user_ids)
|
2016-02-19 03:03:00 +08:00
|
|
|
|
2017-07-26 16:22:21 +08:00
|
|
|
user_attributes = {}
|
2017-07-26 16:18:56 +08:00
|
|
|
|
2017-07-26 16:22:21 +08:00
|
|
|
user_attributes[:primary_group_id] = self.id if self.primary_group?
|
2016-02-19 03:03:00 +08:00
|
|
|
|
2017-07-26 16:22:21 +08:00
|
|
|
user_attributes[:title] = self.title if self.title.present?
|
2017-07-26 16:18:56 +08:00
|
|
|
|
2017-07-26 16:22:21 +08:00
|
|
|
User.where(id: user_ids).update_all(user_attributes) if user_attributes.present?
|
2018-02-23 16:39:49 +08:00
|
|
|
|
|
|
|
# update group user count
|
2018-06-19 14:13:14 +08:00
|
|
|
DB.exec <<~SQL
|
2018-02-23 16:39:49 +08:00
|
|
|
UPDATE groups g
|
|
|
|
SET user_count =
|
|
|
|
(SELECT COUNT(gu.user_id)
|
|
|
|
FROM group_users gu
|
|
|
|
WHERE gu.group_id = g.id)
|
|
|
|
WHERE g.id = #{self.id};
|
|
|
|
SQL
|
2017-07-27 14:39:47 +08:00
|
|
|
end
|
2017-03-03 02:16:01 +08:00
|
|
|
|
2017-07-27 14:39:47 +08:00
|
|
|
if self.grant_trust_level.present?
|
|
|
|
Jobs.enqueue(:bulk_grant_trust_level, user_ids: user_ids, trust_level: self.grant_trust_level)
|
2016-02-19 03:03:00 +08:00
|
|
|
end
|
2017-07-27 14:39:47 +08:00
|
|
|
|
|
|
|
self
|
2016-02-19 03:03:00 +08:00
|
|
|
end
|
|
|
|
|
2021-12-09 20:30:27 +08:00
|
|
|
def add_automatically(user, subject: nil)
|
|
|
|
if users.exclude?(user) && add(user)
|
|
|
|
logger = GroupActionLogger.new(Discourse.system_user, self)
|
|
|
|
logger.log_add_user_to_group(user, subject)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_automatically(user, subject: nil)
|
|
|
|
if users.include?(user) && remove(user)
|
|
|
|
logger = GroupActionLogger.new(Discourse.system_user, self)
|
|
|
|
logger.log_remove_user_from_group(user, subject)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-24 04:20:24 +08:00
|
|
|
def staff?
|
|
|
|
STAFF_GROUPS.include?(self.name.to_sym)
|
|
|
|
end
|
|
|
|
|
2018-03-20 15:50:46 +08:00
|
|
|
def self.member_of(groups, user)
|
2020-01-15 18:21:58 +08:00
|
|
|
groups.joins("LEFT JOIN group_users gu ON gu.group_id = groups.id ").where(
|
|
|
|
"gu.user_id = ?",
|
|
|
|
user.id,
|
|
|
|
)
|
2018-03-20 15:50:46 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.owner_of(groups, user)
|
|
|
|
self.member_of(groups, user).where("gu.owner")
|
|
|
|
end
|
|
|
|
|
2018-05-21 17:29:19 +08:00
|
|
|
%i[group_created group_updated group_destroyed].each do |event|
|
|
|
|
define_method("trigger_#{event}_event") do
|
|
|
|
DiscourseEvent.trigger(event, self)
|
|
|
|
true
|
|
|
|
end
|
2018-03-27 14:23:35 +08:00
|
|
|
end
|
|
|
|
|
2020-05-25 13:38:47 +08:00
|
|
|
def flair_type
|
|
|
|
return :icon if flair_icon.present?
|
2021-04-08 22:54:09 +08:00
|
|
|
return :image if flair_upload.present?
|
2020-05-25 13:38:47 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def flair_url
|
2021-08-06 23:23:23 +08:00
|
|
|
return flair_icon if flair_type == :icon
|
|
|
|
return upload_cdn_path(flair_upload.url) if flair_type == :image
|
|
|
|
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2020-08-14 05:20:23 +08:00
|
|
|
%i[muted regular tracking watching watching_first_post].each do |level|
|
2020-08-07 00:27:27 +08:00
|
|
|
define_method("#{level}_category_ids=") do |category_ids|
|
|
|
|
@category_notifications ||= {}
|
|
|
|
@category_notifications[level] = category_ids
|
|
|
|
end
|
|
|
|
|
|
|
|
define_method("#{level}_tags=") do |tag_names|
|
|
|
|
@tag_notifications ||= {}
|
|
|
|
@tag_notifications[level] = tag_names
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_default_notifications
|
|
|
|
if @category_notifications
|
|
|
|
@category_notifications.each do |level, category_ids|
|
|
|
|
GroupCategoryNotificationDefault.batch_set(self, level, category_ids)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if @tag_notifications
|
|
|
|
@tag_notifications.each do |level, tag_names|
|
|
|
|
GroupTagNotificationDefault.batch_set(self, level, tag_names)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-07-10 17:05:55 +08:00
|
|
|
def imap_mailboxes
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
return [] if !self.imap_enabled || !SiteSetting.enable_imap
|
2020-07-10 17:05:55 +08:00
|
|
|
|
|
|
|
Discourse
|
|
|
|
.cache
|
|
|
|
.fetch("group_imap_mailboxes_#{self.id}", expires_in: 30.minutes) do
|
|
|
|
Rails.logger.info("[IMAP] Refreshing mailboxes list for group #{self.name}")
|
|
|
|
mailboxes = []
|
2023-01-09 20:20:10 +08:00
|
|
|
|
|
|
|
begin
|
2020-08-04 12:19:57 +08:00
|
|
|
imap_provider = Imap::Providers::Detector.init_with_detected_provider(self.imap_config)
|
|
|
|
imap_provider.connect!
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
mailboxes = imap_provider.filter_mailboxes(imap_provider.list_mailboxes_with_attributes)
|
2020-08-04 12:19:57 +08:00
|
|
|
imap_provider.disconnect!
|
2023-01-09 20:20:10 +08:00
|
|
|
|
2020-07-10 17:05:55 +08:00
|
|
|
update_columns(imap_last_error: nil)
|
|
|
|
rescue => ex
|
2020-08-04 12:19:57 +08:00
|
|
|
Rails.logger.warn(
|
|
|
|
"[IMAP] Mailbox refresh failed for group #{self.name} with error: #{ex}",
|
2023-01-09 20:20:10 +08:00
|
|
|
)
|
2020-07-10 17:05:55 +08:00
|
|
|
update_columns(imap_last_error: ex.message)
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2020-07-10 17:05:55 +08:00
|
|
|
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
mailboxes
|
2020-07-10 17:05:55 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-04 12:19:57 +08:00
|
|
|
def imap_config
|
|
|
|
{
|
|
|
|
server: self.imap_server,
|
|
|
|
port: self.imap_port,
|
|
|
|
ssl: self.imap_ssl,
|
|
|
|
username: self.email_username,
|
|
|
|
password: self.email_password,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-06-03 12:47:32 +08:00
|
|
|
def email_username_domain
|
|
|
|
email_username.split("@").last
|
|
|
|
end
|
|
|
|
|
|
|
|
def email_username_user
|
|
|
|
email_username.split("@").first
|
|
|
|
end
|
|
|
|
|
2020-07-10 17:05:55 +08:00
|
|
|
def email_username_regex
|
2021-06-03 12:47:32 +08:00
|
|
|
user = email_username_user
|
|
|
|
domain = email_username_domain
|
2020-07-10 17:05:55 +08:00
|
|
|
if user.present? && domain.present?
|
|
|
|
/^#{Regexp.escape(user)}(\+[^@]*)?@#{Regexp.escape(domain)}$/i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-04 23:02:01 +08:00
|
|
|
def notify_added_to_group(user, owner: false)
|
|
|
|
SystemMessage.create_from_system_user(
|
|
|
|
user,
|
2020-09-02 21:24:01 +08:00
|
|
|
owner ? :user_added_to_group_as_owner : :user_added_to_group_as_member,
|
2021-06-28 08:42:06 +08:00
|
|
|
group_name: name_full_preferred,
|
2020-09-02 21:24:01 +08:00
|
|
|
group_path: "/g/#{self.name}",
|
2020-08-04 23:02:01 +08:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-06-28 08:42:06 +08:00
|
|
|
def name_full_preferred
|
|
|
|
self.full_name.presence || self.name
|
|
|
|
end
|
|
|
|
|
2021-01-22 05:59:34 +08:00
|
|
|
def message_count
|
|
|
|
return 0 unless self.has_messages
|
|
|
|
TopicAllowedGroup.where(group_id: self.id).joins(:topic).count
|
|
|
|
end
|
|
|
|
|
2022-04-13 21:53:54 +08:00
|
|
|
def full_url
|
|
|
|
"#{Discourse.base_url}/g/#{UrlHelper.encode_component(self.name)}"
|
|
|
|
end
|
|
|
|
|
2013-05-09 09:33:56 +08:00
|
|
|
protected
|
|
|
|
|
2014-08-18 19:04:08 +08:00
|
|
|
def name_format_validator
|
2018-08-15 14:59:56 +08:00
|
|
|
return if !name_changed?
|
|
|
|
|
|
|
|
# avoid strip! here, it works now
|
|
|
|
# but may not continue to work long term, especially
|
|
|
|
# once we start returning frozen strings
|
2019-04-23 18:22:47 +08:00
|
|
|
if self.name != (stripped = self.name.unicode_normalize.strip)
|
2018-08-15 14:59:56 +08:00
|
|
|
self.name = stripped
|
|
|
|
end
|
2018-04-02 18:17:06 +08:00
|
|
|
|
2018-04-03 00:44:04 +08:00
|
|
|
UsernameValidator.perform_validation(self, "name") ||
|
|
|
|
begin
|
2019-04-23 18:22:47 +08:00
|
|
|
normalized_name = User.normalize_username(self.name)
|
2018-05-07 14:02:11 +08:00
|
|
|
|
2019-04-23 18:22:47 +08:00
|
|
|
if self.will_save_change_to_name? &&
|
|
|
|
User.normalize_username(self.name_was) != normalized_name &&
|
|
|
|
User.username_exists?(self.name)
|
2019-02-20 05:31:03 +08:00
|
|
|
errors.add(:name, I18n.t("activerecord.errors.messages.taken"))
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2018-04-03 00:44:04 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2013-05-09 15:37:34 +08:00
|
|
|
|
2015-11-27 13:35:16 +08:00
|
|
|
def automatic_membership_email_domains_format_validator
|
|
|
|
return if self.automatic_membership_email_domains.blank?
|
|
|
|
|
2020-04-23 00:37:39 +08:00
|
|
|
domains =
|
|
|
|
Group.get_valid_email_domains(self.automatic_membership_email_domains) do |domain|
|
2020-05-26 13:40:09 +08:00
|
|
|
self.errors.add :base, (I18n.t("groups.errors.invalid_domain", domain: domain))
|
2015-11-27 13:35:16 +08:00
|
|
|
end
|
2020-04-23 00:37:39 +08:00
|
|
|
|
2015-11-27 13:35:16 +08:00
|
|
|
self.automatic_membership_email_domains = domains.join("|")
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2015-11-27 13:35:16 +08:00
|
|
|
|
2014-08-18 19:04:08 +08:00
|
|
|
# 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"
|
2013-05-09 09:33:56 +08:00
|
|
|
end
|
|
|
|
end
|
2014-08-18 19:04:08 +08:00
|
|
|
@deletions = nil
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2013-05-09 09:33:56 +08:00
|
|
|
|
2015-01-24 01:25:43 +08:00
|
|
|
def automatic_group_membership
|
2020-04-23 00:37:39 +08:00
|
|
|
if self.automatic_membership_email_domains.present?
|
2015-01-24 01:25:43 +08:00
|
|
|
Jobs.enqueue(:automatic_group_membership, group_id: self.id)
|
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2015-01-24 01:25:43 +08:00
|
|
|
|
2015-04-10 10:17:28 +08:00
|
|
|
def update_title
|
|
|
|
return if new_record? && !self.title.present?
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
if self.saved_change_to_title?
|
2018-06-19 14:13:14 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
DB.exec(sql, title: title, title_was: title_before_last_save, id: id)
|
2015-04-10 10:17:28 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2015-04-10 10:17:28 +08:00
|
|
|
|
|
|
|
def update_primary_group
|
|
|
|
return if new_record? && !self.primary_group?
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
if self.saved_change_to_primary_group?
|
2017-07-04 03:26:46 +08:00
|
|
|
sql = <<~SQL
|
2018-06-19 14:13:14 +08:00
|
|
|
UPDATE users
|
|
|
|
/*set*/
|
|
|
|
/*where*/
|
|
|
|
SQL
|
|
|
|
|
2021-10-29 01:26:44 +08:00
|
|
|
%i[primary_group_id flair_group_id].each do |column|
|
|
|
|
builder = DB.build(sql)
|
|
|
|
builder.where(<<~SQL, id: id)
|
|
|
|
id IN (
|
|
|
|
SELECT user_id
|
|
|
|
FROM group_users
|
|
|
|
WHERE group_id = :id
|
|
|
|
)
|
|
|
|
SQL
|
2015-04-10 10:17:28 +08:00
|
|
|
|
2021-10-29 01:26:44 +08:00
|
|
|
if primary_group
|
|
|
|
builder.set("#{column} = :id")
|
|
|
|
builder.where("#{column} IS NULL") if column == :flair_group_id
|
|
|
|
else
|
|
|
|
builder.set("#{column} = NULL")
|
|
|
|
builder.where("#{column} = :id")
|
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
|
2021-10-29 01:26:44 +08:00
|
|
|
builder.exec
|
|
|
|
end
|
2015-04-10 10:17:28 +08:00
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2017-01-18 13:39:34 +08:00
|
|
|
|
2020-04-23 00:37:39 +08:00
|
|
|
def self.automatic_membership_users(domains, group_id = nil)
|
|
|
|
pattern = "@(#{domains.gsub(".", '\.')})$"
|
2023-01-09 20:20:10 +08:00
|
|
|
|
2020-04-23 00:37:39 +08:00
|
|
|
users =
|
|
|
|
User
|
|
|
|
.joins(:user_emails)
|
|
|
|
.where("user_emails.email ~* ?", pattern)
|
|
|
|
.activated
|
|
|
|
.where(staged: false)
|
|
|
|
users =
|
|
|
|
users.where(
|
|
|
|
"users.id NOT IN (SELECT user_id FROM group_users WHERE group_users.group_id = ?)",
|
|
|
|
group_id,
|
|
|
|
) if group_id.present?
|
|
|
|
|
|
|
|
users
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.get_valid_email_domains(value)
|
|
|
|
valid_domains = []
|
|
|
|
|
|
|
|
value
|
|
|
|
.split("|")
|
|
|
|
.each do |domain|
|
|
|
|
domain.sub!(%r{^https?://}, "")
|
|
|
|
domain.sub!(%r{/.*$}, "")
|
|
|
|
|
2020-05-26 13:40:09 +08:00
|
|
|
if domain =~ Group::VALID_DOMAIN_REGEX
|
2020-04-23 00:37:39 +08:00
|
|
|
valid_domains << domain
|
|
|
|
else
|
2020-05-26 13:40:09 +08:00
|
|
|
yield domain if block_given?
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2020-04-23 00:37:39 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
valid_domains
|
|
|
|
end
|
|
|
|
|
2017-01-18 13:39:34 +08:00
|
|
|
private
|
|
|
|
|
2018-04-06 17:11:00 +08:00
|
|
|
def validate_grant_trust_level
|
|
|
|
unless TrustLevel.valid?(self.grant_trust_level)
|
|
|
|
self.errors.add(
|
|
|
|
:base,
|
|
|
|
I18n.t("groups.errors.grant_trust_level_not_valid", trust_level: self.grant_trust_level),
|
|
|
|
)
|
|
|
|
end
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
2018-04-06 17:11:00 +08:00
|
|
|
|
2017-06-02 22:15:57 +08:00
|
|
|
def can_allow_membership_requests
|
2017-07-27 14:39:47 +08:00
|
|
|
valid = true
|
|
|
|
|
|
|
|
valid =
|
|
|
|
if self.persisted?
|
2017-06-02 22:15:57 +08:00
|
|
|
self.group_users.where(owner: true).exists?
|
2018-06-07 13:28:18 +08:00
|
|
|
else
|
2017-06-02 22:15:57 +08:00
|
|
|
self.group_users.any?(&:owner)
|
|
|
|
end
|
|
|
|
|
2017-08-31 12:06:56 +08:00
|
|
|
self.errors.add(:base, I18n.t("groups.errors.cant_allow_membership_requests")) if !valid
|
2018-06-07 13:28:18 +08:00
|
|
|
end
|
|
|
|
|
2017-01-18 13:39:34 +08:00
|
|
|
def enqueue_update_mentions_job
|
|
|
|
Jobs.enqueue(
|
|
|
|
:update_group_mentions,
|
2017-08-31 12:06:56 +08:00
|
|
|
previous_name: self.name_before_last_save,
|
2017-01-18 13:39:34 +08:00
|
|
|
group_id: self.id,
|
2018-06-07 13:28:18 +08:00
|
|
|
)
|
|
|
|
end
|
2013-04-17 15:08:21 +08:00
|
|
|
end
|
2013-05-24 10:48:32 +08:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: groups
|
|
|
|
#
|
2015-02-04 12:09:00 +08:00
|
|
|
# id :integer not null, primary key
|
2019-01-12 03:29:56 +08:00
|
|
|
# name :string not null
|
2015-02-04 12:09:00 +08:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
|
|
|
# automatic :boolean default(FALSE), not null
|
|
|
|
# user_count :integer default(0), not null
|
|
|
|
# automatic_membership_email_domains :text
|
2015-09-18 08:41:10 +08:00
|
|
|
# primary_group :boolean default(FALSE), not null
|
2019-01-12 03:29:56 +08:00
|
|
|
# title :string
|
2015-09-02 04:52:05 +08:00
|
|
|
# grant_trust_level :integer
|
2016-01-11 14:30:56 +08:00
|
|
|
# incoming_email :string
|
|
|
|
# has_messages :boolean default(FALSE), not null
|
2016-10-31 17:32:11 +08:00
|
|
|
# flair_bg_color :string
|
|
|
|
# flair_color :string
|
2016-12-07 11:05:18 +08:00
|
|
|
# bio_raw :text
|
|
|
|
# bio_cooked :text
|
2016-12-12 22:59:40 +08:00
|
|
|
# allow_membership_requests :boolean default(FALSE), not null
|
2016-12-13 16:16:26 +08:00
|
|
|
# full_name :string
|
2017-04-21 03:47:25 +08:00
|
|
|
# default_notification_level :integer default(3), not null
|
2017-04-27 02:47:36 +08:00
|
|
|
# visibility_level :integer default(0), not null
|
2017-07-28 10:37:10 +08:00
|
|
|
# public_exit :boolean default(FALSE), not null
|
2017-08-16 22:38:11 +08:00
|
|
|
# public_admission :boolean default(FALSE), not null
|
|
|
|
# membership_request_template :text
|
2017-12-05 23:29:14 +08:00
|
|
|
# messageable_level :integer default(0)
|
|
|
|
# mentionable_level :integer default(0)
|
2020-07-10 17:05:55 +08:00
|
|
|
# smtp_server :string
|
|
|
|
# smtp_port :integer
|
|
|
|
# smtp_ssl :boolean
|
|
|
|
# imap_server :string
|
|
|
|
# imap_port :integer
|
|
|
|
# imap_ssl :boolean
|
|
|
|
# imap_mailbox_name :string default(""), not null
|
|
|
|
# imap_uid_validity :integer default(0), not null
|
|
|
|
# imap_last_uid :integer default(0), not null
|
|
|
|
# email_username :string
|
|
|
|
# email_password :string
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
# publish_read_state :boolean default(FALSE), not null
|
|
|
|
# members_visibility_level :integer default(0), not null
|
2020-07-10 17:05:55 +08:00
|
|
|
# imap_last_error :text
|
|
|
|
# imap_old_emails :integer
|
|
|
|
# imap_new_emails :integer
|
FEATURE: Improve group email settings UI (#13083)
This overhauls the user interface for the group email settings management, aiming to make it a lot easier to test the settings entered and confirm they are correct before proceeding. We do this by forcing the user to test the settings before they can be saved to the database. It also includes some quality of life improvements around setting up IMAP and SMTP for our first supported provider, GMail. This PR does not remove the old group email config, that will come in a subsequent PR. This is related to https://meta.discourse.org/t/imap-support-for-group-inboxes/160588 so read that if you would like more backstory.
### UI
Both site settings of `enable_imap` and `enable_smtp` must be true to test this. You must enable SMTP first to enable IMAP.
You can prefill the SMTP settings with GMail configuration. To proceed with saving these settings you must test them, which is handled by the EmailSettingsValidator.
If there is an issue with the configuration or credentials a meaningful error message should be shown.
IMAP settings must also be validated when IMAP is enabled, before saving.
When saving IMAP, we fetch the mailboxes for that account and populate them. This mailbox must be selected and saved for IMAP to work (the feature acts as though it is disabled until the mailbox is selected and saved):
### Database & Backend
This adds several columns to the Groups table. The purpose of this change is to make it much more explicit that SMTP/IMAP is enabled for a group, rather than relying on settings not being null. Also included is an UPDATE query to backfill these columns. These columns are automatically filled when updating the group.
For GMail, we now filter the mailboxes returned. This is so users cannot use a mailbox like Sent or Trash for syncing, which would generally be disastrous.
There is a new group endpoint for testing email settings. This may be useful in the future for other places in our UI, at which point it can be extracted to a more generic endpoint or module to be included.
2021-05-28 07:28:18 +08:00
|
|
|
# flair_icon :string
|
|
|
|
# flair_upload_id :integer
|
|
|
|
# allow_unknown_sender_topic_replies :boolean default(FALSE), not null
|
|
|
|
# smtp_enabled :boolean default(FALSE)
|
|
|
|
# smtp_updated_at :datetime
|
|
|
|
# smtp_updated_by_id :integer
|
|
|
|
# imap_enabled :boolean default(FALSE)
|
|
|
|
# imap_updated_at :datetime
|
|
|
|
# imap_updated_by_id :integer
|
2022-02-07 11:52:01 +08:00
|
|
|
# email_from_alias :string
|
2013-05-24 10:48:32 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2016-01-11 14:30:56 +08:00
|
|
|
# index_groups_on_incoming_email (incoming_email) UNIQUE
|
|
|
|
# index_groups_on_name (name) UNIQUE
|
2013-05-24 10:48:32 +08:00
|
|
|
#
|