Resolve import conflicts
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
class Account::DataTransfer::EntropyRecordSet < Account::DataTransfer::RecordSet
|
||||
def initialize(account)
|
||||
super(account: account, model: Entropy)
|
||||
end
|
||||
|
||||
private
|
||||
def import_batch(files)
|
||||
batch_data = files.map do |file|
|
||||
data = load(file)
|
||||
data.slice(*attributes).merge("account_id" => account.id)
|
||||
end
|
||||
|
||||
container_keys = batch_data.map { |d| [ d["container_type"], d["container_id"] ] }
|
||||
existing_containers = Entropy
|
||||
.where(account_id: account.id)
|
||||
.where(container_type: container_keys.map(&:first), container_id: container_keys.map(&:last))
|
||||
.pluck(:container_type, :container_id)
|
||||
.to_set
|
||||
|
||||
to_update, to_insert = batch_data.partition do |data|
|
||||
existing_containers.include?([ data["container_type"], data["container_id"] ])
|
||||
end
|
||||
|
||||
to_update.each do |data|
|
||||
Entropy
|
||||
.find_by(account_id: account.id, container_type: data["container_type"], container_id: data["container_id"])
|
||||
.update!(data.slice("auto_postpone_period"))
|
||||
end
|
||||
|
||||
Entropy.insert_all!(to_insert) if to_insert.any?
|
||||
end
|
||||
end
|
||||
@@ -20,10 +20,11 @@ class Account::DataTransfer::Manifest
|
||||
[
|
||||
Account::DataTransfer::AccountRecordSet.new(account),
|
||||
Account::DataTransfer::UserRecordSet.new(account),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::User::Settings),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Tag),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Board),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Column),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Entropy),
|
||||
Account::DataTransfer::EntropyRecordSet.new(account),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Board::Publication),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Webhook),
|
||||
Account::DataTransfer::RecordSet.new(account: account, model: ::Access),
|
||||
|
||||
@@ -87,6 +87,16 @@ class Account::DataTransfer::RecordSet
|
||||
if model.exists?(id: data["id"])
|
||||
raise IntegrityError, "#{model} record with ID #{data['id']} already exists"
|
||||
end
|
||||
|
||||
record = model.new(data.slice(*attributes).merge("account_id" => account.id))
|
||||
belongs_to_associations = model.reflect_on_all_associations(:belongs_to).map { |a| a.name.to_s }
|
||||
|
||||
record.validate
|
||||
errors = record.errors.reject { |e| belongs_to_associations.include?(e.attribute.to_s) }
|
||||
|
||||
if errors.any?
|
||||
raise IntegrityError, "Validation failed for #{model} record ID #{data['id']}: #{errors.map(&:full_message).join(', ')}"
|
||||
end
|
||||
end
|
||||
|
||||
def load(file_path)
|
||||
|
||||
@@ -40,6 +40,9 @@ class Account::DataTransfer::UserRecordSet < Account::DataTransfer::RecordSet
|
||||
)
|
||||
end
|
||||
|
||||
conflicting_identity_ids = batch_data.pluck("identity_id").compact
|
||||
account.users.where(identity_id: conflicting_identity_ids).destroy_all
|
||||
|
||||
User.insert_all!(batch_data)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
class Account::Import < ApplicationRecord
|
||||
broadcasts_refreshes
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :identity
|
||||
|
||||
@@ -7,6 +9,7 @@ class Account::Import < ApplicationRecord
|
||||
enum :status, %w[ pending processing completed failed ].index_by(&:itself), default: :pending
|
||||
|
||||
def process_later
|
||||
ImportAccountDataJob.perform_later(self)
|
||||
end
|
||||
|
||||
def process(start: nil, callback: nil)
|
||||
|
||||
@@ -93,6 +93,45 @@ class Account::ImportTest < ActiveSupport::TestCase
|
||||
assert import.completed?
|
||||
end
|
||||
|
||||
test "export and import round-trip preserves account data" do
|
||||
source_account = accounts("37s")
|
||||
exporter = users(:david)
|
||||
identity = exporter.identity
|
||||
|
||||
# Capture original counts
|
||||
original_counts = capture_counts(source_account)
|
||||
|
||||
# Export
|
||||
export = Account::Export.create!(account: source_account, user: exporter)
|
||||
export.build
|
||||
assert export.completed?
|
||||
|
||||
# Save export file before deleting account
|
||||
export_tempfile = Tempfile.new([ "export", ".zip" ])
|
||||
export.file.open { |f| FileUtils.cp(f.path, export_tempfile.path) }
|
||||
|
||||
# Delete source account
|
||||
source_account.destroy!
|
||||
|
||||
# Create target account
|
||||
target_account = Account.create!(name: "Import Target")
|
||||
target_account.users.create!(role: :system, name: "System")
|
||||
target_account.users.create!(role: :owner, name: "Owner", identity: identity, verified_at: Time.current)
|
||||
|
||||
# Import
|
||||
import = Account::Import.create!(identity: identity, account: target_account)
|
||||
Current.set(account: target_account) do
|
||||
import.file.attach(io: File.open(export_tempfile.path), filename: "export.zip", content_type: "application/zip")
|
||||
end
|
||||
import.process
|
||||
assert import.completed?
|
||||
|
||||
assert_equal original_counts.except(:users), capture_counts(target_account).except(:users)
|
||||
ensure
|
||||
export_tempfile&.close
|
||||
export_tempfile&.unlink
|
||||
end
|
||||
|
||||
private
|
||||
def create_target_account
|
||||
account = Account.create!(name: "Import Target")
|
||||
@@ -202,4 +241,15 @@ class Account::ImportTest < ActiveSupport::TestCase
|
||||
end
|
||||
File.open(tempfile.path, "rb")
|
||||
end
|
||||
|
||||
def capture_counts(account)
|
||||
{
|
||||
boards: account.boards.count,
|
||||
columns: Column.joins(:board).where(boards: { account_id: account.id }).count,
|
||||
cards: account.cards.count,
|
||||
comments: Comment.joins(:card).where(cards: { account_id: account.id }).count,
|
||||
tags: account.tags.count,
|
||||
users: account.users.count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user