Files
MySMSAPio/app/models/sms_message.rb
2025-10-22 17:22:17 +08:00

111 lines
2.6 KiB
Ruby

class SmsMessage < ApplicationRecord
# Normalize metadata to always be a Hash
attribute :metadata, :jsonb, default: {}
belongs_to :gateway, optional: true
validates :phone_number, presence: true
validates :message_body, presence: true, length: { maximum: 1600 }
validates :direction, presence: true, inclusion: { in: %w[inbound outbound] }
validates :message_id, presence: true, uniqueness: true
validates :status, inclusion: { in: %w[pending queued sent delivered failed] }
validate :phone_number_format
before_validation :ensure_metadata_is_hash
before_validation :generate_message_id, on: :create
before_validation :normalize_phone_number
after_create_commit :enqueue_sending, if: :outbound?
scope :pending, -> { where(status: "pending") }
scope :queued, -> { where(status: "queued") }
scope :sent, -> { where(status: "sent") }
scope :delivered, -> { where(status: "delivered") }
scope :failed, -> { where(status: "failed") }
scope :inbound, -> { where(direction: "inbound") }
scope :outbound, -> { where(direction: "outbound") }
scope :recent, -> { order(created_at: :desc) }
# Check message direction
def outbound?
direction == "outbound"
end
def inbound?
direction == "inbound"
end
# Check if message can be retried
def can_retry?
failed? && retry_count < 3
end
# Mark message as sent
def mark_sent!(gateway)
update!(
status: "sent",
gateway: gateway,
sent_at: Time.current
)
gateway.increment_sent_count!
end
# Mark message as delivered
def mark_delivered!
update!(
status: "delivered",
delivered_at: Time.current
)
end
# Mark message as failed
def mark_failed!(error_msg = nil)
update!(
status: "failed",
failed_at: Time.current,
error_message: error_msg
)
end
# Increment retry counter
def increment_retry!
increment!(:retry_count)
end
# Check if message status is failed
def failed?
status == "failed"
end
private
def generate_message_id
self.message_id ||= "msg_#{SecureRandom.hex(16)}"
end
def normalize_phone_number
return unless phone_number.present?
# Remove spaces and special characters
self.phone_number = phone_number.gsub(/[^\d+]/, "")
end
def phone_number_format
return unless phone_number.present?
phone = Phonelib.parse(phone_number)
unless phone.valid?
errors.add(:phone_number, "is not a valid phone number")
end
end
def enqueue_sending
SendSmsJob.perform_later(id)
end
def ensure_metadata_is_hash
self.metadata = {} if metadata.nil?
self.metadata = {} unless metadata.is_a?(Hash)
end
end