module Isomorfeus module Data module Enhancer if RUBY_ENGINE == 'opal' OBJECT_ACTION_TYPES = %w[DATA_LOAD DATA_SAVE DATA_CREATE DATA_SEARCH DATA_DESTROY] OBJECT_MERGE_TYPES = %w[DATA_SAVE DATA_CREATE] CLIENT_OBJECT_ENHANCER = proc do |action, next_enh| if OBJECT_ACTION_TYPES.include?(action[:type]) Isomorfeus.store.promise_action(action) if OBJECT_MERGE_TYPES.include?(action[:type]) action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { fields: action[:fields], revision: action[:revision] }}}} # optimistic end end next_enh.call(action) end FILE_ACTION_TYPES = %w[FILE_LOAD FILE_SAVE FILE_CREATE FILE_DESTROY] FILE_MERGE_TYPES = %w[FILE_SAVE FILE_CREATE] CLIENT_FILE_ENHANCER = proc do |action, next_enh| if FILE_ACTION_TYPES.include?(action[:type]) Isomorfeus.store.promise_action(action) if FILE_MERGE_TYPES.include?(action[:type]) action = { type: 'DATA_MERGE', data: { action[:class_name] => { action[:key] => { data_uri: action[:data_uri], revision: action[:revision] }}}} # optimistic end end next_enh.call(action) end def self.add_enhancer_to_store ::Redux::Store.add_middleware(CLIENT_OBJECT_ENHANCER) ::Redux::Store.add_middleware(CLIENT_FILE_ENHANCER) end else # RUBY_ENGINE SERVER_OBJECT_ENHANCER = proc do |action, next_enh| action_type = action[:type] begin if action_type == 'DATA_LOAD' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key) sid_s = SID.new(item_class_name, item_key).to_s loaded_data = item_class.object_accelerator.load(sid_s: sid_s) || {} revision = loaded_data.delete(:_revision) item_class.field_conditions.each do |field, options| loaded_data.delete(field) if options[:server_only] end action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision }}}} elsif action_type == 'DATA_SAVE' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) item_fields = action[:fields] item_fields.transform_keys!(&:to_sym) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, fields: item_fields }) sid_s = SID.new(item_class_name, item_key).to_s stored_revision = item_class.object_accelerator.revision(sid_s: sid_s) if stored_revision == action[:revision] revision = stored_revision + 1 instance_uuid = action[:instance_uuid] if item_class.before_save_block res = item_class.before_save_block.call(key: item_key, fields: item_fields) if res.is_a?(Hash) item_fields = res item_fields.transform_keys!(&:to_sym) convert_sids(item_class, item_fields) end end if item_class.object_accelerator.save(sid_s: sid_s, fields: item_fields, revision: revision) item_class.after_save_block.call(key: item_key, fields: item_fields) if item_class.after_save_block else instance_uuid = nil end else revision = 0 instance_uuid = nil end loaded_data = item_class.object_accelerator.load(sid_s: sid_s) revision = loaded_data.delete(:_revision) item_class.field_conditions.each do |field, options| loaded_data.delete(field) if options[:server_only] end action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}} elsif action_type == 'DATA_CREATE' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) item_fields = action[:fields] item_fields.transform_keys!(&:to_sym) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, fields: item_fields }) sid_s = SID.new(item_class_name, item_key).to_s stored_revision = item_class.object_accelerator.revision(sid_s: sid_s) if stored_revision.nil? revision = 1 instance_uuid = action[:instance_uuid] if item_class.before_create_block res = item_class.before_create_block.call(key: item_key, fields: item_fields) if res.is_a?(Hash) item_fields = res item_fields.transform_keys!(&:to_sym) convert_sids(item_class, item_fields) end end if item_class.object_accelerator.create(sid_s: sid_s, fields: item_fields) item_class.after_create_block.call(key: item_key, fields: item_fields) if item_class.after_create_block else stored_revision = 0 instance_uuid = nil end else revision = stored_revision instance_uuid = nil end loaded_data = item_class.object_accelerator.load(sid_s: sid_s) revision = loaded_data.delete(:_revision) item_class.field_conditions.each do |field, options| loaded_data.delete(field) if options[:server_only] end action = { type: 'DATA_ADD', data: { item_class_name => { item_key => { fields: loaded_data, revision: revision, instance_uuid: instance_uuid }}}} elsif action_type == 'DATA_SEARCH' item_class_name = action[:class_name] item_class = get_verified_class(item_class_name) query = action[:query].to_sym params = action[:params] raise "Invalid params #{params}!" unless params.nil? || params.is_a?(Hash) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :search, { query: query, params: params }) result = [] query_string = item_class.queries.dig(query, :query_string) raise "Invalid query '#{query}'!" unless query_string options = item_class.queries[query][:options] || {} oa = item_class.object_accelerator full_query = if params.is_a?(Hash) if options[:escape_params] escaped_params = params.to_h do |k,v| [k, oa.escape(v)] end query_string % escaped_params else query_string % params end else query_string end oa.search_each(full_query, options) do |id| sid_s = oa.load_sid_s(id: id) result << sid_s if sid_s end action = { type: 'DATA_MERGE', data: { queries: { item_class_name => { query => { Oj.dump(params, mode: :strict) => { result_time: Time.now.httpdate, result: result }}}}}} elsif action_type == 'DATA_DESTROY' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key) item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block item_class.object_accelerator.destroy(sid_s: SID.new(item_class_name, item_key).to_s) item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }} end rescue => e STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}" # do nothing end next_enh.call(action) end SERVER_FILE_ENHANCER = proc do |action, next_enh| action_type = action[:type] begin if action_type == 'FILE_LOAD' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :load, key: item_key) sid = SID.new(item_class_name, item_key) data, meta = item_class.file_accelerator.load(sid: sid) item_hash = {} if data content_type = Marcel::MimeType.for(data) data_uri = item_class.format_data_uri(content_type, data) revision = meta[:revision] item_hash = { data_uri: data_uri, revision: revision } end action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => item_hash }}} elsif action_type == 'FILE_SAVE' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) data_uri = action[:data_uri] authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :save, { key: item_key, data_uri: data_uri }) revision = action[:revision] sid = SID.new(item_class_name, item_key) meta = item_class.file_accelerator.meta(sid: sid) stored_revision = meta[:revision] if stored_revision == action[:revision] revision = stored_revision + 1 instance_uuid = action[:instance_uuid] if item_class.before_save_block res = item_class.before_save_block.call(key: item_key, data_uri: data_uri) data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri) end if item_class.file_accelerator.save(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision }) item_class.after_save_block.call(key: item_key, data_uri: data_uri) if item_class.after_save_block else instance_uuid = nil end else revision = 0 instance_uuid = nil end action = { type: 'DATA_MERGE', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}} elsif action_type == 'FILE_CREATE' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) data_uri = action[:data_uri] authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :create, { key: item_key, data_uri: data_uri }) sid = SID.new(item_class_name, item_key) meta = item_class.file_accelerator.meta(sid: sid) stored_revision = meta ? meta[:revision] : nil if stored_revision.nil? revision = 1 instance_uuid = action[:instance_uuid] if item_class.before_create_block res = item_class.before_create_block.call(key: item_key, data_uri: data_uri) data_uri = res[:data_uri] if res.is_a?(Hash) && res.key?(:data_uri) end if item_class.file_accelerator.create(sid: sid, data: URI::Data.new(data_uri).data, meta: { revision: revision }) item_class.after_create_block.call(key: item_key, data_uri: data_uri) if item_class.after_create_block else stored_revision = 0 instance_uuid = nil end else revision = stored_revision instance_uuid = nil end action = { type: 'DATA_ADD', data: { item_class_name => { item_key => { data_uri: data_uri, revision: revision, instance_uuid: instance_uuid }}}} elsif action_type == 'FILE_DESTROY' item_class_name = action[:class_name] item_key = action[:key] item_class = get_verified_class(item_class_name) authorization_error!(action) unless Isomorfeus.current_user.authorized?(item_class, :destroy, key: item_key) item_class.before_destroy_block.call(key: item_key) if item_class.before_destroy_block item_class.file_accelerator.destroy(sid: SID.new(item_class_name, item_key)) item_class.after_destroy_block.call(key: item_key) if item_class.after_destroy_block action = { type: 'DATA_SET', class_name: item_class_name, key: item_key, data: { revision: -1 }} end rescue => e STDERR.puts "#{e.message}\n#{e.backtrace&.join("\n")}" # do nothing end next_enh.call(action) end class << self def convert_sids(item_class, item_fields) item_class.field_types.each do |field, type| if type == :object field_s = field.to_s v = item_fields[field_s] if v.is_a?(SID) item_fields[field_s] = v.to_s elsif v.is_a?(Array) item_fields[field_s].map! do |o| o.is_a?(SID) ? o.to_s : o end end end end end def get_verified_class(item_class_name) raise "Invalid data class!" unless Isomorfeus.valid_data_class_name?(item_class_name) item_class = Isomorfeus.cached_data_class(item_class_name) end def authorization_error!(action) raise "Not authorized!#{" (action: #{action})" if Isomorfeus.development?}" end def add_enhancer_to_store ::Redux::Store.add_middleware(SERVER_OBJECT_ENHANCER) ::Redux::Store.add_middleware(SERVER_FILE_ENHANCER) end end end # RUBY_ENGINE end end end