dawarich/app/services/users/import_data/imports.rb
2025-10-01 22:05:51 +02:00

153 lines
4.9 KiB
Ruby

# frozen_string_literal: true
class Users::ImportData::Imports
def initialize(user, imports_data, files_directory)
@user = user
@imports_data = imports_data
@files_directory = files_directory
end
def call
return [0, 0] unless imports_data.is_a?(Array)
Rails.logger.info "Importing #{imports_data.size} imports for user: #{user.email}"
imports_created = 0
files_restored = 0
# Preload existing imports to avoid N+1 queries
@existing_imports_cache = load_existing_imports
imports_data.each do |import_data|
next unless import_data.is_a?(Hash)
# Normalize created_at for consistent cache key lookup
created_at_normalized = normalize_created_at(import_data['created_at'])
cache_key = import_cache_key(import_data['name'], import_data['source'], created_at_normalized)
existing_import = @existing_imports_cache[cache_key]
if existing_import
Rails.logger.debug "Import already exists: #{import_data['name']}"
next
end
import_record = create_import_record(import_data)
next unless import_record # Skip if creation failed
imports_created += 1
files_restored += 1 if import_data['file_name'] && restore_import_file(import_record, import_data)
end
Rails.logger.info "Imports import completed. Created: #{imports_created}, Files restored: #{files_restored}"
[imports_created, files_restored]
end
private
attr_reader :user, :imports_data, :files_directory
def load_existing_imports
# Extract import identifiers from imports_data and normalize created_at
import_keys = imports_data.select { |id| id.is_a?(Hash) && id['name'].present? && id['source'].present? }
.map do |id|
# Normalize created_at to string for consistent comparison
created_at_normalized = normalize_created_at(id['created_at'])
{ name: id['name'], source: id['source'], created_at: created_at_normalized }
end
return {} if import_keys.empty?
# Build a hash for quick lookup
cache = {}
# Build OR conditions using Arel to fetch all matching imports in a single query
arel_table = Import.arel_table
conditions = import_keys.map do |key|
condition = arel_table[:user_id].eq(user.id)
.and(arel_table[:name].eq(key[:name]))
.and(arel_table[:source].eq(key[:source]))
# Handle created_at being nil
if key[:created_at].nil?
condition.and(arel_table[:created_at].eq(nil))
else
# Parse the string back to Time for querying
condition.and(arel_table[:created_at].eq(Time.zone.parse(key[:created_at])))
end
end.reduce { |result, condition| result.or(condition) }
# Fetch all matching imports in a single query
Import.where(conditions).find_each do |import|
# Normalize created_at from database for cache key
created_at_normalized = normalize_created_at(import.created_at)
cache_key = import_cache_key(import.name, import.source, created_at_normalized)
cache[cache_key] = import
end
cache
end
def normalize_created_at(created_at)
return nil if created_at.nil?
# Convert to string in ISO8601 format for consistent comparison
time = created_at.is_a?(String) ? Time.zone.parse(created_at) : created_at
time&.iso8601
end
def import_cache_key(name, source, created_at)
"#{name}_#{source}_#{created_at}"
end
def create_import_record(import_data)
import_attributes = prepare_import_attributes(import_data)
begin
import_record = user.imports.build(import_attributes)
import_record.skip_background_processing = true
import_record.save!
Rails.logger.debug "Created import: #{import_record.name}"
import_record
rescue ActiveRecord::RecordInvalid => e
Rails.logger.error "Failed to create import: #{e.message}"
nil
end
end
def prepare_import_attributes(import_data)
import_data.except(
'file_name',
'original_filename',
'file_size',
'content_type',
'file_error',
'updated_at'
).merge(user: user)
end
def restore_import_file(import_record, import_data)
file_path = files_directory.join(import_data['file_name'])
unless File.exist?(file_path)
Rails.logger.warn "Import file not found: #{import_data['file_name']}"
return false
end
begin
import_record.file.attach(
io: File.open(file_path),
filename: import_data['original_filename'] || import_data['file_name'],
content_type: import_data['content_type'] || 'application/octet-stream'
)
Rails.logger.debug "Restored file for import: #{import_record.name}"
true
rescue StandardError => e
ExceptionReporter.call(e, 'Import file restoration failed')
false
end
end
end