# frozen_string_literal: true class MigrationService attr_reader :archive_path, :new_user_name, :opts delegate :errors, :warnings, to: :archive_validator def initialize(archive_path, new_user_name, opts={}) @archive_path = archive_path @new_user_name = new_user_name @opts = opts end def validate return unless archive_file_exists? archive_validator.validate raise ArchiveValidationFailed, errors.join("\n") if errors.any? raise MigrationAlreadyExists if AccountMigration.where(old_person: old_person).any? raise SelfMigrationNotAllowed if self_import? end def perform! find_or_create_user import_archive run_migration ensure remove_intermediate_file end def self_import? source_diaspora_id = archive_validator.archive_author_diaspora_id target_diaspora_id = "#{new_user_name}#{User.diaspora_id_host}" source_diaspora_id == target_diaspora_id end # when old person can't be resolved we still import data but we don't create&perform AccountMigration instance def only_import? old_person.nil? end private def find_or_create_user archive_importer.find_or_create_user(username: new_user_name, password: SecureRandom.hex) end def import_archive archive_importer.import(opts) end def run_migration account_migration.save account_migration.perform! end def account_migration @account_migration ||= AccountMigration.new( old_person: old_person, new_person: archive_importer.user.person, old_private_key: archive_importer.serialized_private_key, old_person_diaspora_id: archive_importer.archive_author_diaspora_id, archive_contacts: archive_importer.contacts ) end def old_person @old_person ||= Person.by_account_identifier(archive_validator.archive_author_diaspora_id) end def archive_importer @archive_importer ||= ArchiveImporter.new(archive_validator.archive_hash) end def archive_validator @archive_validator ||= ArchiveValidator.new(archive_file) end def archive_file return uncompressed_zip if zip_file? return uncompressed_gz if gzip_file? File.new(archive_path, "r") end def archive_file_exists? File.exist?(archive_path) end def zip_file? filetype = MIME::Types.type_for(archive_path).first.content_type filetype.eql?("application/zip") end def gzip_file? filetype = MIME::Types.type_for(archive_path).first.content_type filetype.eql?("application/gzip") end def uncompressed_gz target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST unzipped_archive_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file Zlib::GzipReader.open(archive_path) {|gz| File.open(unzipped_archive_file, "w") do |output_stream| IO.copy_stream(gz, output_stream) end @intermediate_file = unzipped_archive_file } File.new(unzipped_archive_file, "r") end def uncompressed_zip target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST zip_stream = Zip::InputStream.open(archive_path) while entry = zip_stream.get_next_entry # rubocop:disable Lint/AssignmentInCondition next unless entry.name.end_with?(".json") target_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file entry.extract(target_file) @intermediate_file = target_file return File.new(target_file, "r") end end def remove_intermediate_file # If an unzip operation created an unzipped file, remove it after migration return if @intermediate_file.nil? return unless File.exist?(@intermediate_file) File.delete(@intermediate_file) end class ArchiveValidationFailed < RuntimeError end class MigrationAlreadyExists < RuntimeError end class SelfMigrationNotAllowed < RuntimeError end end