isomorfeus-data 2.5.4 → 22.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Isomorfeus
4
+ module Data
5
+ class RackMiddleware
6
+ NOT_FOUND = [404, {}, ['Not found!']].freeze
7
+ NOT_AUTHORIZED = [401, {}, ['Not authorized!']].freeze
8
+
9
+ FILES_REGEXP = /\A\/files\/([A-Za-z\:]+)\/([A-Za-z0-9\-\/_]+)\z/
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ unless env['isomorfeus_store_initialized']
17
+ Isomorfeus.init_store
18
+ Isomorfeus.store.clear!
19
+ env['isomorfeus_store_initialized'] = true
20
+ end
21
+
22
+ path_info = env['PATH_INFO']
23
+
24
+ if path_info.start_with?(Isomorfeus.api_files_path)
25
+ begin
26
+ m = FILES_REGEXP.match(path_info)
27
+ return NOT_FOUND unless m
28
+ type_class_name = m[1]
29
+ key = m[2]
30
+
31
+ return NOT_FOUND unless Isomorfeus.valid_data_class_name?(type_class_name)
32
+
33
+ type_class = Isomorfeus.cached_data_class(type_class_name)
34
+ return NOT_FOUND unless type_class.ancestors.include?(LucidFile)
35
+
36
+ env['isomorfeus_user_session'] = Isomorfeus::Transport::RackMiddleware.user_from_env(env) unless env.key?('isomorfeus_user_session')
37
+ user, session_id = env['isomorfeus_user_session']
38
+
39
+ authorized = false
40
+ begin
41
+ Thread.current[:isomorfeus_user] = user
42
+ Thread.current[:isomorfeus_session_id] = session_id
43
+ authorized = true if user.authorized?(type_class, :load, key: key)
44
+ ensure
45
+ Thread.current[:isomorfeus_user] = nil
46
+ Thread.current[:isomorfeus_session_id] = nil
47
+ end
48
+ return NOT_AUTHORIZED unless authorized
49
+ data = type_class.file_accelerator.load_data(sid: SID.new(type_class_name, key))
50
+
51
+ return NOT_FOUND unless data
52
+
53
+ mime_type = Marcel::MimeType.for(data)
54
+ headers = { Rack::CONTENT_TYPE => mime_type }
55
+
56
+ return [200, headers, [data]]
57
+ rescue
58
+ return NOT_FOUND
59
+ end
60
+ else
61
+ @app.call(env)
62
+ end
63
+ end
64
+
65
+ def check_and_prepare_path(class_name:, key:)
66
+ # '..', '.' and other critical characters are already excluded by the FILES_REGEXP
67
+ elements = key.split('/')
68
+ Isomorfeus.raise_error(message: 'Invalid key (contains more than 2 slashes "/")') if elements.size > 3
69
+ ::File.expand_path(::File.join(Isomorfeus.files_path, class_name, *elements))
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,30 +1,60 @@
1
1
  module Isomorfeus
2
2
  module Data
3
3
  module Reducer
4
- def self.add_reducer_to_store
5
- data_reducer = Redux.create_reducer do |prev_state, action|
4
+ if RUBY_ENGINE == 'opal'
5
+ CLIENT_REDUCER = proc do |prev_state, action|
6
6
  action_type = action[:type]
7
- if action_type.JS.startsWith('DATA_')
8
- case action_type
9
- when 'DATA_STATE'
10
- if action.key?(:set_state)
11
- action[:set_state]
12
- else
13
- prev_state
14
- end
15
- when 'DATA_LOAD'
16
- prev_state.deep_merge(action[:data])
7
+ if action_type == 'DATA_STATE' && action.key?(:set_state)
8
+ action[:set_state]
9
+ elsif action_type == 'REDUX_MERGE'
10
+ red_state = action.dig(:state, :data_state)
11
+ if red_state
12
+ new_state = prev_state ? prev_state.deep_dup : {}
13
+ new_state.deep_merge!(red_state)
17
14
  else
18
- prev_state
15
+ prev_state || {}
19
16
  end
17
+ elsif action_type == 'DATA_MERGE'
18
+ new_state = prev_state ? prev_state.deep_dup : {}
19
+ new_state.deep_merge!(action[:data])
20
+ new_state
20
21
  else
21
- prev_state.nil? ? {} : prev_state
22
+ prev_state || {}
22
23
  end
23
24
  end
24
25
 
25
- Redux::Store.preloaded_state_merge!(data_state: {})
26
- Redux::Store.add_reducer(data_state: data_reducer)
27
- end
26
+ def self.add_reducer_to_store
27
+ ::Redux::Store.preloaded_state_merge!(data_state: {})
28
+ ::Redux::Store.add_reducers(data_state: CLIENT_REDUCER)
29
+ end
30
+ else # RUBY_ENGINE
31
+ SERVER_REDUCER = proc do |prev_state, action|
32
+ action_type = action[:type]
33
+ if action_type == 'DATA_STATE' && action.key?(:set_state)
34
+ action[:set_state]
35
+ elsif action_type == 'DATA_MERGE'
36
+ new_state = prev_state ? prev_state.deep_dup : {}
37
+ new_state.deep_merge!(action[:data])
38
+ new_state
39
+ elsif action_type == 'DATA_SET'
40
+ new_state = prev_state ? prev_state.deep_dup : {}
41
+ if !new_state[action[:class_name]]
42
+ new_state[action[:class_name]] = {}
43
+ else
44
+ new_state[action[:class_name]].delete(action[:key])
45
+ end
46
+ new_state[action[:class_name]][action[:key]] = action[:data]
47
+ new_state
48
+ else
49
+ prev_state || {}
50
+ end
51
+ end
52
+
53
+ def self.add_reducer_to_store
54
+ ::Redux::Store.preloaded_state_merge!(data_state: {})
55
+ ::Redux::Store.add_reducers(data_state: SERVER_REDUCER)
56
+ end
57
+ end # RUBY_ENGINE
28
58
  end
29
59
  end
30
60
  end
@@ -0,0 +1,34 @@
1
+ class SID
2
+ SID_S_RE = /\ASID__([a-zA-Z0-9:]+)__([\w\/\-]+)__\z/ # must use this way for compatiblity
3
+
4
+ class << self
5
+ def from_s(sid_s)
6
+ m = SID_S_RE.match(sid_s)
7
+ self.new(m[1], m[2]) if m
8
+ end
9
+
10
+ def is?(string)
11
+ SID_S_RE.match?(string)
12
+ end
13
+ end
14
+
15
+ attr_reader :class_name
16
+ attr_reader :key
17
+
18
+ if RUBY_ENGINE == 'opal'
19
+ def initialize(class_name, key)
20
+ @class_name = class_name
21
+ @key = key
22
+ end
23
+ else
24
+ def initialize(class_name, key)
25
+ raise "Invalid class name '#{class_name}!" unless Isomorfeus.valid_data_class_name?(class_name)
26
+ @class_name = class_name
27
+ @key = key
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ "SID__#{@class_name}__#{@key}__"
33
+ end
34
+ end
@@ -1,5 +1,5 @@
1
1
  module Isomorfeus
2
2
  module Data
3
- VERSION = '2.5.4'
3
+ VERSION = '22.9.0.rc1'
4
4
  end
5
5
  end
@@ -1,50 +1,45 @@
1
- require 'base64'
2
- require 'stringio'
3
- if RUBY_ENGINE == 'opal'
4
- require 'iso_uri'
5
- end
6
- require 'data_uri'
7
- require 'securerandom'
8
- require 'isomorfeus-policy'
9
1
  require 'isomorfeus-transport'
2
+ require 'isomorfeus-redux'
10
3
  require 'isomorfeus-i18n'
4
+ require 'isomorfeus-preact'
5
+ require 'isomorfeus-policy'
11
6
  require 'isomorfeus/data/config'
12
- require 'isomorfeus/data/field_support'
13
- require 'isomorfeus/data/generic_class_api'
14
- require 'isomorfeus/data/generic_instance_api'
7
+ require 'isomorfeus/data/sid'
15
8
 
16
- if RUBY_ENGINE == 'opal'
17
- require 'isomorfeus/data/reducer'
18
- Isomorfeus::Data::Reducer.add_reducer_to_store
19
- Isomorfeus.zeitwerk.push_dir('isomorfeus_data')
20
- require_tree 'isomorfeus_data', autoload: true
21
- Isomorfeus.zeitwerk.push_dir('data')
22
- else
9
+ if RUBY_ENGINE != 'opal'
23
10
  require 'fileutils'
24
11
  require 'uri'
25
12
  require 'oj'
26
13
  require 'active_support'
27
14
  require 'active_support/core_ext/hash'
28
-
15
+ require 'marcel'
29
16
  require 'isomorfeus-ferret'
30
17
  require 'isomorfeus/data/object_accelerator'
18
+ require 'isomorfeus/data/file_accelerator'
19
+ end
20
+
21
+ require 'lucid_data_generic'
22
+ require 'lucid_object'
23
+ require 'lucid_file'
24
+
25
+ require 'isomorfeus/data/reducer'
26
+ Isomorfeus::Data::Reducer.add_reducer_to_store
31
27
 
32
- require 'isomorfeus_data/lucid_query_result'
33
- require 'isomorfeus_data/lucid_object/mixin'
34
- require 'isomorfeus_data/lucid_object/base'
35
- require 'isomorfeus_data/lucid_query/mixin'
36
- require 'isomorfeus_data/lucid_query/base'
37
- require 'isomorfeus_data/lucid_file/mixin'
38
- require 'isomorfeus_data/lucid_file/base'
28
+ require 'isomorfeus/data/enhancer'
29
+ Isomorfeus::Data::Enhancer.add_enhancer_to_store
30
+
31
+ if RUBY_ENGINE == 'opal'
32
+ Isomorfeus.zeitwerk.push_dir('isomorfeus_data')
33
+ require_tree 'isomorfeus_data', autoload: true
34
+ Isomorfeus.zeitwerk.push_dir('data')
35
+ else
36
+ require 'isomorfeus/data/rack_middleware'
39
37
 
40
- require 'isomorfeus/data/handler/generic'
38
+ Isomorfeus.add_middleware(Isomorfeus::Data::RackMiddleware)
41
39
 
42
40
  require 'iso_opal'
43
- Opal.append_path(__dir__.untaint) unless IsoOpal.paths.include?(__dir__.untaint)
44
- uri_path = File.expand_path(File.join(__dir__.untaint, '..', 'opal'))
45
- Opal.append_path(uri_path) unless IsoOpal.paths.include?(uri_path)
41
+ Opal.append_path(__dir__.untaint) unless IsoOpal.paths_include?(__dir__.untaint)
46
42
 
47
43
  path = File.expand_path(File.join('app', 'data'))
48
-
49
- Isomorfeus.zeitwerk.push_dir(path)
44
+ Isomorfeus.zeitwerk.push_dir(path) if Dir.exist?(path)
50
45
  end
@@ -0,0 +1,163 @@
1
+ class LucidDataGeneric
2
+ class << self
3
+ def create(key: nil, **things)
4
+ new(key: key, **things).create
5
+ end
6
+
7
+ def load(key:)
8
+ instance = self.new(key: key, _loading: true)
9
+ instance.loaded? ? instance : load!(key: key, instance: instance)
10
+ end
11
+
12
+ def destroyed?(key:)
13
+ Isomorfeus.store.dig(:data_state, self.name, key, :revision) == -1
14
+ end
15
+
16
+ def current_user
17
+ Isomorfeus.current_user
18
+ end
19
+ end
20
+
21
+ if RUBY_ENGINE == 'opal'
22
+ class << self
23
+ # callbacks
24
+ def before_load(&block); end
25
+ def after_load(&block); end
26
+ def before_create(&block); end
27
+ def after_create(&block); end
28
+ def before_save(&block); end
29
+ def after_save(&block); end
30
+ def before_destroy(&block); end
31
+ def after_destroy(&block); end
32
+ end
33
+ else # RUBY_ENGINE
34
+ class << self
35
+ # callbacks
36
+
37
+ def before_create(&block)
38
+ @before_create_block = block
39
+ end
40
+
41
+ def after_create(&block)
42
+ @after_create_block = block
43
+ end
44
+
45
+ def before_load(&block)
46
+ @before_load_block = block
47
+ end
48
+
49
+ def after_load(&block)
50
+ @after_load_block = block
51
+ end
52
+
53
+ def before_save(&block)
54
+ @before_save_block = block
55
+ end
56
+
57
+ def after_save(&block)
58
+ @after_save_block = block
59
+ end
60
+
61
+ def before_destroy(&block)
62
+ @before_destroy_block = block
63
+ end
64
+
65
+ def after_destroy(&block)
66
+ @after_destroy_block = block
67
+ end
68
+
69
+ # callback readers
70
+
71
+ attr_reader :before_create_block, :after_create_block
72
+ attr_reader :before_load_block, :after_load_block
73
+ attr_reader :before_save_block, :after_save_block
74
+ attr_reader :before_destroy_block, :after_destroy_block
75
+ end
76
+
77
+ def pub_sub_client
78
+ Isomorfeus.pub_sub_client
79
+ end
80
+ end # RUBY_ENGINE
81
+
82
+ def initialize(key: nil)
83
+ @key = key.nil? ? SecureRandom.uuid : key.to_s
84
+ @self_class = self.class
85
+ @class_name = @self_class.name
86
+ @class_name = @class_name.split('>::').last if @class_name.start_with?('#<')
87
+ @changed = false
88
+ end
89
+
90
+ def loaded?
91
+ revision > 0
92
+ end
93
+
94
+ def instance_uuid
95
+ @instance_uuid ||= SecureRandom.uuid
96
+ end
97
+
98
+ def state_instance_uuid
99
+ Isomorfeus.store.dig(:data_state, @class_name, @key, :instance_uuid)
100
+ end
101
+
102
+ def revision
103
+ Isomorfeus.store.dig(:data_state, @class_name, @key, :revision) || 0
104
+ end
105
+
106
+ def created?
107
+ (state_instance_uuid == instance_uuid) && (revision == 1)
108
+ end
109
+
110
+ def saved?
111
+ (state_instance_uuid == instance_uuid) && (revision == (@last_revision + 1))
112
+ end
113
+
114
+ def reload
115
+ unchange!
116
+ @self_class.load!(key: @key, instance: self)
117
+ self
118
+ end
119
+
120
+ def destroy
121
+ @self_class.destroy(key: @key)
122
+ end
123
+
124
+ def destroyed?
125
+ @self_class.destroyed?(key: @key)
126
+ end
127
+
128
+ def changed!
129
+ @changed = true
130
+ end
131
+
132
+ def unchange!
133
+ @changed = false
134
+ end
135
+
136
+ def key
137
+ @key
138
+ end
139
+
140
+ def key=(k)
141
+ @key = k.to_s
142
+ end
143
+
144
+ def changed?
145
+ @changed
146
+ end
147
+
148
+ def sid
149
+ SID.new(@class_name, @key)
150
+ end
151
+
152
+ def to_n
153
+ sid.to_s
154
+ end
155
+
156
+ def current_user
157
+ Isomorfeus.current_user
158
+ end
159
+
160
+ def pub_sub_client
161
+ Isomorfeus.pub_sub_client
162
+ end
163
+ end
data/lib/lucid_file.rb ADDED
@@ -0,0 +1,137 @@
1
+ class LucidFile < LucidDataGeneric
2
+ include LucidI18nMixin
3
+
4
+ VALID_METHODS = %i[key data data_uri content_type]
5
+
6
+ class << self
7
+ def api_path(key:)
8
+ "#{Isomorfeus.api_files_path}#{self.name}/#{key}"
9
+ end
10
+
11
+ def format_data_uri(c, d)
12
+ "data:#{c};base64,#{Base64.strict_encode64(d)}"
13
+ end
14
+
15
+ def destroy(key:)
16
+ Isomorfeus.store.deferred_dispatch(type: 'FILE_DESTROY', class_name: self.name, key: key)
17
+ true
18
+ end
19
+
20
+ def load!(key:, instance: nil)
21
+ instance = self.new(key: key, _loading: true) unless instance
22
+ Isomorfeus.something_loading!
23
+ Isomorfeus.store.deferred_dispatch(type: 'FILE_LOAD', class_name: self.name, key: key)
24
+ instance
25
+ end
26
+ end
27
+
28
+ def initialize(key: nil, content_type: nil, data: nil, data_uri: nil, _loading: false)
29
+ super(key: key)
30
+ _update_paths
31
+ loaded = loaded?
32
+ @changed_uri_string = nil
33
+ if data_uri
34
+ @changed_uri_string = data_uri
35
+ elsif data || content_type
36
+ data = data.to_s unless data.is_a?(String)
37
+ if RUBY_ENGINE != 'opal'
38
+ content_type = Marcel::MimeType.for(data) unless content_type
39
+ end
40
+ @changed_uri_string = self.class.format_data_uri(content_type, data)
41
+ end
42
+ end
43
+
44
+ def create
45
+ raise 'Already created!' if revision > 0
46
+ @last_revision = 0
47
+ d = data_uri
48
+ unchange!
49
+ Isomorfeus.something_loading!
50
+ Isomorfeus.store.deferred_dispatch(type: "FILE_CREATE", class_name: @class_name, key: @key, data_uri: d, instance_uuid: instance_uuid, revision: 0)
51
+ self
52
+ end
53
+
54
+ def save
55
+ @last_revision = revision
56
+ return create if @last_revision == 0
57
+ d = data_uri
58
+ unchange!
59
+ Isomorfeus.something_loading!
60
+ Isomorfeus.store.deferred_dispatch(type: "FILE_SAVE", class_name: @class_name, key: @key, data_uri: d, instance_uuid: instance_uuid, revision: @last_revision)
61
+ self
62
+ end
63
+
64
+ def [](name)
65
+ self.send(name) if VALID_METHODS.include?(name)
66
+ end
67
+
68
+ def []=(name, value)
69
+ self.send("#{name}=".to_sym, value) if VALID_METHODS.include?(name)
70
+ end
71
+
72
+ def unchange!
73
+ super
74
+ @changed_uri_string = nil
75
+ end
76
+
77
+ def _update_paths
78
+ @store_path = [:data_state, @class_name, @key, :data_uri]
79
+ end
80
+
81
+ def content_type
82
+ data_uri_object&.content_type
83
+ end
84
+
85
+ def content_type=(c)
86
+ changed!
87
+ uri_string = @changed_uri_string ? @changed_uri_string : Isomorfeus.store.dig(*@store_path)
88
+ @changed_uri_string = self.class.format_data_uri(c, URI::Data.new(uri_string).data)
89
+ c
90
+ end
91
+
92
+ def data_uri
93
+ @changed_uri_string ? @changed_uri_string : Isomorfeus.store.dig(*@store_path)
94
+ end
95
+
96
+ def data_uri=(d)
97
+ changed!
98
+ @changed_uri_string = d
99
+ end
100
+
101
+ def data_uri_object
102
+ du = data_uri
103
+ URI::Data.new(du) if du
104
+ end
105
+
106
+ def data
107
+ data_uri_object&.data
108
+ end
109
+
110
+ def data=(d)
111
+ changed!
112
+ d = d.to_s unless d.is_a?(String)
113
+ if RUBY_ENGINE == 'opal'
114
+ ct = content_type
115
+ else
116
+ ct = Marcel::MimeType.for(d)
117
+ end
118
+ @changed_uri_string = self.class.format_data_uri(ct, d)
119
+ end
120
+
121
+ if RUBY_ENGINE != 'opal'
122
+ class << self
123
+ def inherited(base)
124
+ Isomorfeus.add_valid_data_class(base) unless 'LucidFile' == base.name
125
+ end
126
+
127
+ def file_accelerator
128
+ @file_accelerator ||= Isomorfeus::Data::FileAccelerator.new(self)
129
+ end
130
+
131
+ def exist?(key:)
132
+ raise "Not authorized!" unless current_user.authorized?(self, :load, key: key)
133
+ file_accelerator.exist?(sid: SID.new(self.name, key))
134
+ end
135
+ end
136
+ end # RUBY_ENGINE
137
+ end