Refactor inactive contact archivation

Fixes #956
This commit is contained in:
Artur Beljajev 2019-03-28 22:08:16 +02:00
parent 296442e330
commit 487613db1e
14 changed files with 424 additions and 84 deletions

View file

@ -0,0 +1,38 @@
module Concerns
module Contact
module Archivable
extend ActiveSupport::Concern
included do
class_attribute :inactivity_period, instance_predicate: false, instance_writer: false
self.inactivity_period = Setting.orphans_contacts_in_months.months
end
class_methods do
def archivable
unlinked.find_each.select(&:archivable?)
end
end
def archivable?
inactive?
end
def archive
raise 'Contact cannot be archived' unless archivable?
destroy!
end
private
def inactive?
if DomainVersion.was_contact_linked?(self)
DomainVersion.contact_unlinked_more_than?(contact: self,
period: inactivity_period)
else
created_at <= inactivity_period.ago
end
end
end
end
end

View file

@ -5,6 +5,7 @@ class Contact < ActiveRecord::Base
include Concerns::Contact::Transferable
include Concerns::Contact::Identical
include Concerns::Contact::Disclosable
include Concerns::Contact::Archivable
belongs_to :original, class_name: self.name
belongs_to :registrar, required: true
@ -144,61 +145,17 @@ class Contact < ActiveRecord::Base
res.reduce([]) { |o, v| o << { id: v[:id], display_key: "#{v.name} (#{v.code})" } }
end
def find_orphans
where('
NOT EXISTS(
select 1 from domains d where d.registrant_id = contacts.id
) AND NOT EXISTS(
select 1 from domain_contacts dc where dc.contact_id = contacts.id
)
')
end
def find_linked
where('
EXISTS(
select 1 from domains d where d.registrant_id = contacts.id
) OR EXISTS(
select 1 from domain_contacts dc where dc.contact_id = contacts.id
)
')
end
def filter_by_states in_states
states = Array(in_states).dup
scope = all
# all contacts has state ok, so no need to filter by it
scope = scope.where("NOT contacts.statuses && ?::varchar[]", "{#{(STATUSES - [OK, LINKED]).join(',')}}") if states.delete(OK)
scope = scope.find_linked if states.delete(LINKED)
scope = scope.linked if states.delete(LINKED)
scope = scope.where("contacts.statuses @> ?::varchar[]", "{#{states.join(',')}}") if states.any?
scope
end
# To leave only new ones we need to check
# if contact was at any time used in domain.
# This can be checked by domain history.
# This can be checked by saved relations in children attribute
def destroy_orphans
STDOUT << "#{Time.zone.now.utc} - Destroying orphaned contacts\n" unless Rails.env.test?
counter = Counter.new
find_orphans.find_each do |contact|
ver_scope = []
%w(admin_contacts tech_contacts registrant).each do |type|
ver_scope << "(children->'#{type}')::jsonb <@ json_build_array(#{contact.id})::jsonb"
end
next if DomainVersion.where("created_at > ?", Time.now - Setting.orphans_contacts_in_months.to_i.months).where(ver_scope.join(" OR ")).any?
next if contact.linked?
contact.destroy
counter.next
STDOUT << "#{Time.zone.now.utc} Contact.destroy_orphans: ##{contact.id} (#{contact.name})\n" unless Rails.env.test?
end
STDOUT << "#{Time.zone.now.utc} - Successfully destroyed #{counter} orphaned contacts\n" unless Rails.env.test?
end
def admin_statuses
[
SERVER_UPDATE_PROHIBITED,
@ -257,6 +214,23 @@ class Contact < ActiveRecord::Base
.country.alpha2)
end
def linked
sql = <<-SQL
EXISTS(SELECT 1 FROM domains WHERE domains.registrant_id = contacts.id) OR
EXISTS(SELECT 1 FROM domain_contacts WHERE domain_contacts.contact_id =
contacts.id)
SQL
where(sql)
end
def unlinked
where('NOT EXISTS(SELECT 1 FROM domains WHERE domains.registrant_id = contacts.id)
AND
NOT EXISTS(SELECT 1 FROM domain_contacts WHERE domain_contacts.contact_id =
contacts.id)')
end
private
def registrant_user_indirect_contacts(registrant_user)
@ -557,4 +531,4 @@ class Contact < ActiveRecord::Base
def deletable?
!linked?
end
end
end

View file

@ -0,0 +1,14 @@
class InactiveContacts
attr_reader :contacts
def initialize(contacts = Contact.archivable)
@contacts = contacts
end
def archive
contacts.each do |contact|
contact.archive
yield contact if block_given?
end
end
end

View file

@ -5,4 +5,41 @@ class DomainVersion < PaperTrail::Version
self.sequence_name = :log_domains_id_seq
scope :deleted, -> { where(event: 'destroy') }
end
def self.was_contact_linked?(contact)
sql = <<-SQL
SELECT
COUNT(*)
FROM
#{table_name}
WHERE
(children->'registrant') @> '#{contact.id}'
OR
(children->'admin_contacts') @> '#{contact.id}'
OR
(children->'tech_contacts') @> '#{contact.id}'
SQL
count_by_sql(sql).nonzero?
end
def self.contact_unlinked_more_than?(contact:, period:)
sql = <<-SQL
SELECT
COUNT(*)
FROM
#{table_name}
WHERE
created_at < TIMESTAMP WITH TIME ZONE '#{period.ago}'
AND (
(children->'registrant') @> '#{contact.id}'
OR
(children->'admin_contacts') @> '#{contact.id}'
OR
(children->'tech_contacts') @> '#{contact.id}'
)
SQL
count_by_sql(sql).nonzero?
end
end