dawarich/app/services/families/invite.rb

127 lines
3.7 KiB
Ruby

# frozen_string_literal: true
module Families
class Invite
include ActiveModel::Validations
attr_reader :family, :email, :invited_by, :invitation
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
def initialize(family:, email:, invited_by:)
@family = family
@email = email.downcase.strip
@invited_by = invited_by
end
def call
return false unless valid?
return false unless invite_sendable?
ActiveRecord::Base.transaction do
invitation = create_invitation
send_invitation_email(invitation)
send_notification
end
true
rescue ActiveRecord::RecordInvalid => e
handle_record_invalid_error(e)
false
rescue Net::SMTPError => e
handle_email_error(e)
false
rescue StandardError => e
handle_generic_error(e)
false
end
def error_message
return errors.full_messages.first if errors.any?
return @custom_error_message if @custom_error_message
'Failed to send invitation'
end
private
def invite_sendable?
unless invited_by.family_owner?
return add_error_and_false(:invited_by,
'You must be a family owner to send invitations')
end
return add_error_and_false(:family, 'Family is full') if family.full?
return add_error_and_false(:email, 'User is already in a family') if user_already_in_family?
return add_error_and_false(:email, 'Invitation already sent to this email') if pending_invitation_exists?
true
end
def add_error_and_false(attribute, message)
errors.add(attribute, message)
false
end
def user_already_in_family?
User.joins(:family_membership)
.where(email: email)
.exists?
end
def pending_invitation_exists?
family.family_invitations.active.where(email: email).exists?
end
def create_invitation
@invitation = Family::Invitation.create!(
family: family,
email: email,
invited_by: invited_by
)
end
def send_invitation_email(invitation)
Families::Invitations::SendingJob.perform_later(invitation.id)
end
def send_notification
message =
if DawarichSettings.self_hosted?
"Family invitation sent to #{email} if SMTP is configured properly. If you're not using SMTP, copy the invitation link from the family page and share it manually."
else
"Family invitation sent to #{email}"
end
Notification.create!(
user: invited_by,
kind: :info,
title: 'Invitation Sent',
content: "Family invitation sent to #{email}"
)
rescue StandardError => e
# Don't fail the entire operation if notification fails
ExceptionReporter.call(e, "Unexpected error in Families::Invite: #{e.message}")
end
def handle_record_invalid_error(error)
@custom_error_message = if invitation&.errors&.any?
invitation.errors.full_messages.first
else
"Failed to create invitation: #{error.message}"
end
end
def handle_email_error(error)
Rails.logger.error "Email delivery failed for family invitation: #{error.message}"
@custom_error_message = 'Failed to send invitation email. Please try again later'
# Clean up the invitation if email fails
invitation&.destroy
end
def handle_generic_error(error)
ExceptionReporter.call(error, "Unexpected error in Families::Invite: #{error.message}")
@custom_error_message = 'An unexpected error occurred while sending the invitation. Please try again'
end
end
end