discourse/app/models/email_log.rb
Alan Guo Xiang Tan 322a3be2db
DEV: Remove logical OR assignment of constants (#29201)
Constants should always be only assigned once. The logical OR assignment
of a constant is a relic of the past before we used zeitwerk for
autoloading and had bugs where a file could be loaded twice resulting in
constant redefinition warnings.
2024-10-16 10:09:07 +08:00

154 lines
4.3 KiB
Ruby

# frozen_string_literal: true
class EmailLog < ActiveRecord::Base
CRITICAL_EMAIL_TYPES =
Set.new %w[
account_created
admin_login
confirm_new_email
confirm_old_email
confirm_old_email_add
forgot_password
notify_old_email
notify_old_email_add
signup
signup_after_approval
]
# cf. https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml
SMTP_ERROR_CODE_REGEXP = Regexp.new(/\d\.\d\.\d+|\d{3}/).freeze
belongs_to :user
belongs_to :post
belongs_to :smtp_group, class_name: "Group"
validates :email_type, :to_address, presence: true
scope :bounced, -> { where(bounced: true) }
scope :addressed_to_user, ->(user) { where(<<~SQL, user_id: user.id) }
EXISTS(
SELECT 1
FROM user_emails
WHERE user_emails.user_id = :user_id AND
(email_logs.to_address = user_emails.email OR
email_logs.cc_addresses ILIKE '%' || user_emails.email || '%')
)
SQL
before_save do
if self.bounce_error_code.present?
match = SMTP_ERROR_CODE_REGEXP.match(self.bounce_error_code)
self.bounce_error_code = match.present? ? match[0] : nil
end
end
after_create do
# Update last_emailed_at if the user_id is present and email was sent
User.where(id: user_id).update_all("last_emailed_at = CURRENT_TIMESTAMP") if user_id.present?
end
def topic
@topic ||= self.topic_id.present? ? Topic.find_by(id: self.topic_id) : self.post&.topic
end
def self.unique_email_per_post(post, user)
return yield unless post && user
DistributedMutex.synchronize("email_log_#{post.id}_#{user.id}") do
if where(post_id: post.id, user_id: user.id).exists?
nil
else
yield
end
end
end
def self.reached_max_emails?(user, email_type = nil)
if SiteSetting.max_emails_per_day_per_user == 0 || CRITICAL_EMAIL_TYPES.include?(email_type)
return false
end
count = where("created_at > ?", 1.day.ago).where(user_id: user.id).count
count >= SiteSetting.max_emails_per_day_per_user
end
def self.count_per_day(start_date, end_date)
where("created_at BETWEEN ? AND ?", start_date, end_date)
.group("DATE(created_at)")
.order("DATE(created_at)")
.count
end
def self.for(reply_key)
self.find_by(reply_key: reply_key)
end
def self.last_sent_email_address
self.where(email_type: "signup").order(created_at: :desc).limit(1).pluck(:to_address).first
end
def bounce_key
super&.delete("-")
end
def cc_users
return [] if !self.cc_user_ids
@cc_users ||= User.where(id: self.cc_user_ids)
end
def cc_addresses_split
@cc_addresses_split ||= self.cc_addresses&.split(";") || []
end
def as_mail_message
return if self.raw.blank?
@mail_message ||= Mail.new(self.raw)
end
def raw_headers
return if self.raw.blank?
as_mail_message.header.raw_source
end
def raw_body
return if self.raw.blank?
as_mail_message.body
end
end
# == Schema Information
#
# Table name: email_logs
#
# id :integer not null, primary key
# to_address :string not null
# email_type :string not null
# user_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# post_id :integer
# bounce_key :uuid
# bounced :boolean default(FALSE), not null
# message_id :string
# smtp_group_id :integer
# cc_addresses :text
# cc_user_ids :integer is an Array
# raw :text
# topic_id :integer
# bounce_error_code :string
# smtp_transaction_response :string(500)
# bcc_addresses :text
#
# Indexes
#
# index_email_logs_on_bounce_key (bounce_key) UNIQUE WHERE (bounce_key IS NOT NULL)
# index_email_logs_on_bounced (bounced)
# index_email_logs_on_created_at (created_at)
# index_email_logs_on_message_id (message_id)
# index_email_logs_on_post_id (post_id)
# index_email_logs_on_topic_id (topic_id) WHERE (topic_id IS NOT NULL)
# index_email_logs_on_user_id (user_id)
#