Adição da função de configuração de email e whatts
This commit is contained in:
192
app/models/configuracao_notificacao.rb
Normal file
192
app/models/configuracao_notificacao.rb
Normal file
@@ -0,0 +1,192 @@
|
||||
# app/models/configuracao_notificacao.rb
|
||||
#
|
||||
# Configuração de envio (SMTP + WhatsApp/Twilio) editável pelo ADM na tela
|
||||
# /admin/configuracao_notificacao. Tabela SINGLETON: sempre uma linha só.
|
||||
#
|
||||
# HIERARQUIA: banco > .env. Enquanto a tela não estiver preenchida e marcada
|
||||
# como ativa, `smtp_settings` devolve nil e `credenciais_whatsapp` cai no ENV —
|
||||
# ou seja, o comportamento é idêntico ao que existia antes desta tela.
|
||||
class ConfiguracaoNotificacao < ApplicationRecord
|
||||
include AtributoCifrado
|
||||
|
||||
self.table_name = 'configuracao_notificacoes'
|
||||
|
||||
atributo_cifrado :smtp_password, :twilio_auth_token
|
||||
|
||||
AUTENTICACOES = %w[plain login cram_md5].freeze
|
||||
EMAIL_RE = /\A[^@\s]+@[^@\s]+\z/
|
||||
REMETENTE_PADRAO = 'noreply@gade.com.br'
|
||||
|
||||
before_validation :normalizar
|
||||
|
||||
validates :smtp_address, presence: true, if: :smtp_ativo?
|
||||
validates :smtp_username, presence: true, if: :smtp_ativo?
|
||||
validates :smtp_port, numericality: { only_integer: true,
|
||||
greater_than: 0, less_than_or_equal_to: 65_535 }
|
||||
validates :smtp_autenticacao, inclusion: { in: AUTENTICACOES }
|
||||
validates :remetente_email, format: { with: EMAIL_RE }, allow_blank: true
|
||||
validates :email_admin, format: { with: EMAIL_RE }, allow_blank: true
|
||||
validate :senha_smtp_presente, if: :smtp_ativo?
|
||||
|
||||
validates :twilio_account_sid, presence: true, if: :whatsapp_ativo?
|
||||
validates :twilio_from, presence: true, if: :whatsapp_ativo?
|
||||
validate :token_twilio_presente, if: :whatsapp_ativo?
|
||||
|
||||
# ── Singleton ───────────────────────────────────────────────
|
||||
def self.instancia
|
||||
first || create!
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
# Dois workers Puma criando a primeira linha ao mesmo tempo.
|
||||
first
|
||||
rescue ActiveRecord::StatementInvalid,
|
||||
ActiveRecord::NoDatabaseError,
|
||||
ActiveRecord::ConnectionNotEstablished => e
|
||||
# Boot do container antes do db:migrate, assets:precompile, console sem banco…
|
||||
Rails.logger.warn("[ConfiguracaoNotificacao] tabela indisponível (#{e.class}) — usando .env")
|
||||
new
|
||||
end
|
||||
|
||||
# ── Usados pelo ApplicationMailer: NUNCA podem estourar ─────
|
||||
# (um erro aqui derrubaria todo envio de e-mail, inclusive o reset de senha)
|
||||
|
||||
def self.remetente
|
||||
instancia.remetente_formatado
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[ConfiguracaoNotificacao.remetente] #{e.class}: #{e.message}")
|
||||
ENV.fetch('SMTP_USERNAME', REMETENTE_PADRAO)
|
||||
end
|
||||
|
||||
# nil => o ActionMailer faz `.merge({})` e mantém o que o
|
||||
# config/initializers/smtp.rb montou a partir do .env no boot.
|
||||
def self.opcoes_de_entrega
|
||||
instancia.smtp_settings
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[ConfiguracaoNotificacao.opcoes_de_entrega] #{e.class}: #{e.message}")
|
||||
nil
|
||||
end
|
||||
|
||||
# ── SMTP ────────────────────────────────────────────────────
|
||||
def smtp_pronto?
|
||||
smtp_ativo? && smtp_address.present? && smtp_port.to_i.positive? &&
|
||||
smtp_username.present? && smtp_password.present?
|
||||
end
|
||||
|
||||
def smtp_settings
|
||||
return nil unless smtp_pronto?
|
||||
|
||||
porta = smtp_port.to_i
|
||||
base = {
|
||||
address: smtp_address,
|
||||
port: porta,
|
||||
user_name: smtp_username,
|
||||
password: smtp_password,
|
||||
domain: smtp_domain.presence || dominio_padrao,
|
||||
authentication: (smtp_autenticacao.presence || 'plain').to_sym,
|
||||
# Sem timeout, uma porta filtrada pelo firewall do VPS pendura o worker
|
||||
# Puma até o Nginx cortar — o botão "enviar teste" ficaria girando.
|
||||
open_timeout: 10,
|
||||
read_timeout: 10
|
||||
}
|
||||
# 465 = SSL implícito; 587/25 = STARTTLS.
|
||||
base.merge(porta == 465 ? { ssl: true, tls: true } : { enable_starttls_auto: true })
|
||||
end
|
||||
|
||||
def remetente_formatado
|
||||
endereco = remetente_email.presence ||
|
||||
smtp_username.presence ||
|
||||
ENV.fetch('SMTP_USERNAME', REMETENTE_PADRAO)
|
||||
remetente_nome.present? ? %("#{remetente_nome}" <#{endereco}>) : endereco
|
||||
end
|
||||
|
||||
def email_habilitado?
|
||||
email_notificacoes_ativo?
|
||||
end
|
||||
|
||||
# ── WhatsApp / Twilio ───────────────────────────────────────
|
||||
def whatsapp_pronto?
|
||||
whatsapp_ativo? && twilio_account_sid.present? &&
|
||||
twilio_auth_token.present? && twilio_from.present?
|
||||
end
|
||||
|
||||
def whatsapp_habilitado?
|
||||
whatsapp_ativo? && credenciais_whatsapp.present?
|
||||
end
|
||||
|
||||
# Tudo-ou-nada: nunca mistura o SID do banco com o token do .env (a conta
|
||||
# seria outra e o Twilio devolveria 20003 sem explicação óbvia).
|
||||
def credenciais_whatsapp
|
||||
if whatsapp_pronto?
|
||||
{ sid: twilio_account_sid,
|
||||
token: twilio_auth_token,
|
||||
from: self.class.canal(twilio_from) }
|
||||
elsif ENV['TWILIO_ACCOUNT_SID'].present? && ENV['TWILIO_AUTH_TOKEN'].present?
|
||||
{ sid: ENV['TWILIO_ACCOUNT_SID'],
|
||||
token: ENV['TWILIO_AUTH_TOKEN'],
|
||||
from: self.class.canal(ENV['TWILIO_WHATSAPP_FROM']) }
|
||||
end
|
||||
end
|
||||
|
||||
# ── Telefone ────────────────────────────────────────────────
|
||||
# "11 920051157", "(11) 92005-1157", "011 92005-1157", "+55 11 92005-1157"
|
||||
# e "whatsapp:+5511920051157" → "+5511920051157".
|
||||
def self.normalizar_telefone(valor)
|
||||
return nil if valor.blank?
|
||||
|
||||
d = valor.to_s.gsub(/\D/, '').sub(/\A0+/, '')
|
||||
# Só tira o 55 quando sobra número demais — senão quebraria o DDD 55
|
||||
# (Santa Maria/RS), onde "55991234567" já é o número completo.
|
||||
d = d.sub(/\A55/, '') if d.length > 11 && d.start_with?('55')
|
||||
return nil unless d.length.between?(10, 11)
|
||||
|
||||
"+55#{d}"
|
||||
end
|
||||
|
||||
def self.canal(numero)
|
||||
e164 = normalizar_telefone(numero)
|
||||
return nil if e164.blank?
|
||||
|
||||
"whatsapp:#{e164}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dominio_padrao
|
||||
(remetente_email.presence || smtp_username).to_s.split('@').last.presence || 'localhost'
|
||||
end
|
||||
|
||||
def normalizar
|
||||
self.smtp_address = smtp_address.to_s.strip.presence
|
||||
self.smtp_username = smtp_username.to_s.strip.presence
|
||||
self.smtp_domain = smtp_domain.to_s.strip.presence
|
||||
self.remetente_email = remetente_email.to_s.strip.downcase.presence
|
||||
self.remetente_nome = remetente_nome.to_s.strip.presence
|
||||
self.email_admin = email_admin.to_s.strip.downcase.presence
|
||||
self.twilio_account_sid = twilio_account_sid.to_s.strip.presence
|
||||
# Mantém o que o admin digitou se não der para normalizar — a validação de
|
||||
# presença acusa, e ele vê o próprio valor de volta no campo para corrigir.
|
||||
self.twilio_from = self.class.normalizar_telefone(twilio_from) || twilio_from.presence
|
||||
self.whatsapp_admin = self.class.normalizar_telefone(whatsapp_admin) || whatsapp_admin.presence
|
||||
end
|
||||
|
||||
def senha_smtp_presente
|
||||
return if smtp_password.present?
|
||||
|
||||
errors.add(:smtp_password,
|
||||
if smtp_password_cifrado.present?
|
||||
'gravada está ilegível (o SECRET_KEY_BASE do servidor mudou?). Digite a senha novamente.'
|
||||
else
|
||||
'é obrigatória quando o envio de e-mail está ativo'
|
||||
end)
|
||||
end
|
||||
|
||||
def token_twilio_presente
|
||||
return if twilio_auth_token.present?
|
||||
|
||||
errors.add(:twilio_auth_token,
|
||||
if twilio_auth_token_cifrado.present?
|
||||
'gravado está ilegível (o SECRET_KEY_BASE do servidor mudou?). Cole o token novamente.'
|
||||
else
|
||||
'é obrigatório quando o WhatsApp está ativo'
|
||||
end)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user