Skip to content

Commit

Permalink
Integrate auction
Browse files Browse the repository at this point in the history
Closes #874
  • Loading branch information
Artur Beljajev committed Jan 16, 2019
1 parent 9d8479e commit f6709be
Show file tree
Hide file tree
Showing 53 changed files with 1,607 additions and 52 deletions.
48 changes: 48 additions & 0 deletions app/controllers/api/v1/auctions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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])
auction.update!(updatable_params)

if auction.no_bids?
auction.mark_as_no_bids
elsif auction.payment_received?
auction.mark_as_payment_received
elsif auction.payment_not_received?
auction.mark_as_payment_not_received
end

render json: serializable_hash_for_update_action(auction)
end

private

def updatable_params
return unless Auction.statuses.values.include?(params[:status])
params.permit(:status)
end

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
18 changes: 18 additions & 0 deletions app/controllers/api/v1/base_controller.rb
Original file line number Diff line number Diff line change
@@ -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
Rails.configuration.auction.api_allowed_ips.split(',').map(&:strip)
end
end
end
end
69 changes: 67 additions & 2 deletions app/controllers/epp/domains_controller.rb
Original file line number Diff line number Diff line change
@@ -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: [:renew, :update, :transfer, :delete]
before_action :find_password, only: [: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
Expand All @@ -20,6 +45,40 @@ def info

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: I18n.t('activerecord.errors.models.epp_domain.attributes.base.required_parameter_missing_reserved'),
}
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: I18n.t('activerecord.errors.models.epp_domain.attributes.base.invalid_auth_information_reserved'),
}
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?
Expand All @@ -38,6 +97,12 @@ def create
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)
Expand Down
60 changes: 60 additions & 0 deletions app/models/auction.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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
DNS::DomainName.new(domain).update_whois
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
15 changes: 0 additions & 15 deletions app/models/concerns/domain/discardable.rb
Original file line number Diff line number Diff line change
@@ -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?

Expand Down
42 changes: 42 additions & 0 deletions app/models/concerns/domain/releasable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Concerns::Domain::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
mattr_accessor :release_to_auction
self.release_to_auction = Rails.configuration.domains.release_to_auction
end

def release
if release_to_auction
domain_name.sell_at_auction
destroy!
else
discard
end
end
end
64 changes: 64 additions & 0 deletions app/models/dns/domain_name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'observer'

module DNS
# Namespace is needed, because a class with the same name is defined by `domain_name` gem,
# a dependency of `actionmailer`,
class DomainName
def initialize(name)
@name = name
end

def available?
!unavailable?
end

def available_with_code?(code)
pending_auction.domain_registrable?(code)
end

def unavailable?
at_auction? || awaiting_payment?
end

def unavailability_reason
if 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

def pending_auction
Auction.pending(self)
end
end
end
5 changes: 5 additions & 0 deletions app/models/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down Expand Up @@ -582,6 +583,10 @@ def as_json(_options)
hash
end

def domain_name
DNS::DomainName.new(name)
end

def self.to_csv
CSV.generate do |csv|
csv << column_names
Expand Down
10 changes: 10 additions & 0 deletions app/models/epp/domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,16 @@ def check_availability(domains)
next
end

if Domain.release_to_auction
domain_name = DNS::DomainName.new(x)

if domain_name.unavailable?
reason = I18n.t("errors.messages.epp_domain_#{domain_name.unavailability_reason}")
res << { name: x, avail: 0, reason: reason }
next
end
end

if ReservedDomain.pw_for(x).present?
res << { name: x, avail: 0, reason: I18n.t('errors.messages.epp_domain_reserved') }
next
Expand Down
Loading

0 comments on commit f6709be

Please sign in to comment.