mirror of
https://github.com/internetee/registry.git
synced 2025-08-22 17:20:49 +02:00
This commit refines the contact duplication checks and notification messages for domain creation and updates, ensuring consistency and addressing a bug in domain updates. **Key Changes:** * **Consistent Duplicate Contact Handling:** * The `check_for_cross_role_duplicates`, `remove_duplicate_contacts`, and `duplicate_contact?` methods are now more closely aligned between `DomainCreate` and `DomainUpdate` interactors. * `DomainUpdate` now correctly uses `admin_contact_ids=` and `tech_contact_ids=` to assign filtered contacts. This resolves a `PG::UniqueViolation` error that occurred when trying to re-associate existing contacts using `_attributes=` methods. * The `duplicate_contact?` method in `DomainCreate` was updated to match `DomainUpdate`, primarily checking for semantic duplicates based on attributes (name, ident, email, phone) rather than also including a `contact.code` check, which is more suitable for cross-role duplication. * **Standardized Notification Messages:** * The `notify_about_removed_duplicates` method in both interactors now generates a more concise message: ". [Role] contact [CODE] was discarded as duplicate;" for each discarded contact. * This message is appended to `domain.skipped_domain_contacts_validation`. * **EPP & REPP Response Updates:** * The `message` method in `Repp::V1::DomainsController` now correctly appends `domain.skipped_domain_contacts_validation` to the "Command completed successfully" message, ensuring it appears in REPP JSON responses for both create and update. * The EPP XML views (`app/views/epp/domains/create.xml.builder` and `app/views/epp/domains/success.xml.builder`) are updated to dynamically include `domain.skipped_domain_contacts_validation` in the `<msg>` tag. * **Model Attribute:** * Added `skipped_domain_contacts_validation` as a string attribute to the `Domain` model to store these notification messages. * **Test Refinements (Implicit):** * The related test suite for domain updates (`test/integration/epp/domain/base_test.rb`) was being updated to reflect these logic changes and assert the correct notification messages. These changes improve the robustness and consistency of contact management during domain operations, providing clearer feedback to users about discarded duplicate contacts.
291 lines
12 KiB
Ruby
291 lines
12 KiB
Ruby
require 'serializers/repp/domain'
|
|
module Repp
|
|
module V1
|
|
class DomainsController < BaseController # rubocop:disable Metrics/ClassLength
|
|
before_action :set_authorized_domain, only: %i[transfer_info destroy]
|
|
before_action :find_password, only: %i[update destroy]
|
|
before_action :validate_registrar_authorization, only: %i[transfer_info destroy]
|
|
before_action :forward_registrar_id, only: %i[create update destroy]
|
|
before_action :set_domain, only: %i[update]
|
|
|
|
THROTTLED_ACTIONS = %i[transfer_info transfer index create show update destroy].freeze
|
|
include Shunter::Integration::Throttle
|
|
|
|
api :GET, '/repp/v1/domains'
|
|
desc 'Get all existing domains'
|
|
def index
|
|
authorize! :info, Epp::Domain
|
|
records = current_user.registrar.domains.includes(:registrar, :registrant)
|
|
q = records.ransack(PartialSearchFormatter.format(search_params))
|
|
q.sorts = ['valid_to asc', 'created_at desc'] if q.sorts.empty?
|
|
# use distinct: false here due to ransack bug:
|
|
# https://github.com/activerecord-hackery/ransack/issues/429
|
|
domains = q.result(distinct: false)
|
|
|
|
limited_domains = domains.limit(limit).offset(offset)
|
|
|
|
render_success(data: { new_domain: records.any? ? serialized_domains([records.last]) : [],
|
|
domains: serialized_domains(limited_domains.to_a.uniq),
|
|
count: domains.count,
|
|
statuses: DomainStatus::STATUSES })
|
|
end
|
|
|
|
api :GET, '/repp/v1/domains/:domain_name'
|
|
desc 'Get a specific domain'
|
|
def show
|
|
@domain = Epp::Domain.find_by(name: params[:id])
|
|
authorize! :info, @domain
|
|
|
|
sponsor = @domain.registrar == current_user.registrar
|
|
serializer = Serializers::Repp::Domain.new(@domain, sponsored: sponsor)
|
|
render_success(data: { domain: serializer.to_json })
|
|
end
|
|
|
|
api :POST, '/repp/v1/domains'
|
|
desc 'Create a new domain'
|
|
param :domain, Hash, required: true, desc: 'Parameters for new domain' do
|
|
param :name, String, required: true, desc: 'Domain name to be registered'
|
|
param :registrant, String, required: true, desc: 'Registrant contact code'
|
|
param :reserved_pw, String, required: false, desc: 'Reserved password for domain'
|
|
param :transfer_code, String, required: false, desc: 'Desired transfer code for domain'
|
|
# param :period, Integer, required: true, desc: 'Registration period in months or years'
|
|
param :period_unit, String, required: true, desc: 'Period type (month m) or (year y)'
|
|
param :nameservers_attributes, Array, required: false, desc: 'Domain nameservers' do
|
|
param :hostname, String, required: true, desc: 'Nameserver hostname'
|
|
param :ipv4, Array, desc: 'Array of IPv4 addresses'
|
|
param :ipv6, Array, desc: 'Array of IPv4 addresses'
|
|
end
|
|
param :admin_contacts, Array, required: false, desc: 'Admin domain contacts codes'
|
|
param :tech_contacts, Array, required: false, desc: 'Tech domain contacts codes'
|
|
param :dnskeys_attributes, Array, required: false, desc: 'DNSSEC keys for domain' do
|
|
param_group :dns_keys_apidoc, Repp::V1::Domains::DnssecController
|
|
end
|
|
end
|
|
returns code: 200, desc: 'Successful domain registration response' do
|
|
property :code, Integer, desc: 'EPP code'
|
|
property :message, String, desc: 'EPP code explanation'
|
|
property :data, Hash do
|
|
property :domain, Hash do
|
|
property :name, String, 'Domain name'
|
|
end
|
|
end
|
|
end
|
|
def create
|
|
authorize! :create, Epp::Domain
|
|
@domain = Epp::Domain.new
|
|
|
|
action = Actions::DomainCreate.new(@domain, domain_params)
|
|
|
|
# rubocop:disable Style/AndOr
|
|
handle_errors(@domain) and return unless action.call
|
|
# rubocop:enable Style/AndOr
|
|
|
|
render_success(message: message, data: { domain: { name: @domain.name,
|
|
transfer_code: @domain.transfer_code,
|
|
id: @domain.reload.uuid } })
|
|
end
|
|
|
|
api :PUT, '/repp/v1/domains/:domain_name'
|
|
desc 'Update existing domain'
|
|
param :id, String, desc: 'Domain name in IDN / Puny format'
|
|
param :domain, Hash, required: true, desc: 'Changes of domain object' do
|
|
param :registrant, Hash, required: false, desc: 'New registrant object' do
|
|
param :code, String, required: true, desc: 'New registrant contact code'
|
|
param :verified, [true, false, 'true', 'false'], required: false,
|
|
desc: 'Registrant change is already verified'
|
|
end
|
|
param :transfer_code, String, required: false, desc: 'New authorization code'
|
|
end
|
|
def update
|
|
authorize!(:update, @domain, @password)
|
|
action = Actions::DomainUpdate.new(@domain, update_params, false)
|
|
unless action.call
|
|
handle_errors(@domain)
|
|
return
|
|
end
|
|
|
|
render_success(message: message, data: { domain: { name: @domain.name } })
|
|
end
|
|
|
|
api :GET, '/repp/v1/domains/:domain_name/transfer_info'
|
|
desc "Retrieve specific domain's transfer info"
|
|
def transfer_info
|
|
contact_fields = %i[code name ident ident_type ident_country_code phone email street city
|
|
zip country_code statuses]
|
|
|
|
data = {
|
|
domain: @domain.name,
|
|
registrant: @domain.registrant.as_json(only: contact_fields),
|
|
admin_contacts: @domain.admin_contacts.map { |c| c.as_json(only: contact_fields) },
|
|
tech_contacts: @domain.tech_contacts.map { |c| c.as_json(only: contact_fields) },
|
|
}
|
|
|
|
render_success(data: data)
|
|
end
|
|
|
|
api :POST, '/repp/v1/domains/transfer'
|
|
desc 'Transfer multiple domains'
|
|
def transfer
|
|
authorize! :transfer, Epp::Domain
|
|
@errors ||= []
|
|
@successful = []
|
|
transfer_params[:domain_transfers].each do |transfer|
|
|
initiate_transfer(transfer)
|
|
end
|
|
|
|
render_success(data: { success: @successful, failed: @errors })
|
|
end
|
|
|
|
api :DELETE, '/repp/v1/domains/:domain_name'
|
|
desc 'Delete specific domain'
|
|
param :id, String, desc: 'Domain name in IDN / Puny format'
|
|
param :domain, Hash, required: true, desc: 'Changes of domain object' do
|
|
param :delete, Hash, required: true, desc: 'Object holding verified key' do
|
|
param :verified, [true, false, 'true', 'false'], required: true,
|
|
desc: 'Whether to ask registrant verification or not'
|
|
end
|
|
end
|
|
def destroy
|
|
authorize!(:delete, @domain, @password)
|
|
action = Actions::DomainDelete.new(@domain, domain_params, current_user.registrar)
|
|
|
|
# rubocop:disable Style/AndOr
|
|
handle_errors(@domain) and return unless action.call
|
|
# rubocop:enable Style/AndOr
|
|
|
|
render_success(data: { domain: { name: @domain.name } })
|
|
end
|
|
|
|
private
|
|
|
|
def serialized_domains(domains)
|
|
return domains.pluck(:name) unless index_params[:details] == 'true'
|
|
|
|
simple = index_params[:simple] == 'true' || false
|
|
domains.map { |d| Serializers::Repp::Domain.new(d, simplify: simple).to_json }
|
|
end
|
|
|
|
def initiate_transfer(transfer)
|
|
domain = Epp::Domain.find_or_initialize_by(name: transfer[:domain_name])
|
|
action = Actions::DomainTransfer.new(domain, transfer[:transfer_code],
|
|
current_user.registrar)
|
|
|
|
if action.call
|
|
@successful << { type: 'domain_transfer', domain_name: domain.name }
|
|
else
|
|
@errors << { type: 'domain_transfer', domain_name: domain.name,
|
|
errors: domain.errors.where(:epp_errors).first.options }
|
|
end
|
|
end
|
|
|
|
def transfer_params
|
|
params.require(:data).require(:domain_transfers)
|
|
params.require(:data).permit(domain_transfers: [%i[domain_name transfer_code]])
|
|
end
|
|
|
|
def transfer_info_params
|
|
params.require(:id)
|
|
params.permit(:id, :legal_document, delete: [:verified])
|
|
end
|
|
|
|
def forward_registrar_id
|
|
return unless params[:domain]
|
|
|
|
params[:domain][:registrar] = current_user.registrar.id
|
|
end
|
|
|
|
def set_domain
|
|
registrar = current_user.registrar
|
|
|
|
@domain = Epp::Domain.find_by(registrar: registrar, name: params[:id])
|
|
@domain ||= Epp::Domain.find_by!(registrar: registrar, name_puny: params[:id])
|
|
|
|
return @domain if @domain
|
|
|
|
raise ActiveRecord::RecordNotFound
|
|
end
|
|
|
|
def find_password
|
|
@password = domain_params[:transfer_code]
|
|
end
|
|
|
|
def set_authorized_domain
|
|
@epp_errors ||= ActiveModel::Errors.new(self)
|
|
@domain = domain_from_url_hash
|
|
end
|
|
|
|
def validate_registrar_authorization
|
|
return if @domain.registrar == current_user.registrar
|
|
return if @domain.transfer_code.eql?(request.headers['Auth-Code'])
|
|
|
|
@epp_errors.add(:epp_errors,
|
|
code: 2202,
|
|
msg: I18n.t('errors.messages.epp_authorization_error'))
|
|
handle_errors
|
|
end
|
|
|
|
def domain_from_url_hash
|
|
entry = params[:id]
|
|
return Epp::Domain.find(entry) if entry.match?(/\A[0-9]+\z/)
|
|
|
|
Epp::Domain.find_by!('name = ? OR name_puny = ?', entry, entry)
|
|
end
|
|
|
|
def limit
|
|
index_params[:limit]
|
|
end
|
|
|
|
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
|
|
nameservers_hostname_eq valid_to_gteq valid_to_lteq
|
|
statuses_contains_array] + [s: []])
|
|
end
|
|
|
|
def search_params
|
|
index_params.fetch(:q, {}) || {}
|
|
end
|
|
|
|
def update_params
|
|
dup_params = domain_params.to_h.dup
|
|
return dup_params unless dup_params[:contacts]
|
|
|
|
modify_contact_params(dup_params)
|
|
end
|
|
|
|
def modify_contact_params(params)
|
|
new_contact_params = params[:contacts].map { |c| c.to_h.symbolize_keys }
|
|
old_contact_params = @domain.domain_contacts.includes(:contact).map do |c|
|
|
{ code: c.contact.code, type: c.name.downcase }
|
|
end
|
|
params[:contacts] = (new_contact_params - old_contact_params).map do |c|
|
|
c.merge(action: 'add')
|
|
end
|
|
params[:contacts].concat((old_contact_params - new_contact_params)
|
|
.map { |c| c.merge(action: 'rem') })
|
|
params
|
|
end
|
|
|
|
def domain_params
|
|
params.require(:domain).permit(:name, :period, :period_unit, :registrar, :transfer_code,
|
|
:reserved_pw, :legal_document, :registrant,
|
|
legal_document: %i[body type], registrant: [%i[code verified]],
|
|
dns_keys: [%i[id flags alg protocol public_key action]],
|
|
nameservers: [[:id, :hostname, :action, { ipv4: [], ipv6: [] }]],
|
|
contacts: [%i[code type action]],
|
|
nameservers_attributes: [[:hostname, { ipv4: [], ipv6: [] }]],
|
|
admin_contacts: [], tech_contacts: [],
|
|
dnskeys_attributes: [%i[flags alg protocol public_key]],
|
|
delete: [:verified])
|
|
end
|
|
end
|
|
end
|
|
end
|