ar_sync 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/Rakefile +10 -0
- data/ar_sync.gemspec +28 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/core/ActioncableAdapter.d.ts +10 -0
- data/core/ActioncableAdapter.js +29 -0
- data/core/ArSyncApi.d.ts +5 -0
- data/core/ArSyncApi.js +74 -0
- data/core/ArSyncModelBase.d.ts +71 -0
- data/core/ArSyncModelBase.js +110 -0
- data/core/ConnectionAdapter.d.ts +7 -0
- data/core/ConnectionAdapter.js +2 -0
- data/core/ConnectionManager.d.ts +19 -0
- data/core/ConnectionManager.js +75 -0
- data/core/DataType.d.ts +60 -0
- data/core/DataType.js +2 -0
- data/core/hooksBase.d.ts +29 -0
- data/core/hooksBase.js +80 -0
- data/graph/ArSyncModel.d.ts +10 -0
- data/graph/ArSyncModel.js +22 -0
- data/graph/ArSyncStore.d.ts +28 -0
- data/graph/ArSyncStore.js +593 -0
- data/graph/hooks.d.ts +3 -0
- data/graph/hooks.js +10 -0
- data/graph/index.d.ts +2 -0
- data/graph/index.js +4 -0
- data/lib/ar_sync.rb +25 -0
- data/lib/ar_sync/class_methods.rb +215 -0
- data/lib/ar_sync/collection.rb +83 -0
- data/lib/ar_sync/config.rb +18 -0
- data/lib/ar_sync/core.rb +138 -0
- data/lib/ar_sync/field.rb +96 -0
- data/lib/ar_sync/instance_methods.rb +130 -0
- data/lib/ar_sync/rails.rb +155 -0
- data/lib/ar_sync/type_script.rb +80 -0
- data/lib/ar_sync/version.rb +3 -0
- data/lib/generators/ar_sync/install/install_generator.rb +87 -0
- data/lib/generators/ar_sync/types/types_generator.rb +11 -0
- data/package-lock.json +1115 -0
- data/package.json +19 -0
- data/src/core/ActioncableAdapter.ts +30 -0
- data/src/core/ArSyncApi.ts +75 -0
- data/src/core/ArSyncModelBase.ts +126 -0
- data/src/core/ConnectionAdapter.ts +5 -0
- data/src/core/ConnectionManager.ts +69 -0
- data/src/core/DataType.ts +73 -0
- data/src/core/hooksBase.ts +86 -0
- data/src/graph/ArSyncModel.ts +21 -0
- data/src/graph/ArSyncStore.ts +567 -0
- data/src/graph/hooks.ts +7 -0
- data/src/graph/index.ts +2 -0
- data/src/tree/ArSyncModel.ts +145 -0
- data/src/tree/ArSyncStore.ts +323 -0
- data/src/tree/hooks.ts +7 -0
- data/src/tree/index.ts +2 -0
- data/tree/ArSyncModel.d.ts +39 -0
- data/tree/ArSyncModel.js +143 -0
- data/tree/ArSyncStore.d.ts +21 -0
- data/tree/ArSyncStore.js +365 -0
- data/tree/hooks.d.ts +3 -0
- data/tree/hooks.js +10 -0
- data/tree/index.d.ts +2 -0
- data/tree/index.js +4 -0
- data/tsconfig.json +15 -0
- data/vendor/assets/javascripts/ar_sync_actioncable_adapter.js.erb +7 -0
- data/vendor/assets/javascripts/ar_sync_graph.js.erb +17 -0
- data/vendor/assets/javascripts/ar_sync_tree.js.erb +17 -0
- metadata +187 -0
data/graph/hooks.d.ts
ADDED
data/graph/hooks.js
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
var hooksBase_1 = require("../core/hooksBase");
|
4
|
+
exports.useArSyncFetch = hooksBase_1.useArSyncFetch;
|
5
|
+
const hooksBase_2 = require("../core/hooksBase");
|
6
|
+
const ArSyncModel_1 = require("./ArSyncModel");
|
7
|
+
function useArSyncModel(request) {
|
8
|
+
return hooksBase_2.useArSyncModelWithClass(ArSyncModel_1.default, request);
|
9
|
+
}
|
10
|
+
exports.useArSyncModel = useArSyncModel;
|
data/graph/index.d.ts
ADDED
data/graph/index.js
ADDED
data/lib/ar_sync.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module ArSync
|
2
|
+
module GraphSync; end
|
3
|
+
module TreeSync; end
|
4
|
+
def self.use(mode, klass: ActiveRecord::Base)
|
5
|
+
case mode
|
6
|
+
when :tree
|
7
|
+
if klass.ancestors.include? ArSync::GraphSync
|
8
|
+
raise ArgumentError, 'already activated ArSync::GraphSync'
|
9
|
+
end
|
10
|
+
klass.include ArSync::TreeSync
|
11
|
+
when :graph
|
12
|
+
if klass.ancestors.include? ArSync::TreeSync
|
13
|
+
raise ArgumentError, 'already activated ArSync::TreeSync'
|
14
|
+
end
|
15
|
+
klass.include ArSync::GraphSync
|
16
|
+
else
|
17
|
+
raise ArgumentError, 'argument should be :tree or :graph'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
require 'ar_sync/version'
|
22
|
+
require 'ar_sync/core'
|
23
|
+
require 'ar_sync/config'
|
24
|
+
require 'ar_sync/type_script'
|
25
|
+
require 'ar_sync/rails' if Kernel.const_defined?('Rails')
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require_relative 'field'
|
2
|
+
require_relative 'collection'
|
3
|
+
|
4
|
+
module ArSync::ClassMethodsBase
|
5
|
+
def _sync_self?
|
6
|
+
instance_variable_defined? '@_sync_self'
|
7
|
+
end
|
8
|
+
|
9
|
+
def _sync_parents_info
|
10
|
+
@_sync_parents_info ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def _sync_children_info
|
14
|
+
@_sync_children_info ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def _sync_child_info(name)
|
18
|
+
info = _sync_children_info[name]
|
19
|
+
return info if info
|
20
|
+
superclass._sync_child_info name if superclass < ActiveRecord::Base
|
21
|
+
end
|
22
|
+
|
23
|
+
def _each_sync_parent(&block)
|
24
|
+
_sync_parents_info.each(&block)
|
25
|
+
superclass._each_sync_parent(&block) if superclass < ActiveRecord::Base
|
26
|
+
end
|
27
|
+
|
28
|
+
def _each_sync_child(&block)
|
29
|
+
_sync_children_info.each(&block)
|
30
|
+
superclass._each_sync_child(&block) if superclass < ActiveRecord::Base
|
31
|
+
end
|
32
|
+
|
33
|
+
def sync_parent(parent, inverse_of:, only_to: nil)
|
34
|
+
_initialize_sync_callbacks
|
35
|
+
_sync_parents_info << [
|
36
|
+
parent,
|
37
|
+
{ inverse_name: inverse_of, only_to: only_to }
|
38
|
+
]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ArSync::TreeSync::ClassMethods
|
43
|
+
include ArSync::ClassMethodsBase
|
44
|
+
def sync_collection(name)
|
45
|
+
ArSync::Collection::Tree.find self, name
|
46
|
+
end
|
47
|
+
|
48
|
+
def sync_has_data(*names, **option, &original_data_block)
|
49
|
+
@_sync_self = true
|
50
|
+
option = option.dup
|
51
|
+
if original_data_block
|
52
|
+
data_block = ->(*args) { instance_exec(*args, &original_data_block).as_json }
|
53
|
+
end
|
54
|
+
option[:params_type] = {}
|
55
|
+
names.each do |name|
|
56
|
+
type_override = data_block.nil? && reflect_on_association(name.to_s.underscore) ? { type: :any } : {}
|
57
|
+
_sync_define ArSync::DataField.new(name), option.merge(type_override), &data_block
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def sync_has_one(name, **option, &data_block)
|
62
|
+
_sync_define ArSync::HasOneField.new(name), option, &data_block
|
63
|
+
end
|
64
|
+
|
65
|
+
def sync_has_many(name, order: :asc, propagate_when: nil, params_type: nil, limit: nil, preload: nil, association: nil, **option, &data_block)
|
66
|
+
if data_block.nil? && preload.nil?
|
67
|
+
underscore_name = name.to_s.underscore.to_sym
|
68
|
+
preload = lambda do |records, _context, params|
|
69
|
+
ArSerializer::Field.preload_association(
|
70
|
+
self, records, association || underscore_name,
|
71
|
+
order: (!limit && params && params[:order]) || order,
|
72
|
+
limit: [params && params[:limit]&.to_i, limit].compact.min
|
73
|
+
)
|
74
|
+
end
|
75
|
+
data_block = lambda do |preloaded, _context, _params|
|
76
|
+
preloaded ? preloaded[id] || [] : send(name)
|
77
|
+
end
|
78
|
+
params_type = { limit?: :int, order?: [{ :* => %w[asc desc] }, 'asc', 'desc'] }
|
79
|
+
else
|
80
|
+
params_type = {}
|
81
|
+
end
|
82
|
+
field = ArSync::HasManyField.new name, association: association, order: order, limit: limit, propagate_when: propagate_when
|
83
|
+
_sync_define field, preload: preload, association: association, params_type: params_type, **option, &data_block
|
84
|
+
end
|
85
|
+
|
86
|
+
def _sync_define(info, **option, &data_block)
|
87
|
+
_initialize_sync_callbacks
|
88
|
+
_sync_children_info[info.name] = info
|
89
|
+
serializer_field info.name, **option, &data_block unless _serializer_field_info info.name
|
90
|
+
serializer_field info.name, **option, namespace: :sync, &data_block
|
91
|
+
end
|
92
|
+
|
93
|
+
def sync_define_collection(name, limit: nil, order: :asc)
|
94
|
+
_initialize_sync_callbacks
|
95
|
+
collection = ArSync::Collection::Tree.new self, name, limit: limit, order: order
|
96
|
+
sync_parent collection, inverse_of: [self, name]
|
97
|
+
end
|
98
|
+
|
99
|
+
def _initialize_sync_callbacks
|
100
|
+
return if instance_variable_defined? '@_sync_callbacks_initialized'
|
101
|
+
@_sync_callbacks_initialized = true
|
102
|
+
_sync_define ArSync::DataField.new(:id)
|
103
|
+
%i[create update destroy].each do |action|
|
104
|
+
after_commit on: action do
|
105
|
+
next if ArSync.skip_notification?
|
106
|
+
self.class.default_scoped.scoping { _sync_notify action }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
module ArSync::GraphSync::ClassMethods
|
113
|
+
include ArSync::ClassMethodsBase
|
114
|
+
def sync_collection(name)
|
115
|
+
ArSync::Collection::Graph.find self, name
|
116
|
+
end
|
117
|
+
|
118
|
+
def sync_has_data(*names, **option, &data_block)
|
119
|
+
@_sync_self = true
|
120
|
+
names.each do |name|
|
121
|
+
_sync_children_info[name] = nil
|
122
|
+
_sync_define name, option, &data_block
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def sync_has_many(name, **option, &data_block)
|
127
|
+
_sync_children_info[name] = :many
|
128
|
+
_sync_has_many name, option, &data_block
|
129
|
+
end
|
130
|
+
|
131
|
+
def sync_has_one(name, **option, &data_block)
|
132
|
+
_sync_children_info[name] = :one
|
133
|
+
_sync_define name, option, &data_block
|
134
|
+
end
|
135
|
+
|
136
|
+
def _sync_has_many(name, order: :asc, limit: nil, preload: nil, association: nil, **option, &data_block)
|
137
|
+
raise "order not in [:asc, :desc] : #{order}" unless %i[asc desc].include? order
|
138
|
+
if data_block.nil? && preload.nil?
|
139
|
+
underscore_name = name.to_s.underscore.to_sym
|
140
|
+
preload = lambda do |records, _context, params|
|
141
|
+
ArSerializer::Field.preload_association(
|
142
|
+
self, records, association || underscore_name,
|
143
|
+
order: (!limit && params && params[:order]) || order,
|
144
|
+
limit: [params && params[:limit]&.to_i, limit].compact.min
|
145
|
+
)
|
146
|
+
end
|
147
|
+
data_block = lambda do |preloaded, _context, params|
|
148
|
+
records = preloaded ? preloaded[id] || [] : send(name)
|
149
|
+
next records unless limit || order == :asc
|
150
|
+
ArSync::CollectionWithOrder.new(
|
151
|
+
records,
|
152
|
+
order: (!limit && params && params[:order]) || order,
|
153
|
+
limit: [params && params[:limit]&.to_i, limit].compact.min
|
154
|
+
)
|
155
|
+
end
|
156
|
+
params_type = { limit?: :int, order?: [{ :* => %w[asc desc] }, 'asc', 'desc'] }
|
157
|
+
else
|
158
|
+
params_type = {}
|
159
|
+
end
|
160
|
+
_sync_define name, preload: preload, association: association, **option.merge(params_type: params_type), &data_block
|
161
|
+
end
|
162
|
+
|
163
|
+
def _sync_define(name, **option, &data_block)
|
164
|
+
_initialize_sync_callbacks
|
165
|
+
serializer_field name, **option, &data_block unless _serializer_field_info name
|
166
|
+
serializer_field name, **option, namespace: :sync, &data_block
|
167
|
+
end
|
168
|
+
|
169
|
+
def sync_define_collection(name, limit: nil, order: :asc)
|
170
|
+
_initialize_sync_callbacks
|
171
|
+
collection = ArSync::Collection::Graph.new self, name, limit: limit, order: order
|
172
|
+
sync_parent collection, inverse_of: [self, name]
|
173
|
+
end
|
174
|
+
|
175
|
+
def _initialize_sync_callbacks
|
176
|
+
return if instance_variable_defined? '@_sync_callbacks_initialized'
|
177
|
+
@_sync_callbacks_initialized = true
|
178
|
+
mod = Module.new do
|
179
|
+
def write_attribute(attr_name, value)
|
180
|
+
self.class.default_scoped.scoping do
|
181
|
+
@_sync_parents_info_before_mutation ||= _sync_current_parents_info
|
182
|
+
end
|
183
|
+
super attr_name, value
|
184
|
+
end
|
185
|
+
end
|
186
|
+
prepend mod
|
187
|
+
attr_reader :_sync_parents_info_before_mutation
|
188
|
+
|
189
|
+
_sync_define :id
|
190
|
+
|
191
|
+
_sync_define :sync_keys, type: [:string] do |current_user|
|
192
|
+
ArSync.sync_graph_keys self, current_user
|
193
|
+
end
|
194
|
+
|
195
|
+
_sync_define :defaults, namespace: :sync do |current_user|
|
196
|
+
{ sync_keys: ArSync.sync_graph_keys(self, current_user) }
|
197
|
+
end
|
198
|
+
|
199
|
+
before_destroy do
|
200
|
+
@_sync_parents_info_before_mutation ||= _sync_current_parents_info
|
201
|
+
end
|
202
|
+
|
203
|
+
before_save on: :create do
|
204
|
+
@_sync_parents_info_before_mutation ||= _sync_current_parents_info
|
205
|
+
end
|
206
|
+
|
207
|
+
%i[create update destroy].each do |action|
|
208
|
+
after_commit on: action do
|
209
|
+
next if ArSync.skip_notification?
|
210
|
+
self.class.default_scoped.scoping { _sync_notify action }
|
211
|
+
@_sync_parents_info_before_mutation = nil
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative 'field'
|
2
|
+
|
3
|
+
class ArSync::Collection
|
4
|
+
attr_reader :klass, :name, :limit, :order
|
5
|
+
def initialize(klass, name, limit: nil, order: nil)
|
6
|
+
@klass = klass
|
7
|
+
@name = name
|
8
|
+
@limit = limit
|
9
|
+
@order = order
|
10
|
+
self.class.defined_collections[[klass, name]] = self
|
11
|
+
define_singleton_method(name) { to_a }
|
12
|
+
end
|
13
|
+
|
14
|
+
def sync_send_event(type:, to_user: nil, data:)
|
15
|
+
event_data = { type: type, data: data }
|
16
|
+
ArSync.sync_tree_send to: self, action: :event, path: [], data: event_data, to_user: to_user
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_a
|
20
|
+
all = klass.all
|
21
|
+
all = all.order id: order if order
|
22
|
+
all = all.limit limit if limit
|
23
|
+
all
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.defined_collections
|
27
|
+
@defined_collections ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find(klass, name)
|
31
|
+
defined_collections[[klass, name]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ArSync::Collection::Tree < ArSync::Collection
|
36
|
+
def initialize(*)
|
37
|
+
super
|
38
|
+
@field = ArSync::CollectionField.new @name, limit: @limit, order: @order
|
39
|
+
self.class._sync_children_info[[@klass, @name]] = @field
|
40
|
+
end
|
41
|
+
|
42
|
+
def _sync_notify_parent(*); end
|
43
|
+
|
44
|
+
def self._sync_children_info
|
45
|
+
@sync_children_info ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def self._sync_child_info(key)
|
49
|
+
_sync_children_info[key]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ArSync::Collection::Graph < ArSync::Collection
|
54
|
+
def _sync_notify_child_changed(_child, _name, _to_user, _owned); end
|
55
|
+
|
56
|
+
def _sync_notify_child_added(child, _name, to_user, _owned)
|
57
|
+
ArSync.sync_graph_send to: self, action: :add, model: child, path: :collection, to_user: to_user
|
58
|
+
end
|
59
|
+
|
60
|
+
def _sync_notify_child_removed(child, _name, to_user, _owned)
|
61
|
+
ArSync.sync_graph_send to: self, action: :remove, model: child, path: :collection, to_user: to_user
|
62
|
+
end
|
63
|
+
|
64
|
+
def self._sync_children_info
|
65
|
+
@sync_children_info ||= {}
|
66
|
+
end
|
67
|
+
|
68
|
+
def self._sync_child_info(key)
|
69
|
+
_sync_children_info[key]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ArSync::CollectionWithOrder < ArSerializer::CompositeValue
|
74
|
+
def initialize(records, order:, limit:)
|
75
|
+
@records = records
|
76
|
+
@order = { mode: order, limit: limit }
|
77
|
+
end
|
78
|
+
|
79
|
+
def ar_serializer_build_sub_calls
|
80
|
+
values = @records.map { {} }
|
81
|
+
[{ order: @order, collection: values }, @records.zip(values)]
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
module ArSync
|
3
|
+
config_keys = %i[
|
4
|
+
current_user_method
|
5
|
+
key_secret
|
6
|
+
key_prefix
|
7
|
+
key_expires_in
|
8
|
+
]
|
9
|
+
Config = Struct.new(*config_keys)
|
10
|
+
|
11
|
+
def self.config
|
12
|
+
@config ||= Config.new :current_user, nil, nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure
|
16
|
+
yield config
|
17
|
+
end
|
18
|
+
end
|
data/lib/ar_sync/core.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'ar_serializer'
|
2
|
+
require_relative 'collection'
|
3
|
+
require_relative 'class_methods'
|
4
|
+
require_relative 'instance_methods'
|
5
|
+
|
6
|
+
module ArSync
|
7
|
+
ArSync::TreeSync.module_eval do
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
include ArSync::TreeSync::InstanceMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
ArSync::GraphSync.module_eval do
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
include ArSync::GraphSync::InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.on_notification(&block)
|
18
|
+
@sync_send_block = block
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.with_compact_notification
|
22
|
+
key = :ar_sync_compact_notifications
|
23
|
+
Thread.current[key] = []
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
events = Thread.current[key]
|
27
|
+
Thread.current[key] = nil
|
28
|
+
@sync_send_block&.call events if events.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.skip_notification?
|
32
|
+
Thread.current[:ar_sync_skip_notification]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.without_notification
|
36
|
+
key = :ar_sync_skip_notification
|
37
|
+
flag_was = Thread.current[key]
|
38
|
+
Thread.current[key] = true
|
39
|
+
yield
|
40
|
+
ensure
|
41
|
+
Thread.current[key] = flag_was
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.sync_tree_send(to:, action:, path:, data:, to_user: nil, ordering: nil)
|
45
|
+
key = sync_key to, path.grep(Symbol), to_user, signature: false
|
46
|
+
event = [key, action: action, path: path, data: data, ordering: ordering]
|
47
|
+
buffer = Thread.current[:ar_sync_compact_notifications]
|
48
|
+
if buffer
|
49
|
+
buffer << event
|
50
|
+
else
|
51
|
+
@sync_send_block&.call [event]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.sync_graph_send(to:, action:, model:, path: nil, to_user: nil)
|
56
|
+
key = sync_graph_key to, to_user, signature: false
|
57
|
+
event = ["#{key}#{path}", action: action, class_name: model.class.base_class.name, id: model.id]
|
58
|
+
buffer = Thread.current[:ar_sync_compact_notifications]
|
59
|
+
if buffer
|
60
|
+
buffer << event
|
61
|
+
else
|
62
|
+
@sync_send_block&.call [event]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.sync_graph_key(model, to_user = nil, signature: true)
|
67
|
+
sync_key(model, :graph_sync, to_user, signature: signature) + ';/'
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.sync_graph_keys(model, user)
|
71
|
+
[sync_graph_key(model), sync_graph_key(model, user)]
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.sync_key(model, path, to_user = nil, signature: true)
|
75
|
+
if model.is_a? ArSync::Collection
|
76
|
+
key = [to_user&.id, model.klass.name, model.name, path].join '/'
|
77
|
+
else
|
78
|
+
key = [to_user&.id, model.class.name, model.id, path].join '/'
|
79
|
+
end
|
80
|
+
key = Digest::SHA256.hexdigest("#{config.key_secret}#{key}")[0, 32] if config.key_secret
|
81
|
+
key = "#{config.key_prefix}#{key}" if config.key_prefix
|
82
|
+
signature && config.key_expires_in ? signed_key(key, Time.now.to_i.to_s) : key
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.validate_expiration(signed_key)
|
86
|
+
signed_key = signed_key.to_s
|
87
|
+
return signed_key unless config.key_expires_in
|
88
|
+
key, time, signature, other = signed_key.split ';', 4
|
89
|
+
return unless signed_key(key, time) == [key, time, signature].join(';')
|
90
|
+
[key, other].compact.join ';' if Time.now.to_i - time.to_i < config.key_expires_in
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.signed_key(key, time)
|
94
|
+
"#{key};#{time};#{Digest::SHA256.hexdigest("#{config.key_secret}#{key};#{time}")[0, 16]}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.sync_collection_api(collection, current_user, args)
|
98
|
+
paths = _extract_paths args
|
99
|
+
keys = paths.flat_map do |path|
|
100
|
+
[sync_key(collection, path), sync_key(collection, path, current_user)]
|
101
|
+
end
|
102
|
+
{
|
103
|
+
keys: keys,
|
104
|
+
data: serialize(collection.to_a, args, user: current_user)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.sync_api(model, current_user, args)
|
109
|
+
paths = _extract_paths args
|
110
|
+
keys = paths.flat_map do |path|
|
111
|
+
[sync_key(model, path), sync_key(model, path, current_user)]
|
112
|
+
end
|
113
|
+
{
|
114
|
+
keys: keys,
|
115
|
+
data: serialize(model, args, user: current_user)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def self._extract_paths(args)
|
120
|
+
parsed = ArSerializer::Serializer.parse_args args
|
121
|
+
paths = []
|
122
|
+
extract = lambda do |path, attributes|
|
123
|
+
paths << path
|
124
|
+
attributes.each do |key, value|
|
125
|
+
sub_attributes = value[:attributes]
|
126
|
+
next unless sub_attributes
|
127
|
+
sub_path = [*path, key]
|
128
|
+
extract.call sub_path, sub_attributes
|
129
|
+
end
|
130
|
+
end
|
131
|
+
extract.call [], parsed[:attributes]
|
132
|
+
paths
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.serialize(record_or_records, query, user: nil)
|
136
|
+
ArSerializer.serialize record_or_records, query, context: user, include_id: true, use: :sync
|
137
|
+
end
|
138
|
+
end
|