diff --git a/app/controllers/admin/auctions_controller.rb b/app/controllers/admin/auctions_controller.rb new file mode 100644 index 000000000..c1023b705 --- /dev/null +++ b/app/controllers/admin/auctions_controller.rb @@ -0,0 +1,142 @@ +module Admin + class AuctionsController < BaseController + load_and_authorize_resource + + def index + params[:q] ||= {} + + @auctions = Auction.with_domain_name(params[:domain_matches]) + .with_status(params[:statuses_contains]) + .with_start_created_at_date(params[:created_at_start]) + .with_end_created_at_date(params[:created_at_end]) + .order(created_at: :desc) + + @auction = Auction.new + + normalize_search_parameters do + @q = @auctions.ransack(PartialSearchFormatter.format(params[:q])) + @auctions = @q.result.page(params[:page]) + end + + @auctions = @auctions.per(params[:results_per_page_auction]) if params[:results_per_page_auction].to_i.positive? + + domains = ReservedDomain.all.order(:name) + q = domains.ransack(PartialSearchFormatter.format(params[:q])) + @domains = q.result.page(params[:page]) + @domains = @domains.per(params[:results_per_page]) if params[:results_per_page].to_i.positive? + + render_by_format('admin/auctions/index', 'auctions') + end + + def create + auction = Auction.new(domain: params[:domain], status: Auction.statuses[:started], platform: 'manual') + + if domain_exists_in_blocked_disputed_and_registered?(params[:domain]) + flash[:alert] = "Adding #{params[:domain]} failed - domain registered or regsitration is blocked" + redirect_to admin_auctions_path and return + end + + result = check_availability(params[:domain])[0] + if result[:avail].zero? + flash[:alert] = "Cannot generate domain. Reason: #{result[:reason]}" + redirect_to admin_auctions_path and return + end + + if auction.save + reserved_domain = auction.domain if remove_from_reserved(auction) + flash[:notice] = "Auction #{params[:domain]} created. + #{reserved_domain.present? ? 'These domain will be removed from reserved list: ' + reserved_domain : ' '}" + else + flash[:alert] = 'Something goes wrong' + end + + redirect_to admin_auctions_path + end + + def upload_spreadsheet + if params[:q].nil? + flash[:alert] = 'No file upload! Look at the left of upload button!' + redirect_to admin_auctions_path and return + end + + filename = params[:q][:file] + table = CSV.parse(File.read(filename), headers: true) + + failed_names = [] + reserved_domains = [] + + if validate_table(table) + table.each do |row| + record = row.to_h + + if domain_exists_in_blocked_disputed_and_registered?(record['name']) + failed_names << record['name'] + + next + end + + result = check_availability(record['name'])[0] + if result[:avail].zero? + failed_names << record['name'] + + next + end + + auction = Auction.new(domain: record['name'], status: Auction.statuses[:started], platform: 'manual') + flag = remove_from_reserved(auction) if auction.save! + reserved_domains << auction.domain if flag + end + + message_template = "Domains added! + #{reserved_domains.present? ? + 'These domains will be removed from reserved list: ' + reserved_domains.join(' ') + '! ' + : '! '} + #{failed_names.present? ? 'These domains were ignored: ' + failed_names.join(' ') : '!'}" + + flash[:notice] = message_template + else + flash[:alert] = "Invalid CSV format. Should be column with 'name' where is the list of name of domains!" + end + + redirect_to admin_auctions_path + end + + private + + def check_availability(domain_name) + Epp::Domain.check_availability(domain_name) + end + + def domain_exists_in_blocked_disputed_and_registered?(domain_name) + Domain.exists?(name: domain_name) || + BlockedDomain.exists?(name: domain_name) || + Dispute.exists?(domain_name: domain_name) || + Auction.exists?(domain: domain_name) + end + + def validate_table(table) + first_row = table.headers + first_row.include? 'name' + end + + def remove_from_reserved(auction) + domain = ReservedDomain.find_by(name: auction.domain) + + domain.destroy if domain.present? + end + + def normalize_search_parameters + ca_cache = params[:q][:valid_to_lteq] + begin + end_time = params[:q][:valid_to_lteq].try(:to_date) + params[:q][:valid_to_lteq] = end_time.try(:end_of_day) + rescue + logger.warn('Invalid date') + end + + yield + + params[:q][:valid_to_lteq] = ca_cache + end + end +end diff --git a/app/controllers/admin/reserved_domains_controller.rb b/app/controllers/admin/reserved_domains_controller.rb index 1bfade83e..20957dec4 100644 --- a/app/controllers/admin/reserved_domains_controller.rb +++ b/app/controllers/admin/reserved_domains_controller.rb @@ -51,8 +51,26 @@ module Admin redirect_to admin_reserved_domains_path end + def release_to_auction + redirect_to admin_reserved_domains_path and return if params[:reserved_elements].nil? + + reserved_domains_ids = params[:reserved_elements][:domain_ids] + reserved_domains = ReservedDomain.where(id: reserved_domains_ids) + + reserved_domains.each do |domain| + Auction.create!(domain: domain.name, status: Auction.statuses[:started], platform: 'manual') + domain.destroy! + end + + redirect_to admin_auctions_path + end + private + def reserved_checked_elements + # params.require(:reserved_elements).permit(:name, :password) + end + def reserved_domain_params params.require(:reserved_domain).permit(:name, :password) end diff --git a/app/controllers/api/v1/auctions_controller.rb b/app/controllers/api/v1/auctions_controller.rb index de8e94442..9a01f4e68 100644 --- a/app/controllers/api/v1/auctions_controller.rb +++ b/app/controllers/api/v1/auctions_controller.rb @@ -44,7 +44,7 @@ module Api private def serializable_hash(auction) - { id: auction.uuid, domain: auction.domain, status: auction.status } + { id: auction.uuid, domain: auction.domain, status: auction.status, platform: auction.platform } end def serializable_hash_for_update_action(auction) diff --git a/app/helpers/auction_helper.rb b/app/helpers/auction_helper.rb new file mode 100644 index 000000000..25cf463af --- /dev/null +++ b/app/helpers/auction_helper.rb @@ -0,0 +1,19 @@ +module AuctionHelper + include ActionView::Helpers::TagHelper + + def colorize_auction(auction) + case auction.status + when 'started' then render_status_black(auction.domain) + when 'awaiting_payment' then render_status_black(auction.domain) + else render_status_green(auction.domain) + end + end + + def render_status_black(name) + tag.span name.to_s, style: 'color: black;' + end + + def render_status_green(name) + tag.span name.to_s, style: 'color: green;' + end +end diff --git a/app/models/ability.rb b/app/models/ability.rb index caca24524..bc2caa6ba 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -95,6 +95,7 @@ class Ability can :manage, User can :manage, ApiUser can :manage, AdminUser + can :manage, Auction can :manage, Certificate can :manage, LegalDocument can :manage, BankStatement diff --git a/app/models/auction.rb b/app/models/auction.rb index 791184d60..465a827ec 100644 --- a/app/models/auction.rb +++ b/app/models/auction.rb @@ -9,11 +9,30 @@ class Auction < ApplicationRecord domain_not_registered: 'domain_not_registered', } + enum platform: %i[auto manual] + PENDING_STATUSES = [statuses[:started], statuses[:awaiting_payment], statuses[:payment_received]].freeze + private_constant :PENDING_STATUSES + scope :with_status, ->(status) { + where(status: status) if status.present? + } + + scope :with_start_created_at_date, ->(start_created_at) { + where('created_at >= ?', start_created_at) if start_created_at.present? + } + + scope :with_end_created_at_date, ->(end_created_at) { + where('created_at <= ?', end_created_at) if end_created_at.present? + } + + scope :with_domain_name, ->(domain_name) { + where('domain ilike ?', "%#{domain_name.strip}%") if domain_name.present? + } + def self.pending(domain_name) find_by(domain: domain_name.to_s, status: PENDING_STATUSES) end diff --git a/app/models/dns/domain_name.rb b/app/models/dns/domain_name.rb index bceb4433b..6c68c3797 100644 --- a/app/models/dns/domain_name.rb +++ b/app/models/dns/domain_name.rb @@ -35,6 +35,7 @@ module DNS def sell_at_auction auction = Auction.new auction.domain = name + auction.platform = 'auto' auction.start ToStdout.msg "Created the auction: #{auction.inspect}" update_whois_from_auction(auction) diff --git a/app/views/admin/auctions/_modal.html.erb b/app/views/admin/auctions/_modal.html.erb new file mode 100644 index 000000000..e57d2139d --- /dev/null +++ b/app/views/admin/auctions/_modal.html.erb @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/app/views/admin/auctions/index.html.erb b/app/views/admin/auctions/index.html.erb new file mode 100644 index 000000000..551be567f --- /dev/null +++ b/app/views/admin/auctions/index.html.erb @@ -0,0 +1,155 @@ + + +
+
+ <%= form_with url: admin_auctions_path, method: :get, html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f| %> +
+
+
+ <%= f.label :domain %> + <%= f.search_field :domain_matches, value: params[:domain_matches], class: 'form-control', placeholder: t(:name) %> +
+
+ <%= f.label :status %> + <%= select_tag :statuses_contains, options_for_select(Auction.statuses.map { |x| [x[0], x[1]] }, params[:q][:status]), { include_blank:true, class: 'form-control' } %> +
+
+
+
+ <%= f.label t(:created_at_from) %> + <%= f.search_field :created_at_start, value: params[:created_at_start], class: 'form-control js-datepicker', placeholder: t(:created_at_from) %> +
+
+
+
+ <%= f.label t(:created_at_until) %> + <%= f.search_field :created_at_end, value: params[:created_at_end], class: 'form-control js-datepicker', placeholder: t(:created_at_until) %> +
+
+
+
+ <%= label_tag t(:results_per_page) %> + <%= text_field_tag :results_per_page_auction, params[:results_per_page_auction], class: 'form-control', placeholder: t(:results_per_page) %> +
+
+
+ +
+ <%= link_to('Clear', admin_auctions_path, class: 'btn btn-default') %> +
+
+ <%= link_to 'Download auction list', admin_auctions_path(format: :csv, params: params.permit!), + "data-toggle" => "tooltip", "data-placement" => "bottom", "title" => 'Download CSV', + class: 'btn btn-primary' %> +
+
+ <%= link_to "#", class: "btn btn-warning edit", + data: { + toggle: "modal", + url: admin_reserved_domains_path, + target: "#user-form-edit"} do %> + + Get reserved domains + <% end %> + + <%= render 'modal' %> + +
+
+
+ <% end %> +
+
+ +
+ + <%= search_form_for [:admin, @q], method: :post, html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f| %> + <%= label_tag :new_auction %> + +
+ <%= text_field_tag :domain, params[:domain], class: 'form-control', placeholder: 'domain name' %> + <%= f.submit 'Create', class: 'btn btn-primary', style: 'margin-left: .4rem;' %> +
+ <% end %> + +
+ +
+
+ <%= search_form_for @q, url: upload_spreadsheet_admin_auctions_path, method: :post, html: { style: 'margin-bottom: 0; display: flex; flex-direction: row; align-items: center;', class: 'js-form', autocomplete: 'off' } do |f| %> + <%= f.file_field :file, + accept: ".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel", + direct_upload: true, + style: 'width: 200px;' %> + <%= f.submit 'Upload csv', class: 'btn btn-primary' %> + <% end %> + +
+
+ +
+
+
+ + + + + + + + + + + + + + <% @auctions.each do |auction| %> + + + + + + + + + <% end %> + +
+ <%= sort_link(@q, 'domain') %> + + <%= sort_link(@q, 'status') %> + + <%= sort_link(@q, 'created_at') %> + + <%= sort_link(@q, 'registration_code') %> + + <%= sort_link(@q, 'registration_deadline') %> + + <%= sort_link(@q, 'platform', 'Type') %> +
<%= colorize_auction(auction) %><%= auction.status %><%= auction.created_at %><%= auction.registration_code %><%= auction.registration_deadline %><%= auction.platform.nil? ? 'auto' : auction.platform %>
+
+
+
+ +
+
+ <%= paginate @auctions %> +
+
+ +
+
+ + \ No newline at end of file diff --git a/app/views/admin/base/_menu.haml b/app/views/admin/base/_menu.haml index c5edd4708..92efc2347 100644 --- a/app/views/admin/base/_menu.haml +++ b/app/views/admin/base/_menu.haml @@ -31,6 +31,7 @@ %li.dropdown-header= t(:system) %li= link_to t('.settings'), admin_settings_path %li= link_to t('.zones'), admin_zones_path + %li= link_to t(:auctions), admin_auctions_path %li= link_to t('.blocked_domains'), admin_blocked_domains_path %li= link_to t('.reserved_domains'), admin_reserved_domains_path %li= link_to t('.disputed_domains'), admin_disputes_path diff --git a/app/views/admin/reserved_domains/index.html.erb b/app/views/admin/reserved_domains/index.html.erb new file mode 100644 index 000000000..109dbad9a --- /dev/null +++ b/app/views/admin/reserved_domains/index.html.erb @@ -0,0 +1,137 @@ +<% content_for :actions do %> + <%= link_to(t('.new_btn'), new_admin_reserved_domain_path, class: 'btn btn-primary') %> +<% end %> + +<%= render 'shared/title', name: t('.title') %> +
+
+ <%= search_form_for [:admin, @q], html: { style: 'margin-bottom: 0;', class: 'js-form', autocomplete: 'off' } do |f| %> +
+
+
+ <%= f.label :name %> + <%= f.search_field :name_matches, value: params[:q][:name_matches], class: 'form-control', placeholder: t(:name) %> +
+
+
+
+ <%= f.label t(:created_at_from) %> + <%= f.search_field :created_at_gteq, value: params[:q][:created_at_gteq], class: 'form-control js-datepicker', placeholder: t(:created_at_from) %> +
+
+
+
+ <%= f.label t(:created_at_until) %> + <%= f.search_field :created_at_lteq, value: params[:q][:created_at_lteq], class: 'form-control js-datepicker', placeholder: t(:created_at_until) %> +
+
+
+
+
+
+ <%= label_tag t(:results_per_page) %> + <%= text_field_tag :results_per_page, params[:results_per_page], class: 'form-control', placeholder: t(:results_per_page) %> +
+
+
+ + <%= link_to(t('.csv_btn'), admin_reserved_domains_path(format: :csv, params: params.permit!), class: 'btn btn-default') %> + <%= link_to(t('.reset_btn'), admin_reserved_domains_path, class: 'btn btn-default') %> +
+
+ <% end %> +
+
+ +
+ +<%= form_for :reserved_elements, url: release_to_auction_admin_reserved_domains_path, html: { class: 'form-horizontal', autocomplete: 'off' } do |f| %> +
+ <%= f.submit 'Send to the auction list', class: 'btn btn-primary', style: 'margin: 10px 0 20px 0;' %> + Domains will be removed from reserved list! +
+ +
+
+
+ + + + + + + + + + + + + <% @domains.each do |x| %> + + + + + + + + + <% end %> + +
+ <%= check_box_tag :check_all %> + + <%= sort_link(@q, 'name') %> + + <%= sort_link(@q, 'password') %> + + <%= sort_link(@q, 'created_at', t(:created_at)) %> + + <%= sort_link(@q, 'updated_at', t(:updated_at)) %> + + <%= t(:actions) %> +
+ <%= f.check_box :domain_ids, { multiple: true }, x.id, nil %> + + <%= x.name %> + + <%= x.password %> + + <%= l(x.created_at, format: :short) %> + + <%= l(x.updated_at, format: :short) %> + + <%= link_to(t(:edit_pw), edit_admin_reserved_domain_path(id: x.id), class: 'btn btn-primary btn-xs') %> + <%= link_to(t(:delete), delete_admin_reserved_domain_path(id: x.id), data: { confirm: t(:are_you_sure) }, class: 'btn btn-danger btn-xs') %> +
+
+
+
+<% end %> + +
+
+ <%= paginate @domains %> +
+
+ +
+
+ + \ No newline at end of file diff --git a/app/views/admin/reserved_domains/index.haml b/app/views/admin/reserved_domains/index2.haml similarity index 90% rename from app/views/admin/reserved_domains/index.haml rename to app/views/admin/reserved_domains/index2.haml index 5444ba34d..a3b49e0b0 100644 --- a/app/views/admin/reserved_domains/index.haml +++ b/app/views/admin/reserved_domains/index2.haml @@ -30,6 +30,7 @@   = link_to(t('.csv_btn'), admin_reserved_domains_path(format: :csv, params: params.permit!), class: 'btn btn-default') = link_to(t('.reset_btn'), admin_reserved_domains_path, class: 'btn btn-default') + = link_to 'Send to auction',release_to_auction_admin_reserved_domains_path, method: :post, class: 'btn btn-default', style: 'margin-top: 5px;' %hr .row .col-md-12 @@ -37,6 +38,7 @@ %table.table.table-hover.table-bordered.table-condensed %thead %tr + %th{class: 'col-xs-1'} %th{class: 'col-xs-2'} = sort_link(@q, 'name') %th{class: 'col-xs-2'} @@ -50,6 +52,8 @@ %tbody - @domains.each do |x| %tr + %td{class: 'text-center'} + = check_box_tag "reserved_domains[domain_ids][]", x.id, false %td= x.name %td= x.password %td= l(x.created_at, format: :short) diff --git a/config/locales/en.yml b/config/locales/en.yml index 28d2d0281..c36dcadeb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -189,6 +189,7 @@ en: log_out: 'Log out (%{user})' system: 'System' domains: 'Domains' + auctions: 'Auctions' registrars: 'Registrars' valid_to: 'Valid to' name: 'Name' diff --git a/config/routes.rb b/config/routes.rb index a2a4556f7..4ba44300d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -266,6 +266,13 @@ Rails.application.routes.draw do resources :accounts resources :account_activities + resources :auctions, only: [ :index, :create ] do + collection do + post 'upload_spreadsheet', to: 'auctions#upload_spreadsheet', as: :upload_spreadsheet + end + end + # post 'admi/upload_spreadsheet', to: 'customers#upload_spreadsheet', as: :customers_upload_spreadsheet + resources :bank_statements do resources :bank_transactions @@ -335,6 +342,10 @@ Rails.application.routes.draw do member do get 'delete' end + + collection do + post 'release_to_auction', to: 'reserved_domains#release_to_auction', as: 'release_to_auction' + end end resources :disputes do member do diff --git a/db/migrate/20220412130856_add_type_to_auction.rb b/db/migrate/20220412130856_add_type_to_auction.rb new file mode 100644 index 000000000..14714e868 --- /dev/null +++ b/db/migrate/20220412130856_add_type_to_auction.rb @@ -0,0 +1,5 @@ +class AddTypeToAuction < ActiveRecord::Migration[6.1] + def change + add_column :auctions, :platform, :integer, null: true + end +end diff --git a/db/structure.sql b/db/structure.sql index 2c4723dce..2697c3147 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -337,7 +337,8 @@ CREATE TABLE public.auctions ( uuid uuid DEFAULT public.gen_random_uuid() NOT NULL, created_at timestamp without time zone NOT NULL, registration_code character varying, - registration_deadline timestamp without time zone + registration_deadline timestamp without time zone, + platform integer ); @@ -813,7 +814,8 @@ CREATE TABLE public.dnskeys ( updator_str character varying, legacy_domain_id integer, updated_at timestamp without time zone, - validation_datetime timestamp without time zone + validation_datetime timestamp without time zone, + failed_validation_reason character varying ); @@ -1089,6 +1091,7 @@ CREATE TABLE public.invoices ( buyer_vat_no character varying, issue_date date NOT NULL, e_invoice_sent_at timestamp without time zone, + payment_link character varying, CONSTRAINT invoices_due_date_is_not_before_issue_date CHECK ((due_date >= issue_date)) ); @@ -5084,6 +5087,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220406085500'), ('20220413073315'), ('20220413084536'), -('20220413084748'); - - +('20220413084748'), +('20220124105717'), +('20220216113112'), +('20220228093211'), +('20220412130856'); diff --git a/test/integration/api/v1/auctions/details_test.rb b/test/integration/api/v1/auctions/details_test.rb index 374051258..f1ab11545 100644 --- a/test/integration/api/v1/auctions/details_test.rb +++ b/test/integration/api/v1/auctions/details_test.rb @@ -20,8 +20,8 @@ class ApiV1AuctionDetailsTest < ActionDispatch::IntegrationTest assert_response :ok assert_equal ({ 'id' => '1b3ee442-e8fe-4922-9492-8fcb9dccc69c', 'domain' => 'auction.test', - 'status' => Auction.statuses[:no_bids] }), ActiveSupport::JSON - .decode(response.body) + 'status' => Auction.statuses[:no_bids], + 'platform' => nil }), ActiveSupport::JSON.decode(response.body) end def test_auction_not_found diff --git a/test/integration/api/v1/auctions/list_test.rb b/test/integration/api/v1/auctions/list_test.rb index ae3f4338f..dcdff5cec 100644 --- a/test/integration/api/v1/auctions/list_test.rb +++ b/test/integration/api/v1/auctions/list_test.rb @@ -15,8 +15,8 @@ class ApiV1AuctionListTest < ActionDispatch::IntegrationTest assert_response :ok assert_equal ([{ 'id' => '1b3ee442-e8fe-4922-9492-8fcb9dccc69c', 'domain' => 'auction.test', - 'status' => Auction.statuses[:started] }]), ActiveSupport::JSON - .decode(response.body) + 'status' => Auction.statuses[:started], + 'platform' => nil }]), ActiveSupport::JSON.decode(response.body) end def test_does_not_return_finished_auctions diff --git a/test/integration/api/v1/auctions/update_test.rb b/test/integration/api/v1/auctions/update_test.rb index 5b00a1052..4e48c0ca5 100644 --- a/test/integration/api/v1/auctions/update_test.rb +++ b/test/integration/api/v1/auctions/update_test.rb @@ -27,8 +27,8 @@ class ApiV1AuctionUpdateTest < ActionDispatch::IntegrationTest assert_response :ok assert_equal ({ 'id' => '1b3ee442-e8fe-4922-9492-8fcb9dccc69c', 'domain' => 'auction.test', - 'status' => Auction.statuses[:awaiting_payment] }), ActiveSupport::JSON - .decode(response.body) + 'status' => Auction.statuses[:awaiting_payment], + 'platform' => nil }), ActiveSupport::JSON.decode(response.body) end def test_marks_as_awaiting_payment