Files
MySMSAPio/app/models/sms_message.rb

118 lines
3.0 KiB
Ruby

class SmsMessage < ApplicationRecord
include NtfyDispatchable
# 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)
self.class.dispatch_ntfy("sms_delivered",
title: "SMS delivered",
message: "Message to #{phone_number} (#{message_id}) was delivered",
priority: 2,
tags: ["white_check_mark"])
end
# Mark message as failed
def mark_failed!(error_msg = nil)
update!(status: "failed", failed_at: Time.current, error_message: error_msg)
self.class.dispatch_ntfy("sms_failed",
title: "SMS failed",
message: "Message to #{phone_number} (#{message_id}) failed#{error_msg ? ": #{error_msg}" : ''}",
priority: 5,
tags: ["x", "rotating_light"])
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