zammad/app/models/data_privacy_task.rb
Rolf Schmidt 25dc9ab228 Fixes #6017 - DataPrivacyTask does hang up on >600k customer tickets.
Co-authored-by: Mantas Masalskis <mm@zammad.com>
2026-04-20 08:59:56 +02:00

184 lines
4.5 KiB
Ruby

# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
class DataPrivacyTask < ApplicationModel
include HasDefaultModelUserRelations
include DataPrivacyTask::HasActivityStreamLog
include ChecksClientNotification
scope :failed, -> { where(state: 'failed') }
scope :in_process, -> { where(state: 'in process') }
scope :completed, -> { where(state: 'completed') }
store :preferences
# optional because related data will get deleted and it would
# cause validation errors if e.g. the created_by_id of the task
# would need to get mapped by a deletion
belongs_to :deletable, polymorphic: true, optional: true
before_create :prepare_deletion_preview
validates_with Validations::DataPrivacyTaskValidator
MAX_PREVIEW_TICKETS = 1000
def perform
perform_deletable
update!(state: 'completed')
rescue => e
handle_exception(e)
end
def self.cleanup(diff = 12.months)
where(created_at: ...diff.ago)
.destroy_all
true
end
def prepare_deletion_preview
prepare_deletion_preview_tickets
prepare_deletion_preview_user
prepare_deletion_preview_organization
prepare_deletion_preview_anonymize
end
def deletion_counts
preferences.slice(:owner_tickets_count, :customer_tickets_count)
end
private
# set user inactive before destroy to prevent
# new online notifications or other events while
# the deletion process is running
# https://github.com/zammad/zammad/issues/3942
def update_inactive(object)
object.update(active: false)
end
def perform_deletable
return if !deletable_type.constantize.exists?(id: deletable_id)
prepare_deletion_preview
save!
case deletable
when User
perform_user_or_organization
when Ticket
perform_ticket
end
end
def perform_user_or_organization
if delete_organization?
perform_organization(deletable.organization)
return
end
perform_user
end
def perform_organization(organization)
update_inactive(organization)
organization.members.find_each { |user| update_inactive(user) }
organization.destroy_dependent_associations
organization.destroy(associations: true)
end
def perform_user
update_inactive(deletable)
deletable.destroy_dependent_associations
deletable.destroy
end
def perform_ticket
deletable.destroy
end
def handle_exception(e)
Rails.logger.error e
preferences[:error] = "ERROR: #{e.inspect}"
self.state = 'failed'
save!
end
def delete_organization?
return false if preferences[:delete_organization].blank?
return false if preferences[:delete_organization] != 'true'
return false if !deletable.organization
return false if deletable.organization.members.count != 1
true
end
def prepare_deletion_preview_tickets
case deletable
when User
prepare_deletion_preview_user_tickets
when Ticket
prepare_deletion_preview_ticket
end
end
def prepare_deletion_preview_user_tickets
prepare_deletion_preview_owner_tickets
prepare_deletion_preview_customer_tickets
end
def prepare_deletion_preview_owner_tickets
preferences[:owner_tickets] = owner_tickets.limit(MAX_PREVIEW_TICKETS).map(&:number)
preferences[:owner_tickets_count] = owner_tickets.count
end
def prepare_deletion_preview_customer_tickets
preferences[:customer_tickets] = customer_tickets.limit(MAX_PREVIEW_TICKETS).map(&:number)
preferences[:customer_tickets_count] = customer_tickets.count
end
def prepare_deletion_preview_ticket
preferences[:ticket] = {
title: deletable.title,
}
preferences[:customer_tickets] = [deletable.number]
preferences[:customer_tickets_count] = 1
end
def prepare_deletion_preview_user
return if !deletable.is_a?(User)
preferences[:user] = {
firstname: deletable.firstname,
lastname: deletable.lastname,
email: deletable.email,
}
end
def prepare_deletion_preview_organization
return if !deletable.is_a?(User)
return if !deletable.organization
preferences[:user][:organization] = deletable.organization.name
end
def prepare_deletion_preview_anonymize
case deletable
when User
preferences[:user] = Pseudonymisation.of_hash(preferences[:user])
when Ticket
preferences[:ticket] = Pseudonymisation.of_hash(preferences[:ticket])
end
end
def owner_tickets
@owner_tickets ||= deletable.owner_tickets.reorder(id: 'DESC')
end
def customer_tickets
@customer_tickets ||= deletable.customer_tickets.reorder(id: 'DESC')
end
end