cartodb-4.42/app/services/carto/visualizations_export_persistence_service.rb
2024-04-06 05:25:13 +00:00

172 lines
6.5 KiB
Ruby

require_dependency 'carto/uuidhelper'
require_dependency 'carto/query_rewriter'
module Carto
# Export/import is versioned, but importing should generate a model tree that can be persisted by this class
class VisualizationsExportPersistenceService
include Carto::UUIDHelper
include Carto::QueryRewriter
# `full_restore = true` means keeping the visualization id and permission. Its intended use is to restore an exact
# copy of a visualization (e.g: user migration). When it's `false` it will skip restoring ids and permissions. This
# is the default, and how it's used by the Import API to allow to import a visualization into a different user
def save_import(user, visualization, renamed_tables: {}, full_restore: false)
old_username = visualization.user.username if visualization.user
apply_user_limits(user, visualization)
ActiveRecord::Base.transaction do
visualization.id = random_uuid unless visualization.id && full_restore
visualization.user = user
ensure_unique_name(user, visualization)
visualization.layers.each { |layer| layer.fix_layer_user_information(old_username, user, renamed_tables) }
visualization.analyses.each do |analysis|
analysis.analysis_node.fix_analysis_node_queries(old_username, user, renamed_tables)
end
saved_acl = visualization.permission.access_control_list if full_restore
visualization.permission = Carto::Permission.new(owner: user, owner_username: user.username)
map = visualization.map
map.user = user if map
visualization.analyses.each do |analysis|
analysis.user_id = user.id
end
sync = visualization.synchronization
if sync
sync.id = random_uuid unless sync.id && full_restore
sync.user = user
sync.log.user_id = user.id if sync.log
end
user_table = visualization.map.user_table if map
if user_table
user_table.user = user
user_table.service.register_table_only = true
raise 'Cannot import a dataset without physical table' unless user_table.service.real_table_exists?
data_import = user_table.data_import
if data_import
existing_data_import = Carto::DataImport.where(id: data_import.id).first
user_table.data_import = existing_data_import if existing_data_import
data_import.synchronization_id = sync.id if sync
data_import.user_id = user.id
end
end
unless full_restore
visualization.mapcaps.clear
visualization.created_at = DateTime.now
visualization.updated_at = DateTime.now
visualization.locked = false
end
unless visualization.save
error_message = "Errors saving imported visualization: #{visualization.errors.full_messages}"
raise Carto::UnauthorizedError.new(error_message) if visualization.errors.include?(:privacy)
raise error_message
end
# Save permissions after visualization, in order to be able to regenerate shared_entities
if saved_acl
visualization.permission.access_control_list = saved_acl
visualization.permission.save!
end
visualization.layers.map do |layer|
# Flag needed because `.changed?` won't realize about options hash changes
changed = false
if layer.options.has_key?(:id)
layer.options[:id] = layer.id
changed = true
end
if layer.options.has_key?(:stat_tag)
layer.options[:stat_tag] = visualization.id
changed = true
end
layer.save if changed
end
if map
map.data_layers.each(&:register_table_dependencies)
new_user_layers = map.base_layers.select(&:custom?).select { |l| !contains_equivalent_base_layer?(user.layers, l) }
new_user_layers.map(&:dup).map { |l| user.layers << l }
data_import = map.user_table.try(:data_import)
if data_import
data_import.table_id = map.user_table.id
data_import.save!
data_import.external_data_imports.each do |edi|
edi.synchronization_id = sync.id if sync
edi.save!
end
end
end
end
visualization.save!
visualization
end
private
def contains_equivalent_base_layer?(layers, layer)
layers.any? { |l| equivalent_base_layer?(l, layer) }
end
def equivalent_base_layer?(layer_a, layer_b)
layer_a.kind == 'tiled' && layer_a.kind == layer_b.kind && layer_a.options == layer_b.options
end
def ensure_unique_name(user, visualization)
existing_names = Carto::Visualization.uniq
.where(user_id: user.id)
.where("name ~ ?", "#{Regexp.escape(visualization.name)}( Import)?( \d*)?$")
.where(type: visualization.type)
.pluck(:name)
if existing_names.include?(visualization.name)
raise 'Cannot rename a dataset during import' if visualization.canonical?
import_name = "#{visualization.name} Import"
i = 1
while existing_names.include?(import_name)
import_name = "#{visualization.name} Import #{i += 1}"
end
visualization.name = import_name
end
end
def apply_user_limits(user, visualization)
can_be_private = visualization.derived? ? user.private_maps_enabled : user.private_tables_enabled
unless can_be_private
visualization.privacy = Carto::Visualization::PRIVACY_PUBLIC
visualization.user_table.privacy = Carto::UserTable::PRIVACY_PUBLIC if visualization.canonical?
end
# If password is not exported we must fallback to private
if visualization.password_protected? && !visualization.has_password?
visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
end
layers = []
data_layer_count = 0
if visualization.map
visualization.map.layers.each do |layer|
if layer.data_layer?
if data_layer_count < user.max_layers
layers.push(layer)
data_layer_count += 1
end
else
layers.push(layer)
end
end
visualization.map.layers.clear
visualization.map.layers_maps.clear
layers.each { |l| visualization.map.layers << l }
end
end
end
end