Integrate auction

Closes #874
This commit is contained in:
Artur Beljajev 2018-11-29 15:08:22 +02:00
parent 640faaadb9
commit 42e8f86dae
51 changed files with 1619 additions and 53 deletions

View file

@ -0,0 +1,47 @@
module Api
module V1
class AuctionsController < BaseController
before_action :authenticate, except: :index
def index
render json: Auction.started.map { |auction| serializable_hash(auction) }
end
def show
auction = Auction.find_by(uuid: params[:uuid])
render json: serializable_hash(auction)
end
def update
auction = Auction.find_by(uuid: params[:uuid])
case params[:status]
when Auction.statuses[:awaiting_payment]
auction.awaiting_payment!
when Auction.statuses[:no_bids]
auction.mark_as_no_bids
when Auction.statuses[:payment_received]
auction.mark_as_payment_received
when Auction.statuses[:payment_not_received]
auction.mark_as_payment_not_received
else
raise "Invalid status #{params[:status]}"
end
render json: serializable_hash_for_update_action(auction)
end
private
def serializable_hash(auction)
{ id: auction.uuid, domain: auction.domain, status: auction.status }
end
def serializable_hash_for_update_action(auction)
hash = serializable_hash(auction)
hash[:registration_code] = auction.registration_code if auction.payment_received?
hash
end
end
end
end

View file

@ -0,0 +1,18 @@
require 'rails5_api_controller_backport'
module Api
module V1
class BaseController < ActionController::API
private
def authenticate
ip_allowed = allowed_ips.include?(request.remote_ip)
head :unauthorized unless ip_allowed
end
def allowed_ips
ENV['auction_api_allowed_ips'].split(',').map(&:strip)
end
end
end
end

View file

@ -1,9 +1,34 @@
class Epp::DomainsController < EppController
before_action :find_domain, only: [:info, :renew, :update, :transfer, :delete]
before_action :find_password, only: [:info, :update, :transfer, :delete]
before_action :find_domain, only: %i[renew update transfer delete]
before_action :find_password, only: %i[update transfer delete]
skip_authorization_check only: :info
def info
if Domain.release_to_auction
domain_name = DNS::DomainName.new(params[:parsed_frame].at_css('name').text.strip.downcase)
if domain_name.at_auction?
@name = domain_name
@status = 'At auction'
render_epp_response '/epp/domains/info_auction'
return
elsif domain_name.awaiting_payment?
@name = domain_name
@status = 'Awaiting payment'
render_epp_response '/epp/domains/info_auction'
return
elsif domain_name.pending_registration?
@name = domain_name
@status = 'Reserved'
render_epp_response '/epp/domains/info_auction'
return
end
end
find_domain
find_password
authorize! :info, @domain, @password
@hosts = params[:parsed_frame].css('name').first['hosts'] || 'all'
case @hosts
@ -20,6 +45,36 @@ class Epp::DomainsController < EppController
def create
authorize! :create, Epp::Domain
if Domain.release_to_auction
request_domain_name = params[:parsed_frame].css('name').text.strip.downcase
domain_name = DNS::DomainName.new(request_domain_name)
if domain_name.at_auction?
throw :epp_error,
code: '2306',
msg: 'Parameter value policy error: domain is at auction'
elsif domain_name.awaiting_payment?
throw :epp_error,
code: '2003',
msg: 'Required parameter missing; reserved>pw element required for reserved domains'
elsif domain_name.pending_registration?
registration_code = params[:parsed_frame].css('reserved > pw').text
if registration_code.empty?
throw :epp_error,
code: '2003',
msg: 'Required parameter missing; reserved>pw element is required'
end
unless domain_name.available_with_code?(registration_code)
throw :epp_error,
code: '2202',
msg: 'Invalid authorization information; invalid reserved>pw value'
end
end
end
@domain = Epp::Domain.new_from_epp(params[:parsed_frame], current_user)
handle_errors(@domain) and return if @domain.errors.any?
@domain.valid?
@ -38,6 +93,12 @@ class Epp::DomainsController < EppController
price: @domain_pricelist
})
if Domain.release_to_auction && domain_name.pending_registration?
active_auction = Auction.find_by(domain: domain_name.to_s,
status: Auction.statuses[:payment_received])
active_auction.domain_registered!
end
render_epp_response '/epp/domains/create'
else
handle_errors(@domain)

63
app/models/auction.rb Normal file
View file

@ -0,0 +1,63 @@
class Auction < ActiveRecord::Base
enum status: {
started: 'started',
awaiting_payment: 'awaiting_payment',
no_bids: 'no_bids',
payment_received: 'payment_received',
payment_not_received: 'payment_not_received',
domain_registered: 'domain_registered',
}
PENDING_STATUSES = [statuses[:started],
statuses[:awaiting_payment],
statuses[:payment_received]].freeze
private_constant :PENDING_STATUSES
def self.sell(domain_name)
create!(domain: domain_name.to_s, status: statuses[:started])
end
def self.pending(domain_name)
find_by(domain: domain_name.to_s, status: PENDING_STATUSES)
end
def mark_as_no_bids
transaction do
DNS::DomainName.new(domain).update_whois
no_bids!
end
end
def mark_as_payment_received
self.status = self.class.statuses[:payment_received]
generate_registration_code
save!
end
def mark_as_payment_not_received
self.status = self.class.statuses[:payment_not_received]
transaction do
save!
restart
end
end
def domain_registrable?(registration_code = nil)
payment_received? && registration_code_matches?(registration_code)
end
private
def generate_registration_code
self.registration_code = SecureRandom.hex
end
def restart
self.class.create!(domain: domain, status: self.class.statuses[:started])
end
def registration_code_matches?(code)
registration_code == code
end
end

View file

@ -1,21 +1,6 @@
module Concerns::Domain::Discardable
extend ActiveSupport::Concern
class_methods do
def discard_domains
domains = where('delete_at < ? AND ? != ALL(coalesce(statuses, array[]::varchar[])) AND' \
' ? != ALL(COALESCE(statuses, array[]::varchar[]))',
Time.zone.now,
DomainStatus::SERVER_DELETE_PROHIBITED,
DomainStatus::DELETE_CANDIDATE)
domains.each do |domain|
domain.discard
yield domain if block_given?
end
end
end
def discard
raise 'Domain is already discarded' if discarded?

View file

@ -0,0 +1,46 @@
module Concerns
module Domain
module Releasable
extend ActiveSupport::Concern
class_methods do
def release_domains
releasable_domains.each do |domain|
domain.release
yield domain if block_given?
end
end
private
def releasable_domains
if release_to_auction
where('delete_at < ? AND ? != ALL(coalesce(statuses, array[]::varchar[]))',
Time.zone.now,
DomainStatus::SERVER_DELETE_PROHIBITED)
else
where('delete_at < ? AND ? != ALL(coalesce(statuses, array[]::varchar[])) AND' \
' ? != ALL(COALESCE(statuses, array[]::varchar[]))',
Time.zone.now,
DomainStatus::SERVER_DELETE_PROHIBITED,
DomainStatus::DELETE_CANDIDATE)
end
end
end
included do
class_attribute :release_to_auction
self.release_to_auction = ENV['release_domains_to_auction'] == 'true'
end
def release
if release_to_auction
domain_name.sell_at_auction
destroy!
else
discard
end
end
end
end
end

View file

@ -6,8 +6,16 @@ module DNS
@name = name
end
def available?
!unavailable?
end
def available_with_code?(code)
pending_auction.domain_registrable?(code)
end
def unavailable?
registered? || blocked? || zone_with_same_origin?
at_auction? || awaiting_payment? || registered? || blocked? || zone_with_same_origin?
end
def unavailability_reason
@ -17,9 +25,38 @@ module DNS
:blocked
elsif zone_with_same_origin?
:zone_with_same_origin
elsif at_auction?
:at_auction
elsif awaiting_payment?
:awaiting_payment
end
end
def sell_at_auction
Auction.sell(self)
update_whois
end
def at_auction?
pending_auction&.started?
end
def awaiting_payment?
pending_auction&.awaiting_payment?
end
def pending_registration?
pending_auction&.payment_received?
end
def update_whois
Whois::Record.refresh(self)
end
def to_s
name
end
private
attr_reader :name
@ -35,5 +72,9 @@ module DNS
def zone_with_same_origin?
DNS::Zone.where(origin: name).any?
end
def pending_auction
Auction.pending(self)
end
end
end

View file

@ -8,6 +8,7 @@ class Domain < ActiveRecord::Base
include Concerns::Domain::Deletable
include Concerns::Domain::Transferable
include Concerns::Domain::RegistryLockable
include Concerns::Domain::Releasable
has_paper_trail class_name: "DomainVersion", meta: { children: :children_log }
@ -582,6 +583,10 @@ class Domain < ActiveRecord::Base
hash
end
def domain_name
DNS::DomainName.new(name)
end
def self.to_csv
CSV.generate do |csv|
csv << column_names

View file

@ -1,5 +1,23 @@
module Whois
class Record < Whois::Server
self.table_name = 'whois_records'
def self.disclaimer
Setting.registry_whois_disclaimer
end
def self.refresh(domain_name)
if domain_name.at_auction?
create!(name: domain_name, json: { name: domain_name.to_s,
status: 'AtAuction',
disclaimer: disclaimer })
elsif domain_name.awaiting_payment? || domain_name.pending_registration?
find_by(name: domain_name.to_s).update!(json: { name: domain_name.to_s,
status: 'PendingRegistration',
disclaimer: disclaimer })
else
find_by(name: domain_name.to_s).destroy!
end
end
end
end

View file

@ -0,0 +1,16 @@
xml.epp_head do
xml.response do
xml.result('code' => '1000') do
xml.msg 'Command completed successfully'
end
xml.resData do
xml.tag! 'domain:infData', 'xmlns:domain' => 'https://epp.tld.ee/schema/domain-eis-1.0.xsd' do
xml.tag!('domain:name', @name)
xml.tag!('domain:status', 's' => @status)
end
end
render('epp/shared/trID', builder: xml)
end
end