Resolve import conflicts

This commit is contained in:
Stanko K.R.
2026-01-29 11:35:46 +01:00
parent 6044303078
commit 63f6be4ef5
6 changed files with 100 additions and 1 deletions
@@ -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
+2 -1
View File
@@ -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
+3
View File
@@ -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)
+50
View File
@@ -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