From a5ffce290de6c8f5e219546640865b1f059dbc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergei=20Ts=C3=B5ganov?= Date: Mon, 6 Jun 2022 13:43:30 +0300 Subject: [PATCH] Updated REPP API for new registrar portal --- .gitignore | 1 + Dockerfile | 1 + Gemfile | 4 +- Gemfile.lock | 9 +- app/controllers/repp/v1/account_controller.rb | 149 +++++++++++++++ .../repp/v1/accounts_controller.rb | 33 ---- app/controllers/repp/v1/base_controller.rb | 29 ++- .../repp/v1/contacts_controller.rb | 172 +++++++++++++----- .../v1/domains/base_contacts_controller.rb | 22 +-- .../repp/v1/domains/renews_controller.rb | 12 +- app/controllers/repp/v1/domains_controller.rb | 128 +++++++++---- .../repp/v1/invoices_controller.rb | 118 ++++++++++++ .../repp/v1/registrar/auth_controller.rb | 49 +++++ .../v1/registrar/nameservers_controller.rb | 37 ++-- .../v1/registrar/notifications_controller.rb | 4 +- .../repp/v1/registrar/summary_controller.rb | 111 +++++++++++ app/interactions/actions/domain_delete.rb | 6 +- app/interactions/actions/domain_update.rb | 7 +- app/interactions/actions/invoice_cancel.rb | 15 ++ app/models/ability.rb | 7 +- app/models/action.rb | 18 +- app/models/admin_domain_contact.rb | 2 +- app/models/api_user.rb | 12 +- .../balance_auto_reload_types/threshold.rb | 4 +- app/models/bulk_action.rb | 1 - app/models/concerns/invoice/cancellable.rb | 10 + app/models/concerns/invoice/payable.rb | 2 + app/models/contact.rb | 87 +++++---- app/models/contact_update_action.rb | 1 + app/models/deposit.rb | 1 + app/models/depp/user.rb | 4 +- app/models/domain_transfer.rb | 8 +- app/models/invoice.rb | 7 + app/models/notification.rb | 1 + app/models/registrar.rb | 12 +- app/models/tech_domain_contact.rb | 2 +- app/models/user.rb | 2 +- .../registrar/domain_list_csv_presenter.rb | 5 +- app/views/epp/poll/poll_req.xml.builder | 2 +- .../registrar/domains/_search_form.html.erb | 4 +- app/views/registrar/polls/show.haml | 9 +- config/initializers/arel.rb | 25 +++ config/initializers/omniauth.rb | 91 ++++----- config/initializers/ransack.rb | 7 + config/locales/en.yml | 5 + config/routes.rb | 32 +++- lib/serializers/repp/contact.rb | 52 +++++- lib/serializers/repp/domain.rb | 74 ++++++-- lib/serializers/repp/invoice.rb | 85 +++++++++ test/integration/epp/poll_test.rb | 2 +- .../v1/{accounts => account}/balance_test.rb | 8 +- .../repp/v1/contacts/create_test.rb | 80 ++++---- .../integration/repp/v1/contacts/list_test.rb | 18 +- .../integration/repp/v1/contacts/show_test.rb | 2 +- .../repp/v1/contacts/update_test.rb | 28 +-- .../v1/domains/contact_replacement_test.rb | 7 +- .../repp/v1/domains/delete_test.rb | 16 +- test/integration/repp/v1/domains/list_test.rb | 4 +- .../repp/v1/domains/renews_test.rb | 6 +- .../repp/v1/domains/update_test.rb | 17 +- test/models/registrant_user_test.rb | 10 +- 61 files changed, 1269 insertions(+), 408 deletions(-) create mode 100644 app/controllers/repp/v1/account_controller.rb delete mode 100644 app/controllers/repp/v1/accounts_controller.rb create mode 100644 app/controllers/repp/v1/invoices_controller.rb create mode 100644 app/controllers/repp/v1/registrar/auth_controller.rb create mode 100644 app/controllers/repp/v1/registrar/summary_controller.rb create mode 100644 app/interactions/actions/invoice_cancel.rb delete mode 100644 app/models/bulk_action.rb create mode 100644 app/models/contact_update_action.rb create mode 100644 config/initializers/arel.rb create mode 100644 config/initializers/ransack.rb create mode 100644 lib/serializers/repp/invoice.rb rename test/integration/repp/v1/{accounts => account}/balance_test.rb (88%) diff --git a/.gitignore b/.gitignore index 08606ba47..a8499459f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /coverage/ /.bundle /vendor/bundle +/vendor/gems /config/database.yml /config/application.yml /config/environments/development.rb diff --git a/Dockerfile b/Dockerfile index 9c46182a3..3d065e5bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM internetee/ruby:3.0-buster RUN mkdir -p /opt/webapps/app/tmp/pids WORKDIR /opt/webapps/app COPY Gemfile Gemfile.lock ./ +# ADD vendor/gems/omniauth-tara ./vendor/gems/omniauth-tara RUN gem install bundler && bundle install --jobs 20 --retry 5 EXPOSE 3000 diff --git a/Gemfile b/Gemfile index 08c2d0bb5..3761831d0 100644 --- a/Gemfile +++ b/Gemfile @@ -57,10 +57,9 @@ gem 'digidoc_client', ref: '1645e83a5a548addce383f75703b0275c5310c32' # TARA -gem 'omniauth' gem 'omniauth-rails_csrf_protection' gem 'omniauth-tara', github: 'internetee/omniauth-tara' - +# gem 'omniauth-tara', path: 'vendor/gems/omniauth-tara' gem 'airbrake' gem 'daemons-rails', '1.2.1' @@ -81,6 +80,7 @@ gem 'lhv', github: 'internetee/lhv', branch: 'master' gem 'rexml' gem 'wkhtmltopdf-binary', '~> 0.12.5.1' + gem 'directo', github: 'internetee/directo', branch: 'master' group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index a53651e1b..8f1d86c23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -338,7 +338,7 @@ GEM omniauth-rails_csrf_protection (0.1.2) actionpack (>= 4.2) omniauth (>= 1.3.1) - openid_connect (1.2.0) + openid_connect (1.3.0) activemodel attr_required (>= 1.0.0) json-jwt (>= 1.5.0) @@ -477,7 +477,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) spy (1.0.1) - swd (1.2.0) + swd (1.3.0) activesupport (>= 3) attr_required (>= 0.0.5) httpclient (>= 2.4) @@ -496,7 +496,7 @@ GEM validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) - validate_url (1.0.13) + validate_url (1.0.15) activemodel (>= 3.0.0) public_suffix validates_email_format_of (1.6.3) @@ -511,7 +511,7 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (>= 3.0, < 4.0) - webfinger (1.1.0) + webfinger (1.2.0) activesupport httpclient (>= 2.4) webmock (3.14.0) @@ -572,7 +572,6 @@ DEPENDENCIES newrelic-infinite_tracing newrelic_rpm nokogiri (~> 1.13.0) - omniauth omniauth-rails_csrf_protection omniauth-tara! paper_trail (~> 12.1) diff --git a/app/controllers/repp/v1/account_controller.rb b/app/controllers/repp/v1/account_controller.rb new file mode 100644 index 000000000..e412b784d --- /dev/null +++ b/app/controllers/repp/v1/account_controller.rb @@ -0,0 +1,149 @@ +module Repp + module V1 + class AccountController < BaseController + load_and_authorize_resource + + api :get, '/repp/v1/account' + desc 'Get all activities' + def index + records = current_user.registrar.cash_account.activities + + q = records.ransack(search_params) + q.sorts = 'created_at desc' if q.sorts.empty? + activities = q.result(distinct: true) + + limited_activities = activities.limit(limit).offset(offset) + .includes(:invoice) + + render_success(data: { activities: serialized_activities(limited_activities), + count: activities.count, + types_for_select: AccountActivity.types_for_select }) + end + + api :get, '/repp/v1/account/details' + desc 'Get current registrar account details' + def details + registrar = current_user.registrar + type = registrar.settings['balance_auto_reload']&.dig('type') + resp = { account: { billing_email: registrar.billing_email, + iban: registrar.iban, + iban_max_length: Iban.max_length, + linked_users: serialized_users(current_user.linked_users), + balance_auto_reload: type, + min_deposit: Setting.minimum_deposit } } + render_success(data: resp) + end + + api :put, '/repp/v1/account' + desc 'Update current registrar account details' + def update + registrar = current_user.registrar + unless registrar.update(account_params) + handle_non_epp_errors(registrar) + return + end + + render_success(data: { account: account_params }, + message: I18n.t('registrar.account.update.saved')) + end + + api :post, '/repp/v1/account/update_auto_reload_balance' + desc 'Enable current registrar balance auto reload' + def update_auto_reload_balance + type = BalanceAutoReloadTypes::Threshold.new(type_params) + unless type.valid? + handle_non_epp_errors(type) + return + end + + settings = { balance_auto_reload: { type: type.as_json } } + current_user.registrar.update!(settings: settings) + render_success(data: { settings: settings }, + message: I18n.t('registrar.settings.balance_auto_reload.update.saved')) + end + + api :get, '/repp/v1/account/disable_auto_reload_balance' + desc 'Disable current registrar balance auto reload' + def disable_auto_reload_balance + registrar = current_user.registrar + registrar.settings.delete('balance_auto_reload') + registrar.save! + + render_success(data: { settings: registrar.settings }, + message: I18n.t('registrar.settings.balance_auto_reload.destroy.disabled')) + end + + api :get, '/repp/v1/account/balance' + desc "Get account's balance" + def balance + resp = { balance: current_user.registrar.cash_account.balance, + currency: current_user.registrar.cash_account.currency } + if params[:detailed] == 'true' + activities = current_user.registrar.cash_account.activities.order(created_at: :desc) + activities = activities.where('created_at >= ?', params[:from]) if params[:from] + activities = activities.where('created_at <= ?', params[:until]) if params[:until] + resp[:transactions] = serialized_activities(activities) + end + render_success(data: resp) + end + + private + + def account_params + params.require(:account).permit(:billing_email, :iban) + end + + def index_params + params.permit(:id, :limit, :offset, :q, + :page, :per_page, + q: [:description_matches, :created_at_gteq, + :created_at_lteq, :s, { s: [] }, { activity_type_in: [] }]) + end + + def type_params + permitted_params = params.require(:type).permit(:amount, :threshold) + normalize_params(permitted_params) + end + + def normalize_params(params) + params[:amount] = params[:amount].to_f + params[:threshold] = params[:threshold].to_f + params + end + + def search_params + index_params.fetch(:q, {}) + end + + def limit + index_params[:limit] || 200 + end + + def offset + index_params[:offset] || 0 + end + + def serialized_users(users) + arr = [] + users.each do |u| + arr << { id: u.id, username: u.username, + role: u.roles.first } + end + + arr + end + + def serialized_activities(activities) + arr = [] + activities.each do |a| + arr << { created_at: a.created_at, description: a.description, + type: a.activity_type == 'add_credit' ? 'credit' : 'debit', + sum: a.sum, balance: a.new_balance, currency: a.currency, + updator: a.updator_str } + end + + arr + end + end + end +end diff --git a/app/controllers/repp/v1/accounts_controller.rb b/app/controllers/repp/v1/accounts_controller.rb deleted file mode 100644 index 388bc9a94..000000000 --- a/app/controllers/repp/v1/accounts_controller.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Repp - module V1 - class AccountsController < BaseController - api :GET, '/repp/v1/accounts/balance' - desc "Get account's balance" - def balance - resp = { balance: current_user.registrar.cash_account.balance, - currency: current_user.registrar.cash_account.currency } - resp[:transactions] = activities if params[:detailed] == 'true' - render_success(data: resp) - end - - def activities - arr = [] - registrar_activities.each do |a| - arr << { created_at: a.created_at, description: a.description, - type: a.activity_type == 'add_credit' ? 'credit' : 'debit', - sum: a.sum, balance: a.new_balance } - end - - arr - end - - def registrar_activities - activities = current_user.registrar.cash_account.activities.order(created_at: :desc) - activities = activities.where('created_at >= ?', params[:from]) if params[:from] - activities = activities.where('created_at <= ?', params[:until]) if params[:until] - - activities - end - end - end -end diff --git a/app/controllers/repp/v1/base_controller.rb b/app/controllers/repp/v1/base_controller.rb index 3e9ab5715..5ab910278 100644 --- a/app/controllers/repp/v1/base_controller.rb +++ b/app/controllers/repp/v1/base_controller.rb @@ -1,12 +1,12 @@ module Repp module V1 class BaseController < ActionController::API # rubocop:disable Metrics/ClassLength + attr_reader :current_user + around_action :log_request before_action :authenticate_user before_action :validate_webclient_ca before_action :check_ip_restriction - attr_reader :current_user - before_action :set_paper_trail_whodunnit private @@ -22,6 +22,10 @@ module Repp rescue Apipie::ParamInvalid => e @response = { code: 2005, message: e.message.gsub(/\n/, '. ') } render(json: @response, status: :bad_request) + rescue CanCan::AccessDenied => e + @response = { code: 2201, message: 'Authorization error' } + logger.error e.to_s + render(json: @response, status: :unauthorized) ensure create_repp_log end @@ -65,7 +69,6 @@ module Repp def handle_errors(obj = nil) @epp_errors ||= ActiveModel::Errors.new(self) - if obj obj.construct_epp_errors obj.errors.each { |error| @epp_errors.import error } @@ -85,6 +88,12 @@ module Repp render(json: @response, status: status) end + def handle_non_epp_errors(obj, message = nil) + @response = { message: message || obj.errors.full_messages.join(', '), + data: {} } + render(json: @response, status: :bad_request) + end + def basic_token pattern = /^Basic / header = request.headers['Authorization'] @@ -94,7 +103,8 @@ module Repp def authenticate_user username, password = Base64.urlsafe_decode64(basic_token).split(':') - @current_user ||= ApiUser.find_by(username: username, plain_text_password: password) + @current_user ||= ApiUser.find_by(username: username, plain_text_password: password, + active: true) return if @current_user @@ -123,6 +133,7 @@ module Repp return unless webclient_request? request_name = request.env['HTTP_SSL_CLIENT_S_DN_CN'] + webclient_cn = ENV['webclient_cert_common_name'] || 'webclient' return if request_name == webclient_cn @@ -135,6 +146,16 @@ module Repp def logger Rails.logger end + + def auth_values_to_data(registrar:) + data = current_user.as_json(only: %i[id username roles]) + data[:registrar_name] = registrar.name + data[:legaldoc_mandatory] = registrar.legaldoc_mandatory? + data[:balance] = { amount: registrar.cash_account&.balance, + currency: registrar.cash_account&.currency } + data[:abilities] = Ability.new(current_user).permissions + data + end end end end diff --git a/app/controllers/repp/v1/contacts_controller.rb b/app/controllers/repp/v1/contacts_controller.rb index c19ca3967..01230c7fe 100644 --- a/app/controllers/repp/v1/contacts_controller.rb +++ b/app/controllers/repp/v1/contacts_controller.rb @@ -3,23 +3,61 @@ module Repp module V1 class ContactsController < BaseController # rubocop:disable Metrics/ClassLength before_action :find_contact, only: %i[show update destroy] + skip_around_action :log_request, only: :search api :get, '/repp/v1/contacts' desc 'Get all existing contacts' def index - record_count = current_user.registrar.contacts.count - contacts = showable_contacts(params[:details], params[:limit] || 200, - params[:offset] || 0) - @response = { contacts: contacts, total_number_of_records: record_count } - render(json: @response, status: :ok) + authorize! :check, Epp::Contact + records = current_user.registrar.contacts.order(created_at: :desc) + + q = records.ransack(search_params) + q.sorts = 'created_at desc' if q.sorts.empty? + contacts = q.result(distinct: true) + + limited_contacts = contacts.limit(limit).offset(offset) + .includes(:domain_contacts, :registrant_domains, :registrar) + + render_success(data: { contacts: serialized_contacts(limited_contacts), + count: contacts.count, + statuses: Contact::STATUSES, + ident_types: Contact::Ident.types }) end + # rubocop:disable Metrics/MethodLength + api :get, '/repp/v1/contacts/search(/:id)' + desc 'Search all existing contacts by optional id or query param' + def search + scope = current_user.registrar.contacts + if params[:query] + escaped_str = ActiveRecord::Base.connection.quote_string params[:query] + scope = scope.where("name ilike '%#{escaped_str}%' OR code ilike '%#{escaped_str}%' + OR ident ilike '%#{escaped_str}%'") + elsif params[:id] + scope = scope.where(code: params[:id]) + end + + render_success(data: scope.limit(10) + .map do |c| + { value: c.code, + label: "#{c.code} #{c.name}", + selected: scope.size == 1 } + end) + end + # rubocop:enable Metrics/MethodLength + api :get, '/repp/v1/contacts/:contact_code' desc 'Get a specific contact' def show - serializer = ::Serializers::Repp::Contact.new(@contact, - show_address: Contact.address_processing?) - render_success(data: serializer.to_json) + authorize! :check, Epp::Contact + + simple = params[:simple] == 'true' || false + serializer = Serializers::Repp::Contact.new(@contact, + show_address: Contact.address_processing?, + domain_params: domain_filter_params, + simplify: simple) + + render_success(data: { contact: serializer.to_json }) end api :get, '/repp/v1/contacts/check/:contact_code' @@ -35,7 +73,7 @@ module Repp desc 'Create a new contact' def create @contact = Epp::Contact.new(contact_params_with_address, current_user.registrar, epp: false) - action = Actions::ContactCreate.new(@contact, params[:legal_document], + action = Actions::ContactCreate.new(@contact, contact_params[:legal_document], contact_ident_params) unless action.call @@ -50,7 +88,7 @@ module Repp desc 'Update existing contact' def update action = Actions::ContactUpdate.new(@contact, contact_params_with_address(required: false), - params[:legal_document], + contact_params[:legal_document], contact_ident_params(required: false), current_user) unless action.call @@ -73,29 +111,71 @@ module Repp render_success end - def contact_addr_present? - return false unless contact_addr_params.key?(:addr) + private - contact_addr_params[:addr].keys.any? + def index_params + params.permit(:id, :limit, :offset, :details, :q, :simple, + :page, :per_page, :domain_filter, + domain_filter: [], + q: %i[s name_matches code_eq ident_matches ident_type_eq + email_matches country_code_eq types_contains_array + updated_at_gteq created_at_gteq created_at_lteq + statuses_contains_array] + [s: []]) + end + + def search_params + index_params.fetch(:q, {}) + end + + def domain_filter_params + filter_params = index_params.slice(:id, :page, :per_page, :domain_filter).to_h + filter_params.merge!({ sort: hashify(index_params[:q].fetch(:s)) }) if index_params[:q] + filter_params + end + + def hashify(sort) + return unless sort + + sort_hash = {} + if sort.is_a?(Array) + sort.each do |s| + sort_hash.merge!(Hash[*s.split(' ')]) + end + else + sort_hash.merge!(Hash[*sort.split(' ')]) + end + sort_hash + end + + def limit + index_params[:limit] || 200 + end + + def offset + index_params[:offset] || 0 + end + + def serialized_contacts(contacts) + return contacts.map {|c| c.code } unless index_params[:details] == 'true' + + address_processing = Contact.address_processing? + contacts.map do |c| + Serializers::Repp::Contact.new(c, show_address: address_processing).to_json + end + end + + def contact_addr_present? + return false unless contact_addr_params + + contact_addr_params.keys.any? end def create_update_success_body - { code: opt_addr? ? 1100 : nil, data: { contact: { id: @contact.code } }, + { code: opt_addr? ? 1100 : nil, + data: { contact: { code: @contact.code } }, message: opt_addr? ? I18n.t('epp.contacts.completed_without_address') : nil } end - def showable_contacts(details, limit, offset) - contacts = current_user.registrar.contacts.limit(limit).offset(offset) - - return contacts.pluck(:code) unless details - - contacts.map do |contact| - serializer = ::Serializers::Repp::Contact.new(contact, - show_address: Contact.address_processing?) - serializer.to_json - end - end - def opt_addr? !Contact.address_processing? && contact_addr_present? end @@ -106,36 +186,36 @@ module Repp end def contact_params_with_address(required: true) - return contact_create_params(required: required) unless contact_addr_params.key?(:addr) + return contact_create_params(required: required) unless contact_addr_present? - addr = {} - contact_addr_params[:addr].each_key { |k| addr[k] = contact_addr_params[:addr][k] } - contact_create_params(required: required).merge(addr) + contact_create_params(required: required).merge(contact_addr_params) end def contact_create_params(required: true) - params.require(:contact).require(%i[name email phone]) if required - params.require(:contact).permit(:name, :email, :phone, :id) + create_params = %i[name email phone] + contact_params.require(create_params) if required + contact_params.slice(*create_params) end def contact_ident_params(required: true) - if required - params.require(:contact).require(:ident).require(%i[ident ident_type ident_country_code]) - params.require(:contact).require(:ident).permit(:ident, :ident_type, :ident_country_code) - else - params.permit(contact: { ident: %i[ident ident_type ident_country_code] }) - end - - params[:contact][:ident] + ident_params = %i[ident ident_type ident_country_code] + contact_params.require(:ident).require(ident_params) if required + contact_params[:ident].to_h end def contact_addr_params - if Contact.address_processing? - params.require(:contact).require(:addr).require(%i[country_code city street zip]) - params.require(:contact).require(:addr).permit(:country_code, :city, :street, :zip) - else - params.require(:contact).permit(addr: %i[country_code city street zip]) - end + return contact_params[:addr] unless Contact.address_processing? + + addr_params = %i[country_code city street zip] + contact_params.require(:addr).require(addr_params) + contact_params[:addr] + end + + def contact_params + params.require(:contact).permit(:name, :email, :phone, :legal_document, + legal_document: %i[body type], + ident: [%i[ident ident_type ident_country_code]], + addr: [%i[country_code city street zip state]]) end end end diff --git a/app/controllers/repp/v1/domains/base_contacts_controller.rb b/app/controllers/repp/v1/domains/base_contacts_controller.rb index 65dbea9ac..225b14b58 100644 --- a/app/controllers/repp/v1/domains/base_contacts_controller.rb +++ b/app/controllers/repp/v1/domains/base_contacts_controller.rb @@ -2,19 +2,16 @@ module Repp module V1 module Domains class BaseContactsController < BaseController - before_action :set_current_contact, only: [:update] - before_action :set_new_contact, only: [:update] + before_action :set_contacts, only: [:update] - def set_current_contact - @current_contact = current_user.registrar.contacts - .find_by!(code: contact_params[:current_contact_id]) - end - - def set_new_contact - @new_contact = current_user.registrar.contacts.find_by!(code: params[:new_contact_id]) + def set_contacts + contacts = current_user.registrar.contacts + @current_contact = contacts.find_by!(code: contact_params[:current_contact_id]) + @new_contact = contacts.find_by!(code: contact_params[:new_contact_id]) end def update + authorize! :manage, :repp @epp_errors ||= ActiveModel::Errors.new(self) return unless @new_contact.invalid? @@ -26,8 +23,11 @@ module Repp private def contact_params - params.require(%i[current_contact_id new_contact_id]) - params.permit(:current_contact_id, :new_contact_id) + param_list = %i[current_contact_id new_contact_id] + params.require(param_list) + params.permit(:current_contact_id, :new_contact_id, + contact: {}, + admin_contact: [param_list]) end end end diff --git a/app/controllers/repp/v1/domains/renews_controller.rb b/app/controllers/repp/v1/domains/renews_controller.rb index af40e17b1..f963cd3a7 100644 --- a/app/controllers/repp/v1/domains/renews_controller.rb +++ b/app/controllers/repp/v1/domains/renews_controller.rb @@ -8,28 +8,29 @@ module Repp api :POST, 'repp/v1/domains/:domain_name/renew' desc 'Renew domain' - param :renew, Hash, required: true, desc: 'Renew parameters' do + param :renews, Hash, required: true, desc: 'Renew parameters' do param :period, Integer, required: true, desc: 'Renew period. Month (m) or year (y)' param :period_unit, String, required: true, desc: 'For how many months or years to renew' param :exp_date, String, required: true, desc: 'Current expiry date for domain' end def create authorize!(:renew, @domain) - action = Actions::DomainRenew.new(@domain, renew_params[:renew], current_user.registrar) + action = Actions::DomainRenew.new(@domain, renew_params[:renews], current_user.registrar) unless action.call handle_errors(@domain) return end - render_success(data: { domain: { name: @domain.name } }) + render_success(data: { domain: { name: @domain.name, id: @domain.uuid } }) end def bulk_renew + authorize! :manage, :repp renew = run_bulk_renew_task(@domains, bulk_renew_params[:renew_period]) return render_success(data: { updated_domains: @domains.map(&:name) }) if renew.valid? - msg = renew.errors.keys.map { |k, _v| renew.errors[k] }.join(', ') + msg = renew.errors.attribute_names.map { |k, _v| renew.errors[k] }.join(', ') @epp_errors.add(:epp_errors, msg: msg, code: '2002') handle_errors end @@ -37,7 +38,7 @@ module Repp private def renew_params - params.permit(:domain_id, renew: %i[period period_unit exp_date]) + params.permit(:domain_id, renews: %i[period period_unit exp_date]) end def validate_renew_period @@ -53,6 +54,7 @@ module Repp if bulk_renew_params[:domains].instance_of?(Array) @domains = bulk_renew_domains + @epp_errors.add(:epp_errors, msg: 'Domains cannot be empty', code: '2005') if @domains.empty? else @epp_errors.add(:epp_errors, msg: 'Domains attribute must be an array', code: '2005') end diff --git a/app/controllers/repp/v1/domains_controller.rb b/app/controllers/repp/v1/domains_controller.rb index 06d4a0330..37f735b69 100644 --- a/app/controllers/repp/v1/domains_controller.rb +++ b/app/controllers/repp/v1/domains_controller.rb @@ -3,6 +3,7 @@ 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] @@ -10,20 +11,31 @@ module Repp api :GET, '/repp/v1/domains' desc 'Get all existing domains' def index + authorize! :info, Epp::Domain records = current_user.registrar.domains - domains = records.limit(limit).offset(offset) + q = records.ransack(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) - render_success(data: { domains: serialized_domains(domains), - total_number_of_records: records.count }) + limited_domains = domains.limit(limit).offset(offset).includes(:registrar, :registrant) + + 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]) + @domain = Epp::Domain.find_by_name(params[:id]) + authorize! :info, @domain + sponsor = @domain.registrar == current_user.registrar - render_success(data: { domain: Serializers::Repp::Domain.new(@domain, - sponsored: sponsor).to_json }) + serializer = Serializers::Repp::Domain.new(@domain, sponsored: sponsor) + render_success(data: { domain: serializer.to_json }) end api :POST, '/repp/v1/domains' @@ -33,7 +45,7 @@ module Repp 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, String, required: true, desc: 'Registration period in months or years' + 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' @@ -56,15 +68,18 @@ module Repp end end def create - authorize!(:create, Epp::Domain) + authorize! :create, Epp::Domain @domain = Epp::Domain.new - action = Actions::DomainCreate.new(@domain, domain_create_params) + + 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(data: { domain: { name: @domain.name, transfer_code: @domain.transfer_code } }) + render_success(data: { domain: { name: @domain.name, + transfer_code: @domain.transfer_code, + id: @domain.reload.uuid } }) end api :PUT, '/repp/v1/domains/:domain_name' @@ -73,20 +88,20 @@ module Repp 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], required: false, - desc: 'Registrant change is already verified' + 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 - action = Actions::DomainUpdate.new(@domain, params[:domain], false) - + authorize!(:update, @domain, @password) + action = Actions::DomainUpdate.new(@domain, update_params, false) unless action.call handle_errors(@domain) return end - render_success(data: { domain: { name: @domain.name } }) + render_success(data: { domain: { name: @domain.name, id: @domain.uuid } }) end api :GET, '/repp/v1/domains/:domain_name/transfer_info' @@ -108,23 +123,28 @@ module Repp 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 :delete, Hash, required: true, desc: 'Object holding verified key' do - param :verified, [true, false], required: true, - desc: 'Whether to ask registrant verification or not' + 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 - action = Actions::DomainDelete.new(@domain, params, current_user.registrar) + 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 @@ -138,7 +158,8 @@ module Repp def serialized_domains(domains) return domains.pluck(:name) unless index_params[:details] == 'true' - domains.map { |d| Serializers::Repp::Domain.new(d).to_json } + simple = index_params[:simple] == 'true' || false + domains.map { |d| Serializers::Repp::Domain.new(d, simplify: simple).to_json } end def initiate_transfer(transfer) @@ -155,18 +176,13 @@ module Repp end def transfer_params - params.require(:data).require(:domain_transfers).each do |t| - t.require(:domain_name) - t.permit(:domain_name) - t.require(:transfer_code) - t.permit(:transfer_code) - end - params.require(:data).permit(domain_transfers: %i[domain_name transfer_code]) + 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) + params.permit(:id, :legal_document, delete: [:verified]) end def forward_registrar_id @@ -177,6 +193,7 @@ module Repp 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]) @@ -185,6 +202,10 @@ module Repp 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 @@ -201,7 +222,7 @@ module Repp end def domain_from_url_hash - entry = transfer_info_params[:id] + 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) @@ -216,15 +237,48 @@ module Repp end def index_params - params.permit(:limit, :offset, :details) + params.permit(:limit, :offset, :details, :simple, :q, + q: %i[s name_matches registrant_id_eq contacts_ident_eq + nameservers_hostname_eq valid_to_gteq valid_to_lteq + statuses_contains_array] + [s: []]) end - def domain_create_params - params.require(:domain).permit(:name, :registrant, :period, :period_unit, :registrar, - :transfer_code, :reserved_pw, - dnskeys_attributes: [%i[flags alg protocol public_key]], - nameservers_attributes: [[:hostname, { ipv4: [], ipv6: [] }]], - admin_contacts: [], tech_contacts: []) + 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] + + new_contact_params = dup_params[:contacts].map do |c| + c.to_h.symbolize_keys + end + + old_contact_params = @domain.domain_contacts.map do |c| + { code: c.contact_code_cache, type: c.name.downcase } + end + dup_params[:contacts] = (new_contact_params - old_contact_params).map { |c| c.merge(action: 'add') } + dup_params[:contacts].concat((old_contact_params - new_contact_params) + .map { |c| c.merge(action: 'rem') }) + + dup_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 diff --git a/app/controllers/repp/v1/invoices_controller.rb b/app/controllers/repp/v1/invoices_controller.rb new file mode 100644 index 000000000..c8c6676ec --- /dev/null +++ b/app/controllers/repp/v1/invoices_controller.rb @@ -0,0 +1,118 @@ +require 'serializers/repp/invoice' +module Repp + module V1 + class InvoicesController < BaseController + load_and_authorize_resource + + api :get, '/repp/v1/invoices' + desc 'Get all invoices' + def index + records = current_user.registrar.invoices + + q = records.ransack(search_params) + q.sorts = 'created_at desc' if q.sorts.empty? + invoices = q.result(distinct: true) + + limited_invoices = invoices.limit(limit).offset(offset) + .includes(:items, :account_activity, :buyer) + + render_success(data: { invoices: serialized_invoices(limited_invoices), + count: invoices.count }) + end + + api :get, '/repp/v1/invoices/:id' + desc 'Get a specific invoice' + def show + serializer = Serializers::Repp::Invoice.new(@invoice) + render_success(data: { invoice: serializer.to_json }) + end + + api :get, '/repp/v1/invoices/:id/download' + desc 'Download a specific invoice as pdf file' + def download + filename = "Invoice-#{@invoice.number}.pdf" + @response = { code: 1000, message: 'Command completed successfully', + data: filename } + send_data @invoice.as_pdf, filename: filename + end + + api :post, '/repp/v1/invoices/:id/send_to_recipient' + desc 'Send invoice pdf to recipient' + def send_to_recipient + recipient = invoice_params[:recipient] + InvoiceMailer.invoice_email(invoice: @invoice, recipient: recipient) + .deliver_now + serializer = Serializers::Repp::Invoice.new(@invoice, simplify: true) + render_success(data: { invoice: serializer.to_json + .merge!(recipient: recipient) }) + end + + api :post, '/repp/v1/invoices/:id/cancel' + desc 'Cancel a specific invoice' + def cancel + action = Actions::InvoiceCancel.new(@invoice) + if action.call + EisBilling::SendInvoiceStatus.send_info(invoice_number: @invoice.number, + status: 'cancelled') + else + handle_non_epp_errors(@invoice) + return + end + + serializer = Serializers::Repp::Invoice.new(@invoice, simplify: true) + render_success(data: { invoice: serializer.to_json }) + end + + api :post, '/repp/v1/invoices/add_credit' + desc 'Generate add credit invoice' + def add_credit + deposit = Deposit.new(invoice_params.merge(registrar: current_user.registrar)) + invoice = deposit.issue_prepayment_invoice + if invoice + serializer = Serializers::Repp::Invoice.new(invoice, simplify: true) + render_success(data: { invoice: serializer.to_json }) + else + handle_errors(deposit) + end + end + + private + + def index_params + params.permit(:id, :limit, :offset, :details, :q, :simple, + :page, :per_page, + q: %i[number_str_matches due_date_gteq due_date_lteq + account_activity_created_at_gteq + account_activity_created_at_lteq + account_activity_id_not_null + account_activity_id_null + cancelled_at_not_null + number_gteq number_lteq + total_gteq total_lteq s] + [s: []]) + end + + def search_params + index_params.fetch(:q, {}) + end + + def invoice_params + params.require(:invoice).permit(:id, :recipient, :amount, :description) + end + + def limit + index_params[:limit] || 200 + end + + def offset + index_params[:offset] || 0 + end + + def serialized_invoices(invoices) + return invoices.pluck(:number) unless index_params[:details] == 'true' + + simple = index_params[:simple] == 'true' || false + invoices.map { |i| Serializers::Repp::Invoice.new(i, simplify: simple).to_json } + end + end + end +end \ No newline at end of file diff --git a/app/controllers/repp/v1/registrar/auth_controller.rb b/app/controllers/repp/v1/registrar/auth_controller.rb new file mode 100644 index 000000000..8fba9eefb --- /dev/null +++ b/app/controllers/repp/v1/registrar/auth_controller.rb @@ -0,0 +1,49 @@ +module Repp + module V1 + module Registrar + class AuthController < BaseController + skip_before_action :authenticate_user, only: :tara_callback + skip_before_action :check_ip_restriction, only: :tara_callback + + api :GET, 'repp/v1/registrar/auth' + desc 'check user auth info and return data' + def index + registrar = current_user.registrar + render_success(data: auth_values_to_data(registrar: registrar)) + end + + api :POST, 'repp/v1/registrar/auth/tara_callback' + desc 'check tara callback omniauth user info and return token' + def tara_callback + user = ApiUser.from_omniauth(auth_params) + handle_non_epp_errors(user, I18n.t(:no_such_user)) and return unless user && user&.active + + token = Base64.urlsafe_encode64("#{user.username}:#{user.plain_text_password}") + render_success(data: { token: token, username: user.username }) + end + + api :put, '/repp/v1/registrar/auth/switch_user/:new_user_id' + desc 'Switch session to another api user' + def switch_user + new_user = ApiUser.find(auth_params[:new_user_id]) + unless current_user.linked_with?(new_user) + handle_non_epp_errors(new_user, 'Cannot switch to unlinked user') + return + end + + @current_user = new_user + data = auth_values_to_data(registrar: current_user.registrar) + message = I18n.t('registrar.current_user.switch.switched', new_user: new_user) + token = Base64.urlsafe_encode64("#{new_user.username}:#{new_user.plain_text_password}") + render_success(data: { token: token, registrar: data }, message: message) + end + + private + + def auth_params + params.require(:auth).permit(:uid, :new_user_id) + end + end + end + end +end \ No newline at end of file diff --git a/app/controllers/repp/v1/registrar/nameservers_controller.rb b/app/controllers/repp/v1/registrar/nameservers_controller.rb index 174193350..fbd4c03ec 100644 --- a/app/controllers/repp/v1/registrar/nameservers_controller.rb +++ b/app/controllers/repp/v1/registrar/nameservers_controller.rb @@ -19,13 +19,16 @@ module Repp end def update # rubocop:disable Metrics/MethodLength + authorize! :manage, :repp affected, errored = if hostname.present? - current_user.registrar.replace_nameservers(hostname, - hostname_params[:data][:attributes], - domains: domains_from_params) + current_user.registrar + .replace_nameservers(hostname, + hostname_params[:attributes], + domains: domains_from_params) else - current_user.registrar.add_nameservers(hostname_params[:data][:attributes], - domains: domains_from_params) + current_user.registrar + .add_nameservers(hostname_params[:attributes], + domains: domains_from_params) end render_success(data: data_format_for_success(affected, errored)) @@ -36,34 +39,32 @@ module Repp private def domains_from_params - return [] unless params[:data][:domains] + return [] unless hostname_params[:domains] - params[:data][:domains].map(&:downcase) + hostname_params[:domains].map(&:downcase) end def data_format_for_success(affected_domains, errored_domains) { type: 'nameserver', - id: params[:data][:attributes][:hostname], - attributes: params[:data][:attributes], + id: hostname_params[:attributes][:hostname], + attributes: hostname_params[:attributes], affected_domains: affected_domains, skipped_domains: errored_domains, } end def hostname_params - params.require(:data).require(%i[type]) - params.require(:data).require(:attributes).require([:hostname]) - - params.permit(data: [ - :type, :id, - { domains: [], - attributes: [:hostname, { ipv4: [], ipv6: [] }] } - ]) + params.require(:data).permit(:type, :id, nameserver: [], domains: [], + attributes: [:hostname, { ipv4: [], ipv6: [] }]) + .tap do |data| + data.require(:type) + data.require(:attributes).require([:hostname]) + end end def hostname - hostname_params[:data][:id] || nil + hostname_params[:id] || nil end def verify_nameserver_existance diff --git a/app/controllers/repp/v1/registrar/notifications_controller.rb b/app/controllers/repp/v1/registrar/notifications_controller.rb index 815ee85b9..6b1d342cc 100644 --- a/app/controllers/repp/v1/registrar/notifications_controller.rb +++ b/app/controllers/repp/v1/registrar/notifications_controller.rb @@ -2,7 +2,7 @@ module Repp module V1 module Registrar class NotificationsController < BaseController - before_action :set_notification, only: [:update] + before_action :set_notification, only: %i[update show] api :GET, '/repp/v1/registrar/notifications' desc 'Get the latest unread poll message' @@ -39,7 +39,6 @@ module Repp api :GET, '/repp/v1/registrar/notifications/:notification_id' desc 'Get a specific poll message' def show - @notification = current_user.registrar.notifications.find(params[:id]) data = @notification.as_json(only: %i[id text attached_obj_id attached_obj_type read]) render_success(data: data) @@ -51,6 +50,7 @@ module Repp param :read, [true, 'true'], required: true, desc: 'Set as true to mark as read' end def update + authorize! :manage, :poll # rubocop:disable Style/AndOr handle_errors(@notification) and return unless @notification.mark_as_read # rubocop:enable Style/AndOr diff --git a/app/controllers/repp/v1/registrar/summary_controller.rb b/app/controllers/repp/v1/registrar/summary_controller.rb new file mode 100644 index 000000000..15f7d0164 --- /dev/null +++ b/app/controllers/repp/v1/registrar/summary_controller.rb @@ -0,0 +1,111 @@ +module Repp + module V1 + module Registrar + class SummaryController < BaseController + api :GET, 'repp/v1/registrar/summary' + desc 'check user summary info and return data' + + def index + user = current_user + registrar = user.registrar + if can?(:manage, :poll) + user_notifications = user.unread_notifications + notification = user_notifications.order('created_at DESC').take + notifications_count = user_notifications.count + if notification&.attached_obj_type && notification&.attached_obj_id + begin + object = object_by_type(notification.attached_obj_type) + .find(notification.attached_obj_id) + rescue => e + # the data model might be inconsistent; or ... + # this could happen if the registrar does not dequeue messages, and then the domain was deleted + # SELECT messages.id, domains.name, messages.body FROM messages LEFT OUTER + # JOIN domains ON attached_obj_id::INTEGER = domains.id + # WHERE attached_obj_type = 'Epp::Domain' AND name IS NULL; + message = 'orphan message, domain deleted, registrar should dequeue: ' + Rails.logger.error message + e.to_s + end + end + end + + data = serialize_data(registrar: registrar, + notification: notification, + notifications_count: notifications_count, + object: object) + + render_success(data: data) + end + + private + + def object_by_type(object_type) + Object.const_get(object_type) + rescue NameError + Object.const_get("Version::#{object_type}") + end + + # rubocop:disable Metrics/MethodLength + def serialize_data(registrar:, notification:, notifications_count:, object: nil) + data = current_user.as_json(only: %i[id username]) + data[:registrar_name] = registrar.name + data[:registrar_reg_no] = registrar.reg_no + data[:last_login_date] = last_login_date + data[:domains] = registrar.domains.count if can? :view, Depp::Domain + data[:contacts] = registrar.contacts.count if can? :view, Depp::Contact + data[:phone] = registrar.phone + data[:email] = registrar.email + data[:billing_email] = registrar.billing_email + data[:billing_address] = registrar.address + data[:notification] = serialized_notification(notification, object) + data[:notifications_count] = notifications_count + data + end + # rubocop:enable Metrics/MethodLength + + def last_login_date + q = ApiLog::ReppLog.ransack({ request_path_eq: '/repp/v1/registrar/auth', + response_code_eq: '200', + api_user_name_cont: current_user.username, + request_method_eq: 'GET' }) + q.sorts = 'id desc' + q.result.offset(1).first&.created_at + end + + def serialized_notification(notification, object) + return unless notification + + notification.created_at = notification.created_at.utc.xmlschema + obj_data = serialized_object(object, notification.attached_obj_type) + notification.as_json(only: %i[id text created_at attached_obj_id attached_obj_type]) + .merge({ attached_obj_data: obj_data }) + end + + def serialized_object(object, obj_type) + return unless object + + case obj_type + when 'DomainTransfer' + { + name: object.domain_name, + trStatus: object.status, + reID: object.new_registrar.code, + reDate: object.transfer_requested_at.try(:iso8601), + acID: object.old_registrar.code, + acDate: object.transferred_at.try(:iso8601) || object.wait_until.try(:iso8601), + exDate: object.domain_valid_to.iso8601, + } + when 'ContactUpdateAction' + { + contacts: object.to_non_available_contact_codes, + operation: object.operation, + opDate: object.created_at.utc.xmlschema, + svTrid: object.id, + who: object.user.username, + reason: 'Auto-update according to official data', + } + end + end + end + end + end +end \ No newline at end of file diff --git a/app/interactions/actions/domain_delete.rb b/app/interactions/actions/domain_delete.rb index 7790c25a9..750f0abac 100644 --- a/app/interactions/actions/domain_delete.rb +++ b/app/interactions/actions/domain_delete.rb @@ -32,7 +32,7 @@ module Actions def verify? return false unless Setting.request_confirmation_on_domain_deletion_enabled - return false if params[:delete][:verified] == true + return false if true?(params[:delete][:verified]) true end @@ -51,5 +51,9 @@ module Actions end true end + + def true?(obj) + obj.to_s.downcase == 'true' + end end end diff --git a/app/interactions/actions/domain_update.rb b/app/interactions/actions/domain_update.rb index a86b038ff..377f90e40 100644 --- a/app/interactions/actions/domain_update.rb +++ b/app/interactions/actions/domain_update.rb @@ -14,6 +14,7 @@ module Actions assign_new_registrant if params[:registrant] assign_relational_modifications assign_requested_statuses + ::Actions::BaseAction.maybe_attach_legal_doc(domain, params[:legal_document]) commit @@ -240,7 +241,7 @@ module Actions def verify_registrant_change? return validate_dispute_case if params[:reserved_pw] - return false if !@changes_registrant || params[:registrant][:verified] == true + return false if !@changes_registrant || true?(params[:registrant][:verified]) return true unless domain.disputed? domain.add_epp_error('2304', nil, nil, 'Required parameter missing; reservedpw element ' \ @@ -282,5 +283,9 @@ module Actions false end + + def true?(obj) + obj.to_s.downcase == 'true' + end end end diff --git a/app/interactions/actions/invoice_cancel.rb b/app/interactions/actions/invoice_cancel.rb new file mode 100644 index 000000000..2f0a77894 --- /dev/null +++ b/app/interactions/actions/invoice_cancel.rb @@ -0,0 +1,15 @@ +module Actions + class InvoiceCancel + attr_reader :invoice + + def initialize(invoice) + @invoice = invoice + end + + def call + return false unless @invoice.can_be_cancelled? + + @invoice.update(cancelled_at: Time.zone.now) + end + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index bc2caa6ba..31543a586 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -34,9 +34,11 @@ class Ability if @user.registrar.api_ip_white?(@ip) can :manage, Depp::Contact can :manage, :xml_console - can :manage, Depp::Domain + can :manage, Depp::Domain end + can :manage, Account + # Poll can :manage, :poll @@ -65,12 +67,13 @@ class Ability can(:update, Epp::Contact) { |c, pw| c.registrar_id == @user.registrar_id || c.auth_info == pw } can(:delete, Epp::Contact) { |c, pw| c.registrar_id == @user.registrar_id || c.auth_info == pw } can(:renew, Epp::Contact) - can(:transfer, Epp::Contact) + can(:transfer, Epp::Contact) can(:view_password, Epp::Contact) { |c, pw| c.registrar_id == @user.registrar_id || c.auth_info == pw } end def billing # Registrar/api_user dynamic role can(:manage, Invoice) { |i| i.buyer_id == @user.registrar_id } + can :manage, Account can :manage, :deposit can :read, AccountActivity can :manage, :balance_auto_reload diff --git a/app/models/action.rb b/app/models/action.rb index 8a822f867..03c8e9fe8 100644 --- a/app/models/action.rb +++ b/app/models/action.rb @@ -28,14 +28,20 @@ class Action < ApplicationRecord end def to_non_available_contact_codes - return [] unless bulk_action? + return [serialized_contact(contact)] unless bulk_action? subactions.map do |a| - { - code: a.contact.code, - avail: 0, - reason: 'in use', - } + serialized_contact(a.contact) end end + + private + + def serialized_contact(contact) + { + code: contact.code, + avail: 0, + reason: 'in use', + } + end end diff --git a/app/models/admin_domain_contact.rb b/app/models/admin_domain_contact.rb index 7ccf3efcb..9003afb0f 100644 --- a/app/models/admin_domain_contact.rb +++ b/app/models/admin_domain_contact.rb @@ -6,7 +6,7 @@ class AdminDomainContact < DomainContact skipped_domains = [] admin_contacts = where(contact: current_contact) - admin_contacts.each do |admin_contact| + admin_contacts.includes(:domain).each do |admin_contact| if admin_contact.domain.bulk_update_prohibited? skipped_domains << admin_contact.domain.name next diff --git a/app/models/api_user.rb b/app/models/api_user.rb index 8ae131a6e..d357a2e75 100644 --- a/app/models/api_user.rb +++ b/app/models/api_user.rb @@ -30,11 +30,11 @@ class ApiUser < User alias_attribute :login, :username - SUPER = 'super' - EPP = 'epp' - BILLING = 'billing' + SUPER = 'super'.freeze + EPP = 'epp'.freeze + BILLING = 'billing'.freeze - ROLES = %w(super epp billing) # should not match to admin roles + ROLES = %w[super epp billing].freeze # should not match to admin roles def ability @ability ||= Ability.new(self) @@ -72,8 +72,8 @@ class ApiUser < User def linked_users self.class.where(identity_code: identity_code) - .where("identity_code IS NOT NULL AND identity_code != ''") - .where.not(id: id) + .where("identity_code IS NOT NULL AND identity_code != ''") + .where.not(id: id) end def linked_with?(another_api_user) diff --git a/app/models/balance_auto_reload_types/threshold.rb b/app/models/balance_auto_reload_types/threshold.rb index d55cb977a..8bb494ae5 100644 --- a/app/models/balance_auto_reload_types/threshold.rb +++ b/app/models/balance_auto_reload_types/threshold.rb @@ -1,6 +1,7 @@ module BalanceAutoReloadTypes class Threshold include ActiveModel::Model + include ActiveModel::Validations attr_accessor :amount, :threshold @@ -11,8 +12,9 @@ module BalanceAutoReloadTypes Setting.minimum_deposit end - def as_json(options) + def as_json(options = nil) { name: name }.merge(super) + .except('errors', 'validation_context') end private diff --git a/app/models/bulk_action.rb b/app/models/bulk_action.rb deleted file mode 100644 index 9c98ee2db..000000000 --- a/app/models/bulk_action.rb +++ /dev/null @@ -1 +0,0 @@ -class BulkAction < Action; end diff --git a/app/models/concerns/invoice/cancellable.rb b/app/models/concerns/invoice/cancellable.rb index 8c9e142a8..9b1c6435b 100644 --- a/app/models/concerns/invoice/cancellable.rb +++ b/app/models/concerns/invoice/cancellable.rb @@ -5,12 +5,22 @@ module Invoice::Cancellable scope :non_cancelled, -> { where(cancelled_at: nil) } end + def can_be_cancelled? + unless cancellable? + errors.add(:base, :invoice_status_prohibits_operation) + return false + end + + true + end + def cancellable? unpaid? && not_cancelled? end def cancel raise 'Invoice cannot be cancelled' unless cancellable? + update!(cancelled_at: Time.zone.now) end diff --git a/app/models/concerns/invoice/payable.rb b/app/models/concerns/invoice/payable.rb index 6e2cc19b4..855ea8f41 100644 --- a/app/models/concerns/invoice/payable.rb +++ b/app/models/concerns/invoice/payable.rb @@ -15,6 +15,8 @@ module Invoice::Payable end def receipt_date + return unless paid? + account_activity.created_at.to_date end diff --git a/app/models/contact.rb b/app/models/contact.rb index 2be404997..58c25f777 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -80,41 +80,41 @@ class Contact < ApplicationRecord self.ignored_columns = %w[legacy_id legacy_history_id] - ORG = 'org' - PRIV = 'priv' + ORG = 'org'.freeze + PRIV = 'priv'.freeze # For foreign private persons who has no national identification number BIRTHDAY = 'birthday'.freeze # From old registry software ("Fred"). No new contact can be created with this status - PASSPORT = 'passport' + PASSPORT = 'passport'.freeze # # STATUSES # # Requests to delete the object MUST be rejected. - CLIENT_DELETE_PROHIBITED = 'clientDeleteProhibited' - SERVER_DELETE_PROHIBITED = 'serverDeleteProhibited' + CLIENT_DELETE_PROHIBITED = 'clientDeleteProhibited'.freeze + SERVER_DELETE_PROHIBITED = 'serverDeleteProhibited'.freeze # Requests to transfer the object MUST be rejected. - CLIENT_TRANSFER_PROHIBITED = 'clientTransferProhibited' - SERVER_TRANSFER_PROHIBITED = 'serverTransferProhibited' + CLIENT_TRANSFER_PROHIBITED = 'clientTransferProhibited'.freeze + SERVER_TRANSFER_PROHIBITED = 'serverTransferProhibited'.freeze # The contact object has at least one active association with # another object, such as a domain object. Servers SHOULD provide # services to determine existing object associations. # "linked" status MAY be combined with any status. - LINKED = 'linked' + LINKED = 'linked'.freeze # This is the normal status value for an object that has no pending # operations or prohibitions. This value is set and removed by the # server as other status values are added or removed. # "ok" status MAY only be combined with "linked" status. - OK = 'ok' + OK = 'ok'.freeze # Requests to update the object (other than to remove this status) MUST be rejected. - CLIENT_UPDATE_PROHIBITED = 'clientUpdateProhibited' - SERVER_UPDATE_PROHIBITED = 'serverUpdateProhibited' + CLIENT_UPDATE_PROHIBITED = 'clientUpdateProhibited'.freeze + SERVER_UPDATE_PROHIBITED = 'serverUpdateProhibited'.freeze # A transform command has been processed for the object, but the # action has not been completed by the server. Server operators can @@ -129,16 +129,16 @@ class Contact < ApplicationRecord # the status of the object has changed. # The pendingCreate, pendingDelete, pendingTransfer, and pendingUpdate # status values MUST NOT be combined with each other. - PENDING_CREATE = 'pendingCreate' + PENDING_CREATE = 'pendingCreate'.freeze # "pendingTransfer" status MUST NOT be combined with either # "clientTransferProhibited" or "serverTransferProhibited" status. - PENDING_TRANSFER = 'pendingTransfer' + PENDING_TRANSFER = 'pendingTransfer'.freeze # "pendingUpdate" status MUST NOT be combined with either # "clientUpdateProhibited" or "serverUpdateProhibited" status. - PENDING_UPDATE = 'pendingUpdate' + PENDING_UPDATE = 'pendingUpdate'.freeze # "pendingDelete" MUST NOT be combined with either # "clientDeleteProhibited" or "serverDeleteProhibited" status. - PENDING_DELETE = 'pendingDelete' + PENDING_DELETE = 'pendingDelete'.freeze STATUSES = [ CLIENT_DELETE_PROHIBITED, SERVER_DELETE_PROHIBITED, @@ -146,18 +146,18 @@ class Contact < ApplicationRecord SERVER_TRANSFER_PROHIBITED, CLIENT_UPDATE_PROHIBITED, SERVER_UPDATE_PROHIBITED, OK, PENDING_CREATE, PENDING_DELETE, PENDING_TRANSFER, PENDING_UPDATE, LINKED - ] + ].freeze CLIENT_STATUSES = [ CLIENT_DELETE_PROHIBITED, CLIENT_TRANSFER_PROHIBITED, CLIENT_UPDATE_PROHIBITED - ] + ].freeze SERVER_STATUSES = [ SERVER_UPDATE_PROHIBITED, SERVER_DELETE_PROHIBITED, - SERVER_TRANSFER_PROHIBITED - ] + SERVER_TRANSFER_PROHIBITED, + ].freeze # # END OF STATUSES # @@ -355,7 +355,7 @@ class Contact < ApplicationRecord @desc[dom.name][:roles] << :registrant end - domain_contacts.each do |dc| + domain_contacts.includes(:domain).each do |dc| @desc[dc.domain.name] ||= { id: dc.domain.uuid, roles: [] } @desc[dc.domain.name][:roles] << dc.name.downcase.to_sym @desc[dc.domain.name] = @desc[dc.domain.name].compact @@ -383,6 +383,10 @@ class Contact < ApplicationRecord "#{code} #{name}" end + def name_disclosed_by_registrar(reg_id) + registrar_id == reg_id ? name : 'N/A' + end + def strip_email self.email = email.to_s.strip end @@ -405,7 +409,7 @@ class Contact < ApplicationRecord # using small rails hack to generate outer join domains = if sorts.first == 'registrar_name'.freeze - domains.includes(:registrar).where.not(registrars: { id: nil }) + domains.where.not(registrars: { id: nil }) .order("registrars.name #{order} NULLS LAST") else domains.order("#{sort} #{order} NULLS LAST") @@ -422,7 +426,6 @@ class Contact < ApplicationRecord end domains.each { |d| d.roles = domain_c[d.id].uniq } - domains end @@ -438,18 +441,28 @@ class Contact < ApplicationRecord end end - def qualified_domain_ids(domain_filter) - registrant_ids = registrant_domains.pluck(:id) - return registrant_ids if domain_filter == 'Registrant' + def qualified_domain_ids(filters) + rant_domains = registrant_domains.map { |d| { id: d.id, type: ['Registrant'] } } + contact_domains = domain_contacts.map { |dc| { id: dc.domain_id, type: [dc.type] } } + grouped_domains = group_by_id_and_type(rant_domains + contact_domains) + return grouped_domains.keys if filters.nil? || filters == '' - if %w[AdminDomainContact TechDomainContact].include? domain_filter - DomainContact.select('domain_id').where(contact_id: id, type: domain_filter) - else - (DomainContact.select('domain_id').where(contact_id: id).pluck(:domain_id) + - registrant_ids).uniq - end + # use domain_filters.sort == v.sort if should be exact match + grouped_domains.reject { |_, v| ([].push(filters).flatten & v).empty? }.keys end + # def qualified_domain_ids(domain_filter) + # registrant_ids = registrant_domains.pluck(:id) + # return registrant_ids if domain_filter == 'Registrant' + + # if %w[AdminDomainContact TechDomainContact].include? domain_filter + # DomainContact.where(contact_id: id, type: domain_filter).pluck(:domain_id) + # else + # (DomainContact.where(contact_id: id).pluck(:domain_id) + + # registrant_ids).uniq + # end + # end + def update_prohibited? (statuses & [ CLIENT_UPDATE_PROHIBITED, @@ -459,7 +472,7 @@ class Contact < ApplicationRecord PENDING_CREATE, PENDING_TRANSFER, PENDING_UPDATE, - PENDING_DELETE + PENDING_DELETE, ]).present? end @@ -590,4 +603,14 @@ class Contact < ApplicationRecord def self.csv_header ['Name', 'ID', 'Ident', 'E-mail', 'Created at', 'Registrar', 'Phone'] end + + private + + def group_by_id_and_type(domains_hash_array) + domains_hash_array.group_by { |d| d[:id] } + .transform_values do |v| + v.each.with_object(:type) + .map(&:[]).flatten + end + end end diff --git a/app/models/contact_update_action.rb b/app/models/contact_update_action.rb new file mode 100644 index 000000000..4e7444948 --- /dev/null +++ b/app/models/contact_update_action.rb @@ -0,0 +1 @@ +class ContactUpdateAction < Action; end diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 5943f1540..711d59d72 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -33,6 +33,7 @@ class Deposit def issue_prepayment_invoice return unless valid? + registrar.issue_prepayment_invoice(amount, description) end end diff --git a/app/models/depp/user.rb b/app/models/depp/user.rb index 60c6f6c3d..36fc48fdc 100644 --- a/app/models/depp/user.rb +++ b/app/models/depp/user.rb @@ -31,8 +31,8 @@ module Depp def request(xml) Nokogiri::XML(server.request(xml)).remove_namespaces! - rescue EppErrorResponse => e - Nokogiri::XML(e.response_xml.to_s).remove_namespaces! + rescue EppErrorResponse => e + Nokogiri::XML(e.response_xml.to_s).remove_namespaces! end private diff --git a/app/models/domain_transfer.rb b/app/models/domain_transfer.rb index 02ab2bc88..ff9e55276 100644 --- a/app/models/domain_transfer.rb +++ b/app/models/domain_transfer.rb @@ -4,10 +4,10 @@ class DomainTransfer < ApplicationRecord belongs_to :old_registrar, class_name: 'Registrar' belongs_to :new_registrar, class_name: 'Registrar' - PENDING = 'pending' - CLIENT_APPROVED = 'clientApproved' - CLIENT_REJECTED = 'clientRejected' - SERVER_APPROVED = 'serverApproved' + PENDING = 'pending'.freeze + CLIENT_APPROVED = 'clientApproved'.freeze + CLIENT_REJECTED = 'clientRejected'.freeze + SERVER_APPROVED = 'serverApproved'.freeze before_create :set_wait_until diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 66d3faf86..b7e60abfb 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -180,6 +180,13 @@ class Invoice < ApplicationRecord private + ransacker :number_str do + Arel.sql( + "regexp_replace( + to_char(\"#{table_name}\".\"number\", '999999999999'), ' ', '', 'g')" + ) + end + def receipt_date_status if paid? receipt_date diff --git a/app/models/notification.rb b/app/models/notification.rb index c9af66c56..8cb4335a2 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -1,5 +1,6 @@ class Notification < ApplicationRecord include Versions # version/notification_version.rb + include EppErrors belongs_to :registrar belongs_to :action, optional: true diff --git a/app/models/registrar.rb b/app/models/registrar.rb index caefaddd6..1eba314dc 100644 --- a/app/models/registrar.rb +++ b/app/models/registrar.rb @@ -97,7 +97,7 @@ class Registrar < ApplicationRecord description: 'prepayment', unit: 'piece', quantity: 1, - price: amount + price: amount, } ] ) @@ -233,13 +233,9 @@ class Registrar < ApplicationRecord def notify(action) text = I18n.t("notifications.texts.#{action.notification_key}", contact: action.contact&.code, count: action.subactions&.count) - if action.bulk_action? - notifications.create!(text: text, action_id: action.id, - attached_obj_type: 'BulkAction', - attached_obj_id: action.id) - else - notifications.create!(text: text) - end + notifications.create!(text: text, action_id: action.id, + attached_obj_type: 'ContactUpdateAction', + attached_obj_id: action.id) end def e_invoice_iban diff --git a/app/models/tech_domain_contact.rb b/app/models/tech_domain_contact.rb index eff815350..30db6dec7 100644 --- a/app/models/tech_domain_contact.rb +++ b/app/models/tech_domain_contact.rb @@ -5,7 +5,7 @@ class TechDomainContact < DomainContact skipped_domains = [] tech_contacts = where(contact: current_contact) - tech_contacts.each do |tech_contact| + tech_contacts.includes(:domain).each do |tech_contact| if irreplaceable?(tech_contact) skipped_domains << tech_contact.domain.name next diff --git a/app/models/user.rb b/app/models/user.rb index cca07ca14..3bb8318c3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,6 @@ class User < ApplicationRecord identity_code = uid.slice(2..-1) # country_code = uid.slice(0..1) - find_by(identity_code: identity_code) + find_by(identity_code: identity_code, active: true) end end diff --git a/app/presenters/registrar/domain_list_csv_presenter.rb b/app/presenters/registrar/domain_list_csv_presenter.rb index e38f3f54e..a216d9561 100644 --- a/app/presenters/registrar/domain_list_csv_presenter.rb +++ b/app/presenters/registrar/domain_list_csv_presenter.rb @@ -17,13 +17,13 @@ class Registrar::DomainListCsvPresenter private def header - columns = %w( + columns = %w[ domain_name transfer_code registrant_name registrant_code expire_time - ) + ] columns.map! { |column| view.t("registrar.domains.index.csv.#{column}") } @@ -37,7 +37,6 @@ class Registrar::DomainListCsvPresenter row[2] = domain.registrant.name row[3] = domain.registrant.code row[4] = domain.expire_date - row CSV::Row.new([], row) end diff --git a/app/views/epp/poll/poll_req.xml.builder b/app/views/epp/poll/poll_req.xml.builder index 0a916e6ad..373b8194b 100644 --- a/app/views/epp/poll/poll_req.xml.builder +++ b/app/views/epp/poll/poll_req.xml.builder @@ -15,7 +15,7 @@ xml.epp_head do xml.resData do xml << render('epp/domains/partials/transfer', builder: xml, dt: @object) end - when 'BulkAction' + when 'ContactUpdateAction' xml.resData do xml << render( 'epp/contacts/partials/check', diff --git a/app/views/registrar/domains/_search_form.html.erb b/app/views/registrar/domains/_search_form.html.erb index e9e5b5e1a..584b15ba1 100644 --- a/app/views/registrar/domains/_search_form.html.erb +++ b/app/views/registrar/domains/_search_form.html.erb @@ -44,7 +44,7 @@
- <%= f.label :valid_to_from, for: nil %> + <%= f.label :valid_to_gteq, for: nil %> <%= f.search_field :valid_to_gteq, value: search_params[:valid_to_gteq], class: 'form-control js-datepicker', placeholder: t(:valid_to_from) %> @@ -53,7 +53,7 @@
- <%= f.label :valid_to_until, for: nil %> + <%= f.label :valid_to_lteq, for: nil %> <%= f.search_field :valid_to_lteq, value: search_params[:valid_to_lteq], class: 'form-control js-datepicker', placeholder: t(:valid_to_until) %> diff --git a/app/views/registrar/polls/show.haml b/app/views/registrar/polls/show.haml index 4ff116b81..c97d7a5d5 100644 --- a/app/views/registrar/polls/show.haml +++ b/app/views/registrar/polls/show.haml @@ -27,10 +27,11 @@ = form_tag confirm_transfer_registrar_poll_path, class: 'js-transfer-form' do = hidden_field_tag 'domain[name]', @data.css('name').text - - @data.css('trnData').children.each do |x| - - next if x.blank? - %dt= t(x.name) - %dd= x.text + - @data.css('trnData').children.each do |x| + - next if x.blank? + %dt= t(x.name) + %dd= x.text + - else .row .col-sm-12 diff --git a/config/initializers/arel.rb b/config/initializers/arel.rb new file mode 100644 index 000000000..9db1facf3 --- /dev/null +++ b/config/initializers/arel.rb @@ -0,0 +1,25 @@ +require 'arel/nodes/binary' +require 'arel/predications' +require 'arel/visitors/postgresql' + +module Arel + class Nodes::ContainsArray < Arel::Nodes::Binary + def operator + :"@>" + end + end + + class Visitors::PostgreSQL + private + + def visit_Arel_Nodes_ContainsArray(o, collector) + infix_value o, collector, ' @> ' + end + end + + module Predications + def contains_array(other) + Nodes::ContainsArray.new self, Nodes.build_quoted(other, self) + end + end +end \ No newline at end of file diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index e3e0d644b..2690160ef 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -15,62 +15,67 @@ host = ENV['tara_host'] identifier = ENV['tara_identifier'] secret = ENV['tara_secret'] redirect_uri = ENV['tara_redirect_uri'] +authorization_endpoint = ENV['tara_authorization_endpoint'] +token_endpoint = ENV['tara_token_endpoint'] +jwks_uri = ENV['tara_jwks_uri'] +scope = ENV['tara_scope'] registrant_identifier = ENV['tara_rant_identifier'] registrant_secret = ENV['tara_rant_secret'] registrant_redirect_uri = ENV['tara_rant_redirect_uri'] Rails.application.config.middleware.use OmniAuth::Builder do - provider "tara", { - callback_path: '/registrar/open_id/callback', - name: 'tara', - scope: ['openid'], - state: Proc.new{ SecureRandom.hex(10) }, - client_signing_alg: :RS256, - client_jwk_signing_key: signing_keys, - send_scope_to_token_endpoint: false, - send_nonce: true, - issuer: issuer, + provider 'tara', { + callback_path: '/registrar/open_id/callback', + name: 'tara', + scope: scope, + # state: Proc.new{ SecureRandom.hex(10) }, + client_signing_alg: :RS256, + client_jwk_signing_key: signing_keys, + send_scope_to_token_endpoint: false, + send_nonce: true, + issuer: issuer, + discovery: true, - client_options: { - scheme: 'https', - host: host, + client_options: { + scheme: 'https', + host: host, - authorization_endpoint: '/oidc/authorize', - token_endpoint: '/oidc/token', - userinfo_endpoint: nil, # Not implemented - jwks_uri: '/oidc/jwks', + authorization_endpoint: authorization_endpoint, + token_endpoint: token_endpoint, + userinfo_endpoint: nil, # Not implemented + jwks_uri: jwks_uri, - # Registry - identifier: identifier, - secret: secret, - redirect_uri: redirect_uri, - }, + # Registry + identifier: identifier, + secret: secret, + redirect_uri: redirect_uri, + }, } - provider "tara", { - callback_path: '/registrant/open_id/callback', - name: 'rant_tara', - scope: ['openid'], - client_signing_alg: :RS256, - client_jwk_signing_key: signing_keys, - send_scope_to_token_endpoint: false, - send_nonce: true, - issuer: issuer, + provider 'tara', { + callback_path: '/registrant/open_id/callback', + name: 'rant_tara', + scope: ['openid'], + client_signing_alg: :RS256, + client_jwk_signing_key: signing_keys, + send_scope_to_token_endpoint: false, + send_nonce: true, + issuer: issuer, - client_options: { - scheme: 'https', - host: host, + client_options: { + scheme: 'https', + host: host, - authorization_endpoint: '/oidc/authorize', - token_endpoint: '/oidc/token', - userinfo_endpoint: nil, # Not implemented - jwks_uri: '/oidc/jwks', + authorization_endpoint: '/oidc/authorize', + token_endpoint: '/oidc/token', + userinfo_endpoint: nil, # Not implemented + jwks_uri: '/oidc/jwks', - # Registry - identifier: registrant_identifier, - secret: registrant_secret, - redirect_uri: registrant_redirect_uri, - }, + # Registry + identifier: registrant_identifier, + secret: registrant_secret, + redirect_uri: registrant_redirect_uri, + }, } end diff --git a/config/initializers/ransack.rb b/config/initializers/ransack.rb new file mode 100644 index 000000000..d26360672 --- /dev/null +++ b/config/initializers/ransack.rb @@ -0,0 +1,7 @@ +Ransack.configure do |config| + config.add_predicate 'contains_array', + arel_predicate: 'contains_array', + formatter: proc { |v| "{#{v}}" }, + validator: proc { |v| v.present? }, + type: :string +end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 9c396cbde..ec8953a84 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -101,6 +101,11 @@ en: attributes: value: taken: 'Status already exists on this domain' + + invoice: + attributes: + base: + invoice_status_prohibits_operation: 'Invoice status prohibits operation' user: attributes: diff --git a/config/routes.rb b/config/routes.rb index 8fd80f923..6f336f4d4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,12 +71,27 @@ Rails.application.routes.draw do resources :contacts do collection do get 'check/:id', to: 'contacts#check' + get 'search(/:id)', to: 'contacts#search' end end - resources :accounts do + resource :account, controller: :account, only: %i[index update] do collection do + get '/', to: 'account#index' get 'balance' + get 'details' + post 'update_auto_reload_balance' + get 'disable_auto_reload_balance' + end + end + resources :invoices, only: %i[index show] do + collection do + get ':id/download', to: 'invoices#download' + get ':id/cancel', to: 'invoices#cancel' + post 'add_credit' + end + member do + post 'send_to_recipient', to: 'invoices#send_to_recipient' end end resources :auctions, only: %i[index] @@ -98,6 +113,13 @@ Rails.application.routes.draw do put '/', to: 'nameservers#update' end end + resources :summary, only: %i[index] + resources :auth, only: %i[index] do + collection do + post '/tara_callback', to: 'auth#tara_callback' + put '/switch_user', to: 'auth#switch_user' + end + end end resources :domains, constraints: { id: /.*/ } do resources :nameservers, only: %i[index create destroy], constraints: { id: /.*/ }, controller: 'domains/nameservers' @@ -146,9 +168,9 @@ Rails.application.routes.draw do namespace :accreditation_center do # At the moment invoice_status endpoint returns only cancelled invoices. But in future logic of this enpoint can change. # And it will need to return invoices of different statuses. I decided to leave the name of the endpoint "invoice_status" - resources :invoice_status, only: [ :index ] - resource :domains, only: [ :show ], param: :name - resource :contacts, only: [ :show ], param: :id + resources :invoice_status, only: [:index] + resource :domains, only: [:show], param: :name + resource :contacts, only: [:show], param: :id # resource :auth, only: [ :index ] get 'auth', to: 'auth#index' end @@ -159,7 +181,7 @@ Rails.application.routes.draw do end match '*all', controller: 'cors', action: 'cors_preflight_check', via: [:options], - as: 'cors_preflight_check' + as: 'cors_preflight_check' end # REGISTRAR ROUTES diff --git a/lib/serializers/repp/contact.rb b/lib/serializers/repp/contact.rb index b5d03b5cd..c36fa3258 100644 --- a/lib/serializers/repp/contact.rb +++ b/lib/serializers/repp/contact.rb @@ -3,22 +3,32 @@ module Serializers class Contact attr_reader :contact - def initialize(contact, show_address:) + def initialize(contact, options = {}) @contact = contact - @show_address = show_address + @show_address = options[:show_address] + @domain_params = options[:domain_params] || nil + @simplify = options[:simplify] || false end def to_json(obj = contact) - json = { id: obj.code, name: obj.name, ident: ident, - email: obj.email, phone: obj.phone, - auth_info: obj.auth_info, statuses: obj.statuses, - disclosed_attributes: obj.disclosed_attributes } + return simple_object if @simplify + json = { id: obj.uuid, code: obj.code, name: obj.name, ident: ident, + email: obj.email, phone: obj.phone, created_at: obj.created_at, + auth_info: obj.auth_info, statuses: statuses, + disclosed_attributes: obj.disclosed_attributes, registrar: registrar } json[:address] = address if @show_address - + if @domain_params + json[:domains] = domains + json[:domains_count] = obj.qualified_domain_ids(@domain_params[:domain_filter]).size + end json end + def registrar + contact.registrar.as_json(only: %i[name website]) + end + def ident { code: contact.ident, @@ -31,6 +41,34 @@ module Serializers { street: contact.street, zip: contact.zip, city: contact.city, state: contact.state, country_code: contact.country_code } end + + def domains + contact.all_domains(page: @domain_params[:page], + per: @domain_params[:per_page], + params: @domain_params) + .map do |d| + { id: d.uuid, name: d.name, registrar: { name: d.registrar.name }, + valid_to: d.valid_to, roles: d.roles } + end + end + + def statuses + statuses_with_notes = contact.status_notes + contact.statuses.each do |status| + statuses_with_notes.merge!({ "#{status}": '' }) unless statuses_with_notes.key?(status) + end + statuses_with_notes + end + + private + + def simple_object + { + id: contact.uuid, + code: contact.code, + name: contact.name, + } + end end end end diff --git a/lib/serializers/repp/domain.rb b/lib/serializers/repp/domain.rb index d365859c1..07bb95e36 100644 --- a/lib/serializers/repp/domain.rb +++ b/lib/serializers/repp/domain.rb @@ -3,19 +3,25 @@ module Serializers class Domain attr_reader :domain - def initialize(domain, sponsored: true) + def initialize(domain, sponsored: true, simplify: false) @domain = domain @sponsored = sponsored + @simplify = simplify end # rubocop:disable Metrics/AbcSize def to_json(obj = domain) + return simple_object if @simplify + json = { - name: obj.name, registrant: obj.registrant.code, created_at: obj.created_at, - updated_at: obj.updated_at, expire_time: obj.expire_time, outzone_at: obj.outzone_at, - delete_date: obj.delete_date, force_delete_date: obj.force_delete_date, - contacts: contacts, nameservers: nameservers, dnssec_keys: dnssec_keys, - statuses: obj.status_notes, registrar: registrar + id: obj.uuid, name: obj.name, registrant: registrant, + created_at: obj.created_at, updated_at: obj.updated_at, + expire_time: obj.expire_time, + outzone_at: obj.outzone_at, delete_date: obj.delete_date, + force_delete_date: obj.force_delete_date, contacts: contacts, + nameservers: nameservers, dnssec_keys: dnssec_keys, + statuses: statuses, registrar: registrar, + dispute: Dispute.active.exists?(domain_name: obj.name) } json[:transfer_code] = obj.auth_info if @sponsored json @@ -23,22 +29,54 @@ module Serializers # rubocop:enable Metrics/AbcSize def contacts - domain.domain_contacts.map { |c| { code: c.contact.code, type: c.type } } - end - - def nameservers - domain.nameservers.map { |ns| { hostname: ns.hostname, ipv4: ns.ipv4, ipv6: ns.ipv6 } } - end - - def dnssec_keys - domain.dnskeys.map do |nssec| - { flags: nssec.flags, protocol: nssec.protocol, alg: nssec.alg, - public_key: nssec.public_key } + domain.domain_contacts.includes(:contact).map do |dc| + contact = dc.contact + { code: contact.code, type: dc.type, + name: contact.name_disclosed_by_registrar(domain.registrar_id) } end end + def nameservers + domain.nameservers.order(:created_at).as_json(only: %i[id hostname ipv4 ipv6]) + end + + def dnssec_keys + domain.dnskeys.order(:updated_at).as_json(only: %i[id flags protocol alg public_key]) + end + def registrar - { name: domain.registrar.name, website: domain.registrar.website } + domain.registrar.as_json(only: %i[name website]) + end + + def registrant + rant = domain.registrant + { + id: rant.uuid, + name: rant.name, + code: rant.code, + } + end + + def statuses + statuses_with_notes = domain.status_notes + domain.statuses.each do |status| + statuses_with_notes.merge!({ "#{status}": '' }) unless statuses_with_notes.key?(status) + end + statuses_with_notes + end + + private + + def simple_object + json = { + id: domain.uuid, + name: domain.name, + expire_time: domain.expire_time, + registrant: registrant, + statuses: statuses, + } + json[:transfer_code] = domain.auth_info if @sponsored + json end end end diff --git a/lib/serializers/repp/invoice.rb b/lib/serializers/repp/invoice.rb new file mode 100644 index 000000000..9bc7431b2 --- /dev/null +++ b/lib/serializers/repp/invoice.rb @@ -0,0 +1,85 @@ +module Serializers + module Repp + class Invoice + attr_reader :invoice + + def initialize(invoice, simplify: false) + @invoice = invoice + @simplify = simplify + end + + def to_json(obj = invoice) + return simple_object if @simplify + + { + id: obj.id, issue_date: obj.issue_date, cancelled_at: obj.cancelled_at, + paid: obj.paid?, payable: obj.payable?, cancellable: invoice.cancellable?, + receipt_date: obj.receipt_date, payment_link: obj.payment_link, + number: obj.number, subtotal: obj.subtotal, vat_amount: obj.vat_amount, + vat_rate: obj.vat_rate, total: obj.total, + description: obj.description, reference_no: obj.reference_no, + created_at: obj.created_at, updated_at: obj.updated_at, + due_date: obj.due_date, currency: obj.currency, + seller: seller, buyer: buyer, items: items, + recipient: obj.buyer.billing_email + } + end + + private + + def seller + { + name: invoice.seller_name, + reg_no: invoice.seller_reg_no, + iban: invoice.seller_iban, + bank: invoice.seller_bank, + swift: invoice.seller_swift, + vat_no: invoice.seller_vat_no, + address: invoice.seller_address, + country: invoice.seller_country.name, + phone: invoice.seller_phone, + url: invoice.seller_url, + email: invoice.seller_email, + contact_name: invoice.seller_contact_name, + } + end + + def buyer + { + name: invoice.buyer_name, + reg_no: invoice.buyer_reg_no, + address: invoice.buyer_address, + country: invoice.buyer_country.name, + phone: invoice.buyer_phone, + url: invoice.buyer_url, + email: invoice.buyer_email, + } + end + + def items + invoice.items.map do |item| + { description: item.description, unit: item.unit, + quantity: item.quantity, price: item.price, + sum_without_vat: item.item_sum_without_vat, + vat_amount: item.vat_amount, total: item.total } + end + end + + def simple_object + { + id: invoice.id, + number: invoice.number, + paid: invoice.paid?, + payable: invoice.payable?, + payment_link: invoice.payment_link, + receipt_date: invoice.receipt_date, + cancelled: invoice.cancelled?, + cancellable: invoice.cancellable?, + due_date: invoice.due_date, + total: invoice.total, + recipient: invoice.buyer.billing_email, + } + end + end + end +end diff --git a/test/integration/epp/poll_test.rb b/test/integration/epp/poll_test.rb index 29c24af26..7b114f7c0 100644 --- a/test/integration/epp/poll_test.rb +++ b/test/integration/epp/poll_test.rb @@ -56,7 +56,7 @@ class EppPollTest < EppTestCase bulk_action = actions(:contacts_update_bulk_action) @notification.update!(action: bulk_action, attached_obj_id: bulk_action.id, - attached_obj_type: 'BulkAction') + attached_obj_type: 'ContactUpdateAction') post epp_poll_path, params: { frame: request_req_xml }, headers: { 'HTTP_COOKIE' => 'session=api_bestnames' } diff --git a/test/integration/repp/v1/accounts/balance_test.rb b/test/integration/repp/v1/account/balance_test.rb similarity index 88% rename from test/integration/repp/v1/accounts/balance_test.rb rename to test/integration/repp/v1/account/balance_test.rb index 4b711bd05..a8416be0a 100644 --- a/test/integration/repp/v1/accounts/balance_test.rb +++ b/test/integration/repp/v1/account/balance_test.rb @@ -11,7 +11,7 @@ class ReppV1BalanceTest < ActionDispatch::IntegrationTest end def test_can_query_balance - get '/repp/v1/accounts/balance', headers: @auth_headers + get '/repp/v1/account/balance', headers: @auth_headers json = JSON.parse(response.body, symbolize_names: true) assert_response :ok @@ -28,7 +28,7 @@ class ReppV1BalanceTest < ActionDispatch::IntegrationTest started_from = "2010-07-05" end_to = DateTime.current.to_date.to_s(:db) - get "/repp/v1/accounts/balance?detailed=true", headers: @auth_headers + get "/repp/v1/account/balance?detailed=true", headers: @auth_headers json = JSON.parse(response.body, symbolize_names: true) assert_response :ok @@ -44,8 +44,8 @@ class ReppV1BalanceTest < ActionDispatch::IntegrationTest assert_equal @registrar.registrar.cash_account.account_activities.last.new_balance.to_s, entry[:balance] json[:data][:transactions].map do |trans| - assert trans[:created_at].to_date.to_s(:db) >= started_from - assert trans[:created_at].to_date.to_s(:db) >= end_to + assert trans[:created_at].to_date.to_s(:db) >= started_from + assert trans[:created_at].to_date.to_s(:db) >= end_to end end end diff --git a/test/integration/repp/v1/contacts/create_test.rb b/test/integration/repp/v1/contacts/create_test.rb index f30bc368f..af1ca0fbf 100644 --- a/test/integration/repp/v1/contacts/create_test.rb +++ b/test/integration/repp/v1/contacts/create_test.rb @@ -11,16 +11,16 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest def test_creates_new_contact request_body = { - "contact": { - "name": "Donald Trump", - "phone": "+372.51111112", - "email": "donald@trumptower.com", - "ident": { - "ident_type": "priv", - "ident_country_code": "EE", - "ident": "39708290069" - } - } + contact: { + name: 'Donald Trump', + phone: '+372.51111112', + email: 'donald@trumptower.com', + ident: { + ident_type: 'priv', + ident_country_code: 'EE', + ident: '39708290069', + }, + }, } post '/repp/v1/contacts', headers: @auth_headers, params: request_body @@ -30,7 +30,7 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest assert_equal 1000, json[:code] assert_equal 'Command completed successfully', json[:message] - contact = Contact.find_by(code: json[:data][:contact][:id]) + contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.present? assert_equal(request_body[:contact][:name], contact.name) @@ -42,21 +42,21 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest end def test_removes_postal_info_when_contact_created - request_body = { - "contact": { - "name": "Donald Trump", - "phone": "+372.51111111", - "email": "donald@trump.com", - "ident": { - "ident_type": "priv", - "ident_country_code": "EE", - "ident": "39708290069" + request_body = { + contact: { + name: 'Donald Trump', + phone: '+372.51111111', + email: 'donald@trump.com', + ident: { + ident_type: 'priv', + ident_country_code: 'EE', + ident: '39708290069', }, - "addr": { - "city": "Tallinn", - "street": "Wismari 13", - "zip": "12345", - "country_code": "EE" + addr: { + city: 'Tallinn', + street: 'Wismari 13', + zip: '12345', + country_code: 'EE', } } } @@ -68,7 +68,7 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest assert_equal 1100, json[:code] assert_equal 'Command completed successfully; Postal address data discarded', json[:message] - contact = Contact.find_by(code: json[:data][:contact][:id]) + contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.present? assert_nil contact.city @@ -126,21 +126,21 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest end def test_attaches_legaldoc_if_present - request_body = { - "contact": { - "name": "Donald Trump", - "phone": "+372.51111112", - "email": "donald@trumptower.com", - "ident": { - "ident_type": "priv", - "ident_country_code": "EE", - "ident": "39708290069" + request_body = { + contact: { + name: 'Donald Trump', + phone: '+372.51111112', + email: 'donald@trumptower.com', + ident: { + ident_type: 'priv', + ident_country_code: 'EE', + ident: '39708290069', + }, + legal_document: { + type: 'pdf', + body: ('test' * 2000).to_s, }, }, - "legal_document": { - "type": "pdf", - "body": "#{'test' * 2000}" - } } post '/repp/v1/contacts', headers: @auth_headers, params: request_body @@ -150,7 +150,7 @@ class ReppV1ContactsCreateTest < ActionDispatch::IntegrationTest assert_equal 1000, json[:code] assert_equal 'Command completed successfully', json[:message] - contact = Contact.find_by(code: json[:data][:contact][:id]) + contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.legal_documents.any? end end diff --git a/test/integration/repp/v1/contacts/list_test.rb b/test/integration/repp/v1/contacts/list_test.rb index 31c4baaf9..6cc3e4669 100644 --- a/test/integration/repp/v1/contacts/list_test.rb +++ b/test/integration/repp/v1/contacts/list_test.rb @@ -12,13 +12,13 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest def test_returns_registrar_contacts get repp_v1_contacts_path, headers: @auth_headers json = JSON.parse(response.body, symbolize_names: true) - + assert_response :ok - assert_equal @user.registrar.contacts.count, json[:total_number_of_records] - assert_equal @user.registrar.contacts.count, json[:contacts].length + assert_equal @user.registrar.contacts.count, json[:data][:count] + assert_equal @user.registrar.contacts.count, json[:data][:contacts].length - assert json[:contacts][0].is_a? String + assert json[:data][:contacts][0].is_a? String end @@ -28,10 +28,10 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest assert_response :ok - assert_equal @user.registrar.contacts.count, json[:total_number_of_records] - assert_equal @user.registrar.contacts.count, json[:contacts].length + assert_equal @user.registrar.contacts.count, json[:data][:count] + assert_equal @user.registrar.contacts.count, json[:data][:contacts].length - assert json[:contacts][0].is_a? Hash + assert json[:data][:contacts][0].is_a? Hash end def test_respects_limit @@ -40,7 +40,7 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest assert_response :ok - assert_equal 2, json[:contacts].length + assert_equal 2, json[:data][:contacts].length end def test_respects_offset @@ -50,6 +50,6 @@ class ReppV1ContactsListTest < ActionDispatch::IntegrationTest assert_response :ok - assert_equal (@user.registrar.contacts.count - offset), json[:contacts].length + assert_equal (@user.registrar.contacts.count - offset), json[:data][:contacts].length end end diff --git a/test/integration/repp/v1/contacts/show_test.rb b/test/integration/repp/v1/contacts/show_test.rb index 4a6f5b615..496935ab6 100644 --- a/test/integration/repp/v1/contacts/show_test.rb +++ b/test/integration/repp/v1/contacts/show_test.rb @@ -28,7 +28,7 @@ class ReppV1ContactsShowTest < ActionDispatch::IntegrationTest assert_equal 1000, json[:code] assert_equal 'Command completed successfully', json[:message] - assert_equal contact.code, json[:data][:id] + assert_equal contact.code, json[:data][:contact][:code] end def test_can_not_access_out_of_scope_contacts diff --git a/test/integration/repp/v1/contacts/update_test.rb b/test/integration/repp/v1/contacts/update_test.rb index cf27f98da..e75ce4188 100644 --- a/test/integration/repp/v1/contacts/update_test.rb +++ b/test/integration/repp/v1/contacts/update_test.rb @@ -24,14 +24,14 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest assert_equal 1000, json[:code] assert_equal 'Command completed successfully', json[:message] - contact = Contact.find_by(code: json[:data][:contact][:id]) + contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.present? assert_equal(request_body[:contact][:email], contact.email) end def test_removes_postal_info_when_updated - request_body = { + request_body = { "contact": { "addr": { "city": "Tallinn", @@ -49,7 +49,7 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest assert_equal 1100, json[:code] assert_equal 'Command completed successfully; Postal address data discarded', json[:message] - contact = Contact.find_by(code: json[:data][:contact][:id]) + contact = Contact.find_by(code: json[:data][:contact][:code]) assert contact.present? assert_nil contact.city @@ -81,14 +81,14 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest end def test_attaches_legaldoc_if_present - request_body = { - "contact": { - "email": "donaldtrump@yandex.ru" + request_body = { + contact: { + email: 'donaldtrump@yandex.ru', + legal_document: { + type: 'pdf', + body: ('test' * 2000).to_s, + }, }, - "legal_document": { - "type": "pdf", - "body": "#{'test' * 2000}" - } } put "/repp/v1/contacts/#{@contact.code}", headers: @auth_headers, params: request_body @@ -103,9 +103,11 @@ class ReppV1ContactsUpdateTest < ActionDispatch::IntegrationTest end def test_returns_error_if_ident_wrong_format - request_body = { - "contact": { - "ident": "123" + request_body = { + contact: { + ident: { + ident: '123', + } } } diff --git a/test/integration/repp/v1/domains/contact_replacement_test.rb b/test/integration/repp/v1/domains/contact_replacement_test.rb index 3cbd9eb8e..65ddb4c9d 100644 --- a/test/integration/repp/v1/domains/contact_replacement_test.rb +++ b/test/integration/repp/v1/domains/contact_replacement_test.rb @@ -15,7 +15,7 @@ class ReppV1DomainsContactReplacementTest < ActionDispatch::IntegrationTest payload = { "current_contact_id": replaceable_contact.code, - "new_contact_id": replacing_contact.code + "new_contact_id": replacing_contact.code, } patch '/repp/v1/domains/contacts', headers: @auth_headers, params: payload @@ -37,7 +37,7 @@ class ReppV1DomainsContactReplacementTest < ActionDispatch::IntegrationTest payload = { "current_contact_id": replaceable_contact.code, - "new_contact_id": replacing_contact.code + "new_contact_id": replacing_contact.code, } patch '/repp/v1/domains/contacts', headers: @auth_headers, params: payload @@ -51,7 +51,7 @@ class ReppV1DomainsContactReplacementTest < ActionDispatch::IntegrationTest def test_contact_codes_must_be_valid payload = { "current_contact_id": 'dfgsdfg', - "new_contact_id": 'vvv' + "new_contact_id": 'vvv', } patch '/repp/v1/domains/contacts', headers: @auth_headers, params: payload @@ -61,5 +61,4 @@ class ReppV1DomainsContactReplacementTest < ActionDispatch::IntegrationTest assert_equal 2303, json[:code] assert_equal 'Object does not exist', json[:message] end - end diff --git a/test/integration/repp/v1/domains/delete_test.rb b/test/integration/repp/v1/domains/delete_test.rb index 08b73e832..818815473 100644 --- a/test/integration/repp/v1/domains/delete_test.rb +++ b/test/integration/repp/v1/domains/delete_test.rb @@ -15,9 +15,11 @@ class ReppV1DomainsDeleteTest < ActionDispatch::IntegrationTest @auth_headers['Content-Type'] = 'application/json' payload = { - delete: { - verified: false - } + domain: { + delete: { + verified: false, + }, + }, } delete "/repp/v1/domains/#{@domain.name}", headers: @auth_headers, params: payload.to_json @@ -36,9 +38,11 @@ class ReppV1DomainsDeleteTest < ActionDispatch::IntegrationTest @auth_headers['Content-Type'] = 'application/json' payload = { - delete: { - verified: true - } + domain: { + delete: { + verified: true, + }, + }, } delete "/repp/v1/domains/#{@domain.name}", headers: @auth_headers, params: payload.to_json diff --git a/test/integration/repp/v1/domains/list_test.rb b/test/integration/repp/v1/domains/list_test.rb index 366ac4d26..645947b57 100644 --- a/test/integration/repp/v1/domains/list_test.rb +++ b/test/integration/repp/v1/domains/list_test.rb @@ -15,7 +15,7 @@ class ReppV1DomainsListTest < ActionDispatch::IntegrationTest assert_response :ok - assert_equal @user.registrar.domains.count, json[:data][:total_number_of_records] + assert_equal @user.registrar.domains.count, json[:data][:count] assert_equal @user.registrar.domains.count, json[:data][:domains].length assert json[:data][:domains][0].is_a? String @@ -27,7 +27,7 @@ class ReppV1DomainsListTest < ActionDispatch::IntegrationTest assert_response :ok - assert_equal @user.registrar.domains.count, json[:data][:total_number_of_records] + assert_equal @user.registrar.domains.count, json[:data][:count] assert_equal @user.registrar.domains.count, json[:data][:domains].length assert json[:data][:domains][0].is_a? Hash diff --git a/test/integration/repp/v1/domains/renews_test.rb b/test/integration/repp/v1/domains/renews_test.rb index 3949f49dd..2fc1b7590 100644 --- a/test/integration/repp/v1/domains/renews_test.rb +++ b/test/integration/repp/v1/domains/renews_test.rb @@ -18,7 +18,7 @@ class ReppV1DomainsRenewsTest < ActionDispatch::IntegrationTest :prepare_renewed_expire_time).and_call_through @auth_headers['Content-Type'] = 'application/json' - payload = { renew: { period: 1, period_unit: 'y', exp_date: original_valid_to } } + payload = { renews: { period: 1, period_unit: 'y', exp_date: original_valid_to } } post "/repp/v1/domains/#{@domain.name}/renew", headers: @auth_headers, params: payload.to_json json = JSON.parse(response.body, symbolize_names: true) @@ -36,7 +36,7 @@ class ReppV1DomainsRenewsTest < ActionDispatch::IntegrationTest travel_to Time.zone.parse('2010-07-05') @auth_headers['Content-Type'] = 'application/json' - payload = { renew: { period: 10, period_unit: 'y', exp_date: original_valid_to } } + payload = { renews: { period: 10, period_unit: 'y', exp_date: original_valid_to } } post "/repp/v1/domains/#{@domain.name}/renew", headers: @auth_headers, params: payload.to_json json = JSON.parse(response.body, symbolize_names: true) @@ -60,7 +60,7 @@ class ReppV1DomainsRenewsTest < ActionDispatch::IntegrationTest one_year.reload @auth_headers['Content-Type'] = 'application/json' - payload = { renew: { period: 1, period_unit: 'y', exp_date: original_valid_to } } + payload = { renews: { period: 1, period_unit: 'y', exp_date: original_valid_to } } post "/repp/v1/domains/#{@domain.name}/renew", headers: @auth_headers, params: payload.to_json json = JSON.parse(response.body, symbolize_names: true) diff --git a/test/integration/repp/v1/domains/update_test.rb b/test/integration/repp/v1/domains/update_test.rb index d924fe7a3..59fb00d34 100644 --- a/test/integration/repp/v1/domains/update_test.rb +++ b/test/integration/repp/v1/domains/update_test.rb @@ -16,8 +16,8 @@ class ReppV1DomainsUpdateTest < ActionDispatch::IntegrationTest payload = { domain: { - auth_code: new_auth_code - } + auth_code: new_auth_code, + }, } put "/repp/v1/domains/#{@domain.name}", headers: @auth_headers, params: payload.to_json @@ -40,9 +40,9 @@ class ReppV1DomainsUpdateTest < ActionDispatch::IntegrationTest payload = { domain: { registrant: { - code: new_registrant.code - } - } + code: new_registrant.code, + }, + }, } put "/repp/v1/domains/#{@domain.name}", headers: @auth_headers, params: payload.to_json @@ -67,13 +67,14 @@ class ReppV1DomainsUpdateTest < ActionDispatch::IntegrationTest domain: { registrant: { code: new_registrant.code, - verified: true - } - } + verified: true, + }, + }, } put "/repp/v1/domains/#{@domain.name}", headers: @auth_headers, params: payload.to_json @domain.reload + json = JSON.parse(response.body, symbolize_names: true) assert_response :ok assert_equal 1000, json[:code] diff --git a/test/models/registrant_user_test.rb b/test/models/registrant_user_test.rb index 81e57fa72..4059720c9 100644 --- a/test/models/registrant_user_test.rb +++ b/test/models/registrant_user_test.rb @@ -60,6 +60,8 @@ class RegistrantUserTest < ActiveSupport::TestCase end bulk_action = @user.actions.where(operation: :bulk_update).last + single_action = @user.actions.find_by(operation: :update, + contact_id: contacts(:identical_to_william).id) assert_equal 4, bulk_action.subactions.size @@ -67,14 +69,14 @@ class RegistrantUserTest < ActiveSupport::TestCase notification = r.notifications.unread.order('created_at DESC').take if r == registrars(:bestnames) assert_equal '4 contacts have been updated by registrant', notification.text - assert_equal 'BulkAction', notification.attached_obj_type + assert_equal 'ContactUpdateAction', notification.attached_obj_type assert_equal bulk_action.id, notification.attached_obj_id assert_equal bulk_action.id, notification.action_id else assert_equal 'Contact william-002 has been updated by registrant', notification.text - refute notification.action_id - refute notification.attached_obj_id - refute notification.attached_obj_type + assert_equal 'ContactUpdateAction', notification.attached_obj_type + assert_equal single_action.id, notification.attached_obj_id + assert_equal single_action.id, notification.action_id end end end