ar_sync 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|