diff --git a/app/services/user_data_deletion.rb b/app/services/user_data_deletion.rb new file mode 100644 index 000000000..b1d56dc88 --- /dev/null +++ b/app/services/user_data_deletion.rb @@ -0,0 +1,151 @@ +class UserDataDeletion + def self.delete_team_data(team) + ActiveRecord::Base.logger = Logger.new(STDOUT) + Step.skip_callback(:destroy, :after, :cascade_after_destroy) + team.transaction do + # Destroy tiny_mce_assets + if team.tiny_mce_assets.present? + team.tiny_mce_assets.each do |tiny_mce_asset| + paperclip_file_destroy(tiny_mce_asset) if tiny_mce_asset.image.exists? + end + end + team.repositories.each do |repository| + repository.repository_rows.find_each do |repository_row| + # Destroy repository_cell assets + repository_row + .repository_cells + .where(value_type: 'RepositoryAssetValue').each do |repository_cell| + next unless repository_cell.value.asset.file.exists? + paperclip_file_destroy(repository_cell.value.asset) + end + end + repository.destroy + end + team.projects.each do |project| + project.reports.destroy_all + project.activities.destroy_all + project.experiments.each do |experiment| + experiment.my_modules.each do |my_module| + # Destroy result assets + my_module.results.each do |result| + result.result_table.delete if result.result_table.present? + result.table.delete if result.table.present? + next unless result.asset && result.asset.file.exists? + paperclip_file_destroy(result.asset) + end + my_module.activities.destroy_all + my_module.inputs.destroy_all + my_module.outputs.destroy_all + my_module.results.destroy_all + my_module.my_module_tags.destroy_all + my_module.task_comments.destroy_all + my_module.my_module_repository_rows.destroy_all + my_module.user_my_modules.destroy_all + my_module.report_elements.destroy_all + my_module.sample_my_modules.destroy_all + my_module.protocols.each { |p| p.update_attributes(parent_id: nil) } + my_module.protocols.each do |protocol| + destroy_protocol(protocol) + end + my_module.delete + end + # Destroy workflow image + if experiment.workflowimg.exists? + experiment.workflowimg.clear(:original) + end + experiment.activities.destroy_all + experiment.report_elements.destroy_all + experiment.my_module_groups.delete_all + experiment.delete + end + project.user_projects.destroy_all + project.tags.destroy_all + project.project_comments.destroy_all + project.report_elements.destroy_all + + project.delete + end + team.protocols.each { |p| p.update_attributes(parent_id: nil) } + team.protocols.where(my_module: nil).each do |protocol| + destroy_protocol(protocol) + end + team.samples.destroy_all + team.samples_tables.destroy_all + team.sample_groups.destroy_all + team.sample_types.destroy_all + team.custom_fields.destroy_all + team.protocol_keywords.destroy_all + team.user_teams.delete_all + User.where(current_team_id: team).each do |user| + user.update(current_team_id: nil) + end + team.reports.destroy_all + team.destroy! + # raise ActiveRecord::Rollback + end + Step.set_callback(:destroy, :after, :cascade_after_destroy) + end + + def self.destroy_protocol(protocol) + protocol.steps.each do |step| + # Destroy step assets + if step.assets.present? + step.assets.each do |asset| + next unless asset.file.present? + paperclip_file_destroy(asset) + end + end + # Destroy step + step.tables.destroy_all + step.step_tables.delete_all + step.report_elements.destroy_all + step.step_comments.destroy_all + step.step_assets.destroy_all + step.checklists.destroy_all + step.assets.destroy_all + step.tiny_mce_assets.destroy_all + step.delete + end + # Destroy protocol + protocol.protocol_protocol_keywords.destroy_all + protocol.protocol_keywords.destroy_all + protocol.delete + end + + def self.destroy_notifications(user) + # Find all notifications where user is the only reference + # on the notification, and destroy all such notifications + # (user_notifications are destroyed when notification is + # destroyed). We try to do this efficiently (hence in_groups_of). + nids_all = user.notifications.pluck(:id) + nids_all.in_groups_of(1000, false) do |nids| + Notification + .where(id: nids) + .joins(:user_notifications) + .group('notifications.id') + .having('count(notification_id) <= 1') + .destroy_all + end + # Now, simply destroy all user notification relations left + user.user_notifications.destroy_all + end + + # Workaround for Paperclip preserve_files option, to delete files anyway; + # if you call #clear with a list of styles to delete, + # it'll call #queue_some_for_delete, which doesn't check :preserve_files. + def self.paperclip_file_destroy(asset) + if asset.class.name == 'TinyMceAsset' + all_styles = asset.image.styles.keys.map do |key| + asset.image.styles[key].name + end << :original + asset.image.clear(*all_styles) + else + all_styles = asset.file.styles.keys.map do |key| + asset.file.styles[key].name + end << :original + asset.file.clear(*all_styles) + end + asset.save! + asset.destroy! + end +end diff --git a/lib/tasks/data.rake b/lib/tasks/data.rake index 783b25d9a..6140447dc 100644 --- a/lib/tasks/data.rake +++ b/lib/tasks/data.rake @@ -96,4 +96,14 @@ namespace :data do ) TeamImporter.new.import_from_dir(args[:dir_path]) end + + desc 'Delete team and all data inside the team' + task :team_delete, [:team_id] => [:environment] do |_, args| + Rails.logger.info( + "Deleting team with ID:#{args[:team_id]} and all data inside the team" + ) + team = Team.find_by_id(args[:team_id]) + raise StandardError, 'Can not load team' unless team + UserDataDeletion.delete_team_data(team) if team + end end