From 18ce8534206f9bfc28c5416fa6512235de793e6f Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 14 May 2020 14:33:38 +0300 Subject: [PATCH] Simple implementation of retained domains API endpoint This needed a new name, there are several classes of database object to be included in that endpoint. Currently, there is one list ordered by name, with each object containing status and ascii name for convenience. Can be converted to multiple fields (reserved and blocked separately). Also contains total count. Includes CORS preflight which seems to be a known problem for Rails in the past. --- .../repp/v1/retained_domains_controller.rb | 11 ++++ app/models/retained_domains.rb | 51 +++++++++++++++++++ config/routes.rb | 12 +++++ .../integration/repp/retained_domains_test.rb | 39 ++++++++++++++ 4 files changed, 113 insertions(+) create mode 100644 app/controllers/repp/v1/retained_domains_controller.rb create mode 100644 app/models/retained_domains.rb create mode 100644 test/integration/repp/retained_domains_test.rb diff --git a/app/controllers/repp/v1/retained_domains_controller.rb b/app/controllers/repp/v1/retained_domains_controller.rb new file mode 100644 index 000000000..e28bc531f --- /dev/null +++ b/app/controllers/repp/v1/retained_domains_controller.rb @@ -0,0 +1,11 @@ +module Repp + module V1 + class RetainedDomainsController < ActionController::API + def index + domains = RetainedDomains.new + + render json: { count: domains.count, domains: domains.to_jsonable } + end + end + end +end diff --git a/app/models/retained_domains.rb b/app/models/retained_domains.rb new file mode 100644 index 000000000..b0f077919 --- /dev/null +++ b/app/models/retained_domains.rb @@ -0,0 +1,51 @@ +# Hiding the queries behind its own class will allow us to include disputed or +# auctioned domains without meddling up with controller logic. +class RetainedDomains + RESERVED = 'reserved'.freeze + BLOCKED = 'blocked'.freeze + + attr_reader :domains + + def initialize + @domains = gather_domains + end + + def gather_domains + blocked_domains = BlockedDomain.order(name: :desc).all + reserved_domains = ReservedDomain.order(name: :desc).all + + domains = blocked_domains.to_a.union(reserved_domains.to_a) + + domains.sort_by(&:name) + end + + def to_jsonable + domains.map { |el| domain_to_json(el) } + end + + def domain_to_json(domain) + # Smelly, but ActiveRecord objects are weird and do not respond + # to usual syntax: + # case a + # when Array then "foo" + # when Hash then "bar" + # else "baz" + # end + status = case domain.class.to_s + when 'ReservedDomain' then RESERVED + when 'BlockedDomain' then BLOCKED + end + + punycode = SimpleIDN.to_ascii(domain.name) + + { + name: domain.name, + status: status, + punycode_name: punycode + } + end + + def count + domains.count + end +end diff --git a/config/routes.rb b/config/routes.rb index 53d78dfa9..a8b78a1f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -39,6 +39,18 @@ Rails.application.routes.draw do mount Repp::API => '/' + namespace :repp do + namespace :v1 do + resources :retained_domains, only: %i[index] + end + end + + match 'repp/v1/retained_domains', + controller: 'api/cors', + action: 'cors_preflight_check', + via: [:options], + as: 'repp_cors_preflight_check' + namespace :api do namespace :v1 do namespace :registrant do diff --git a/test/integration/repp/retained_domains_test.rb b/test/integration/repp/retained_domains_test.rb new file mode 100644 index 000000000..c30af001e --- /dev/null +++ b/test/integration/repp/retained_domains_test.rb @@ -0,0 +1,39 @@ +require 'test_helper' + +class ReppV1RetainedDomainsTest < ActionDispatch::IntegrationTest + # Uses magical fixtures, will fail once fixtures inside are changed: + # test/fixtures/blocked_domains.yml + # test/fixtures/reserved_domains.yml + + def test_get_index + get repp_v1_retained_domains_path + response_json = JSON.parse(response.body, symbolize_names: true) + + assert response_json[:count] == 3 + + expected_objects = [{ name: 'blocked.test', + status: 'blocked', + punycode_name: 'blocked.test' }, + { name: 'blockedäöüõ.test', + status: 'blocked', + punycode_name: 'xn--blocked-cxa7mj0e.test' }, + { name: 'reserved.test', + status: 'reserved', + punycode_name: 'reserved.test' }] + + assert_equal response_json[:domains], expected_objects + end + + def test_cors_preflight + process :options, repp_v1_retained_domains_path, headers: { 'Origin' => 'https://example.com' } + + assert_equal('https://example.com', response.headers['Access-Control-Allow-Origin']) + assert_equal('POST, GET, PUT, PATCH, DELETE, OPTIONS', + response.headers['Access-Control-Allow-Methods']) + assert_equal('Origin, Content-Type, Accept, Authorization, Token, Auth-Token, Email, ' \ + 'X-User-Token, X-User-Email', + response.headers['Access-Control-Allow-Headers']) + assert_equal('3600', response.headers['Access-Control-Max-Age']) + assert_equal('', response.body) + end +end