Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/controllers/repp/v1/domains_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def create
handle_errors(@domain) and return unless action.call
# rubocop:enable Style/AndOr

render_success(data: { domain: { name: @domain.name,
render_success(message: message, data: { domain: { name: @domain.name,
transfer_code: @domain.transfer_code,
id: @domain.reload.uuid } })
end
Expand All @@ -104,7 +104,7 @@ def update
return
end

render_success(data: { domain: { name: @domain.name } })
render_success(message: message, data: { domain: { name: @domain.name } })
end

api :GET, '/repp/v1/domains/:domain_name/transfer_info'
Expand Down Expand Up @@ -239,6 +239,10 @@ def offset
index_params[:offset] || 0
end

def message
"Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain.skipped_domain_contacts_validation.present?}"
end

def index_params
params.permit(:limit, :offset, :details, :simple, :q,
q: %i[s name_matches registrant_code_eq contacts_ident_eq
Expand Down
69 changes: 66 additions & 3 deletions app/interactions/actions/domain_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def call
assign_registrant
assign_nameservers
assign_domain_contacts
# domain.attach_default_contacts
assign_expiry_time
maybe_attach_legal_doc

Expand All @@ -38,7 +37,69 @@ def check_for_same_contacts(contacts, contact_type)
false
end

# Check if domain is eligible for new registration
def check_for_cross_role_duplicates
@removed_duplicates = []

registrant_contact = domain.registrant
return true unless registrant_contact

@admin_contacts = remove_duplicate_contacts(@admin_contacts, registrant_contact, 'admin')
@tech_contacts = remove_duplicate_contacts(@tech_contacts, registrant_contact, 'tech')

@admin_contacts.each do |admin|
contact = Contact.find_by(id: admin[:contact_id])
next unless contact

@tech_contacts = remove_duplicate_contacts(@tech_contacts, contact, 'tech')
end

notify_about_removed_duplicates unless @removed_duplicates.empty?

true
end

def remove_duplicate_contacts(contacts_array, reference_contact, role)
return contacts_array unless reference_contact

non_duplicates = contacts_array.reject do |contact_hash|
contact = Contact.find_by(id: contact_hash[:contact_id])
next false unless contact

is_duplicate = duplicate_contact?(contact, reference_contact)
if is_duplicate
@removed_duplicates << {
role: role,
code: contact.code,
duplicate_of: reference_contact.code
}
end
is_duplicate
end

non_duplicates
end

def duplicate_contact?(contact1, contact2)
return false unless contact1 && contact2

contact1.code == contact2.code ||
(contact1.name == contact2.name &&
contact1.ident == contact2.ident &&
contact1.email == contact2.email &&
contact1.phone == contact2.phone)
end

def notify_about_removed_duplicates
return if @removed_duplicates.empty?

message = ''
@removed_duplicates.each do |duplicate|
message += ". #{duplicate[:role].capitalize} contact #{duplicate[:code]} was discarded as duplicate;"
end

domain.skipped_domain_contacts_validation = message
end

def validate_domain_integrity
return unless Domain.release_to_auction

Expand Down Expand Up @@ -132,9 +193,11 @@ def assign_domain_contacts
params[:admin_contacts]&.each { |c| assign_contact(c) }
params[:tech_contacts]&.each { |c| assign_contact(c, admin: false) }

check_contact_duplications
check_for_cross_role_duplicates

domain.admin_domain_contacts_attributes = @admin_contacts
domain.tech_domain_contacts_attributes = @tech_contacts
check_contact_duplications
end

def assign_expiry_time
Expand Down
157 changes: 153 additions & 4 deletions app/interactions/actions/domain_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def assign_relational_modifications

assign_admin_contact_changes
assign_tech_contact_changes
check_for_cross_role_duplicates
end

def check_for_same_contacts(contacts, contact_type)
Expand All @@ -37,6 +38,150 @@ def check_for_same_contacts(contacts, contact_type)
domain.add_epp_error('2306', contact_type, nil, %i[domain_contacts invalid])
end

def check_for_cross_role_duplicates
@removed_duplicates = []
registrant_contact = domain.registrant
return true unless registrant_contact

current_admin_contacts = domain.admin_domain_contacts.map { |dc| { contact_id: dc.contact_id, contact_code: dc.contact.code } }
current_tech_contacts = domain.tech_domain_contacts.map { |dc| { contact_id: dc.contact_id, contact_code: dc.contact.code } }

updated_admin_contacts = remove_duplicate_contacts(current_admin_contacts, registrant_contact, 'admin')

updated_tech_contacts = remove_duplicate_contacts(current_tech_contacts, registrant_contact, 'tech')

if updated_admin_contacts.present?
updated_admin_contacts.each do |admin_hash|
admin_contact_object = Contact.find_by(id: admin_hash[:contact_id])
next unless admin_contact_object
updated_tech_contacts = remove_duplicate_contacts(updated_tech_contacts, admin_contact_object, 'tech')
end
end

admin_ids_after_filtering = updated_admin_contacts.map { |c| c[:contact_id] }
current_admin_ids_from_map = current_admin_contacts.map { |c| c[:contact_id] }
domain.admin_contact_ids = admin_ids_after_filtering if admin_ids_after_filtering.sort != current_admin_ids_from_map.sort

tech_ids_after_filtering = updated_tech_contacts.map { |c| c[:contact_id] }
current_tech_ids_from_map = current_tech_contacts.map { |c| c[:contact_id] }
domain.tech_contact_ids = tech_ids_after_filtering if tech_ids_after_filtering.sort != current_tech_ids_from_map.sort

notify_about_removed_duplicates unless @removed_duplicates.empty?

true
end

def remove_duplicate_contacts(contacts_array, reference_contact, role)
return contacts_array unless reference_contact && contacts_array.present?

contacts_array.reject do |contact_hash|
contact = Contact.find_by(id: contact_hash[:contact_id])
next false unless contact

is_duplicate = duplicate_contact?(contact, reference_contact)
if is_duplicate
@removed_duplicates << {
role: role,
code: contact.code,
duplicate_of: reference_contact.code
}
end
is_duplicate
end
end

def duplicate_contact?(contact1, contact2)
return false unless contact1 && contact2

contact1.code == contact2.code ||
(contact1.name == contact2.name &&
contact1.ident == contact2.ident &&
contact1.email == contact2.email &&
contact1.phone == contact2.phone)
end

def filter_duplicate_contacts_before_assignment(props, role)
@removed_duplicates ||= []
registrant = domain.registrant

# Get existing contacts
existing_admin_contacts = domain.admin_domain_contacts.map(&:contact)
existing_tech_contacts = domain.tech_domain_contacts.map(&:contact)

# Filter new contacts being added
filtered_props = props.select do |prop|
next true if prop[:_destroy] # Keep removal operations

new_contact = Contact.find_by(id: prop[:contact_id])
next false unless new_contact

# Check against registrant
if registrant && duplicate_contact?(new_contact, registrant)
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: registrant.code
}
next false
end

# Check against existing admin contacts
is_duplicate = existing_admin_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate && role == 'admin'
duplicate_of = existing_admin_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end

# Check against existing tech contacts
is_duplicate = existing_tech_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate && role == 'tech'
duplicate_of = existing_tech_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end

# For tech contacts, also check against admin contacts
if role == 'tech'
is_duplicate = existing_admin_contacts.any? { |existing| duplicate_contact?(new_contact, existing) }
if is_duplicate
duplicate_of = existing_admin_contacts.find { |existing| duplicate_contact?(new_contact, existing) }
@removed_duplicates << {
role: role,
code: new_contact.code,
duplicate_of: duplicate_of.code
}
next false
end
end

true
end

notify_about_removed_duplicates unless @removed_duplicates.empty?
filtered_props
end

def notify_about_removed_duplicates
return if @removed_duplicates.empty?

# Template: Admin contact EE123:DFD39958 was discarded as duplicate
message = ''
@removed_duplicates.each do |duplicate|
message += ". #{duplicate[:role].capitalize} contact #{duplicate[:code]} was discarded as duplicate;"
end

domain.skipped_domain_contacts_validation = message
end

def validate_domain_integrity
domain.auth_info = params[:transfer_code] if params[:transfer_code]

Expand Down Expand Up @@ -161,8 +306,10 @@ def assign_admin_contact_changes
domain.add_epp_error('2304', 'admin', DomainStatus::SERVER_ADMIN_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation))
elsif props.present?
domain.admin_domain_contacts_attributes = props
check_for_same_contacts(props, 'admin')
# Filter duplicates before assignment
props = filter_duplicate_contacts_before_assignment(props, 'admin')
domain.admin_domain_contacts_attributes = props if props.present?
check_for_same_contacts(props, 'admin') if props.present?
end
end

Expand All @@ -176,8 +323,10 @@ def assign_tech_contact_changes
domain.add_epp_error('2304', 'tech', DomainStatus::SERVER_TECH_CHANGE_PROHIBITED,
I18n.t(:object_status_prohibits_operation))
elsif props.present?
domain.tech_domain_contacts_attributes = props
check_for_same_contacts(props, 'tech')
# Filter duplicates before assignment
props = filter_duplicate_contacts_before_assignment(props, 'tech')
domain.tech_domain_contacts_attributes = props if props.present?
check_for_same_contacts(props, 'tech') if props.present?
end
end

Expand Down
1 change: 1 addition & 0 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def registrant_change_prohibited?
has_one :csync_record, dependent: :destroy

attribute :skip_whois_record_update, :boolean, default: false
attribute :skipped_domain_contacts_validation, :string, default: ''

after_initialize do
self.pending_json = {} if pending_json.blank?
Expand Down
2 changes: 1 addition & 1 deletion app/views/epp/domains/create.xml.builder
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
xml.epp_head do
xml.response do
xml.result('code' => '1000') do
xml.msg 'Command completed successfully'
xml.msg "Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain.skipped_domain_contacts_validation.present?}"
end

xml.resData do
Expand Down
2 changes: 1 addition & 1 deletion app/views/epp/domains/success.xml.builder
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
xml.epp_head do
xml.response do
xml.result('code' => '1000') do
xml.msg 'Command completed successfully'
xml.msg "Command completed successfully#{@domain.skipped_domain_contacts_validation if @domain && @domain.respond_to?(:skipped_domain_contacts_validation) && @domain.skipped_domain_contacts_validation.present?}"
end

render('epp/shared/trID', builder: xml)
Expand Down
Loading