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