standard-file 0.3.1 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7507a20e41218ab29adb02614a3d56c8093b920e
4
- data.tar.gz: d03b8b5fe61838f446b550bc289a4ba34226903a
3
+ metadata.gz: 4713fd9c76de2ac82f16a1fe84b27a53a25d4c3a
4
+ data.tar.gz: c85729b0055a6b4df9038f4804a4442905ed4dc5
5
5
  SHA512:
6
- metadata.gz: 7f4b70e3fee538bf9709b2328f2832bed09d081042e779cf6762dc56453022a136797353c723315c910723f29d28ac4d417015c5845e31ed3d0b0d4fa03004ed
7
- data.tar.gz: c3a9908c6eae5dc5ea533482a6e25daa3a5d52ce3e635e2a4bcc5b6e9e19e0510028e34bc7e97fdbead59b3b044f278d307ec529632e186c8c8325d697429ba8
6
+ metadata.gz: 6d42f737a016dba90c00cc661170b5b93f21a830664e65a3a2568f778bfd120e2ee344bbb8fa63ebbb9b7eb1a880a320f0685cf042090337c2c13f85535a7da1
7
+ data.tar.gz: 94fe38ec72e42df859cbae76cb50ac1affd49b131e8fa5e5dc366a4f5134c4003a2e3611663b235d1ae58553c40ee8357d4fb724c315a31863b5c424b806c927
data/lib/standard_file.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require "standard_file/engine"
2
- require_relative 'standard_file/sync_manager'
3
- require_relative 'standard_file/user_manager'
2
+ require_relative 'standard_file/abstract/sync_manager'
3
+ require_relative 'standard_file/abstract/user_manager'
4
+ require_relative 'standard_file/2016_12_15/sync_manager'
5
+ require_relative 'standard_file/2019_05_20/sync_manager'
6
+ require_relative 'standard_file/2016_12_15/user_manager'
7
+ require_relative 'standard_file/2019_05_20/user_manager'
4
8
  require_relative 'standard_file/jwt_helper'
5
9
 
6
10
  module StandardFile
@@ -0,0 +1,143 @@
1
+ module StandardFile
2
+ module V20161215
3
+ class SyncManager < StandardFile::AbstractSyncManager
4
+
5
+ def sync(item_hashes, options, request)
6
+ in_sync_token = options[:sync_token]
7
+ in_cursor_token = options[:cursor_token]
8
+ limit = options[:limit]
9
+ content_type = options[:content_type] # optional, only return items of these type if present
10
+
11
+ retrieved_items, cursor_token = _sync_get(in_sync_token, in_cursor_token, limit, content_type).to_a
12
+ last_updated = DateTime.now
13
+ saved_items, unsaved_items = _sync_save(item_hashes, request)
14
+ if saved_items.length > 0
15
+ last_updated = saved_items.sort_by{|m| m.updated_at}.last.updated_at
16
+ end
17
+
18
+ check_for_conflicts(saved_items, retrieved_items, unsaved_items)
19
+
20
+ # add 1 microsecond to avoid returning same object in subsequent sync
21
+ last_updated = (last_updated.to_time + 1/100000.0).to_datetime.utc
22
+
23
+ sync_token = sync_token_from_datetime(last_updated)
24
+ return {
25
+ :retrieved_items => retrieved_items,
26
+ :saved_items => saved_items,
27
+ :unsaved => unsaved_items,
28
+ :sync_token => sync_token,
29
+ :cursor_token => cursor_token
30
+ }
31
+ end
32
+
33
+ def check_for_conflicts(saved_items, retrieved_items, unsaved_items)
34
+ # conflicts occur when you are trying to save an item for which there is a pending change already
35
+ min_conflict_interval = 20
36
+
37
+ if Rails.env.development?
38
+ min_conflict_interval = 1
39
+ end
40
+
41
+ saved_ids = saved_items.map{|x| x.uuid }
42
+ retrieved_ids = retrieved_items.map{|x| x.uuid }
43
+ conflicts = saved_ids & retrieved_ids # & is the intersection
44
+ # saved items take precedence, retrieved items are duplicated with a new uuid
45
+ conflicts.each do |conflicted_uuid|
46
+ # if changes are greater than min_conflict_interval seconds apart,
47
+ # push the retrieved item in the unsaved array so that the client can duplicate it
48
+ saved = saved_items.find{|i| i.uuid == conflicted_uuid}
49
+ conflicted = retrieved_items.find{|i| i.uuid == conflicted_uuid}
50
+ if (saved.updated_at - conflicted.updated_at).abs > min_conflict_interval
51
+ # puts "\n\n\n Creating conflicted copy of #{saved.uuid}\n\n\n"
52
+
53
+ unsaved_items.push({
54
+ :item => conflicted,
55
+ :error => {:tag => "sync_conflict"}
56
+ })
57
+
58
+ end
59
+
60
+ # We remove the item from retrieved items whether or not it satisfies the min_conflict_interval
61
+ # This is because the 'saved' value takes precedence, since that's the current value in the database.
62
+ # So by removing it from retrieved, we are forcing the client to ignore this change.
63
+ retrieved_items.delete(conflicted)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def _sync_save(item_hashes, request)
70
+ if !item_hashes
71
+ return [], []
72
+ end
73
+ saved_items = []
74
+ unsaved = []
75
+
76
+ item_hashes.each do |item_hash|
77
+ begin
78
+ item = @user.items.find_or_create_by(:uuid => item_hash[:uuid])
79
+ rescue => error
80
+ unsaved.push({
81
+ :item => item_hash,
82
+ :error => {:message => error.message, :tag => "uuid_conflict"}
83
+ })
84
+ next
85
+ end
86
+
87
+ item.last_user_agent = request.user_agent
88
+ item.update(item_hash.permit(*permitted_params))
89
+ # we want to force update the updated_at field, even if no changes were made
90
+ # item.touch
91
+
92
+
93
+ if item.deleted == true
94
+ set_deleted(item)
95
+ item.save
96
+ end
97
+
98
+ saved_items.push(item)
99
+ end
100
+
101
+ return saved_items, unsaved
102
+ end
103
+
104
+ def _sync_get(sync_token, input_cursor_token, limit, content_type)
105
+ cursor_token = nil
106
+ if limit == nil
107
+ limit = 100000
108
+ end
109
+
110
+ # if both are present, cursor_token takes precendence as that would eventually return all results
111
+ # the distinction between getting results for a cursor and a sync token is that cursor results use a
112
+ # >= comparison, while a sync token uses a > comparison. The reason for this is that cursor tokens are
113
+ # typically used for initial syncs or imports, where a bunch of notes could have the exact same updated_at
114
+ # by using >=, we don't miss those results on a subsequent call with a cursor token
115
+ if input_cursor_token
116
+ date = datetime_from_sync_token(input_cursor_token)
117
+ items = @user.items.order(:updated_at).where("updated_at >= ?", date)
118
+ elsif sync_token
119
+ date = datetime_from_sync_token(sync_token)
120
+ items = @user.items.order(:updated_at).where("updated_at > ?", date)
121
+ else
122
+ # if no cursor token and no sync token, this is an initial sync. No need to return deleted items.
123
+ items = @user.items.order(:updated_at).where(:deleted => false)
124
+ end
125
+
126
+ if content_type
127
+ items = items.where(:content_type => content_type)
128
+ end
129
+
130
+ items = items.sort_by{|m| m.updated_at}
131
+
132
+ if items.count > limit
133
+ items = items.slice(0, limit)
134
+ date = items.last.updated_at
135
+ cursor_token = sync_token_from_datetime(date)
136
+ end
137
+
138
+ return items, cursor_token
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,7 @@
1
+ module StandardFile
2
+ module V20161215
3
+ class UserManager < StandardFile::AbstractUserManager
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,161 @@
1
+ module StandardFile
2
+ module V20190520
3
+ class SyncManager < StandardFile::AbstractSyncManager
4
+
5
+ def sync(item_hashes, options, request)
6
+ in_sync_token = options[:sync_token]
7
+ in_cursor_token = options[:cursor_token]
8
+ limit = options[:limit]
9
+ content_type = options[:content_type] # optional, only return items of these type if present
10
+
11
+ retrieved_items, cursor_token = _sync_get(in_sync_token, in_cursor_token, limit, content_type).to_a
12
+ last_updated = DateTime.now
13
+ saved_items, conflicts = _sync_save(item_hashes, request, retrieved_items)
14
+
15
+ if saved_items.length > 0
16
+ last_updated = saved_items.sort_by{|m| m.updated_at}.last.updated_at
17
+ end
18
+
19
+ # add 1 microsecond to avoid returning same object in subsequent sync
20
+ last_updated = (last_updated.to_time + 1/100000.0).to_datetime.utc
21
+ sync_token = sync_token_from_datetime(last_updated)
22
+
23
+ return {
24
+ :retrieved_items => retrieved_items,
25
+ :saved_items => saved_items,
26
+ :conflicts => conflicts,
27
+ :sync_token => sync_token,
28
+ :cursor_token => cursor_token
29
+ }
30
+ end
31
+
32
+
33
+ # Ignore differences that are at most this many seconds apart
34
+ # Anything over this threshold will be conflicted.
35
+ MIN_CONFLICT_INTERVAL = 1.0
36
+
37
+ def _sync_save(item_hashes, request, retrieved_items)
38
+ if !item_hashes
39
+ return [], []
40
+ end
41
+
42
+ saved_items = []
43
+ conflicts = []
44
+
45
+ item_hashes.each do |item_hash|
46
+ is_new_record = false
47
+ begin
48
+ item = @user.items.find_or_create_by(:uuid => item_hash[:uuid]) do |created_item|
49
+ # this block is executed if this is a new record.
50
+ is_new_record = true
51
+ end
52
+ rescue => error
53
+ conflicts.push({
54
+ :unsaved_item => item_hash,
55
+ :type => "uuid_conflict"
56
+ })
57
+ next
58
+ end
59
+
60
+ # SFJS did not send updated_at prior to 0.3.59.
61
+ # updated_at value from client will not be saved, as it is not a permitted_param.
62
+ if item_hash['updated_at']
63
+ incoming_updated_at = DateTime.parse(item_hash['updated_at'])
64
+ else
65
+ # Default to epoch
66
+ incoming_updated_at = Time.at(0).to_datetime
67
+ end
68
+
69
+ if !is_new_record
70
+ # We want to check if this updated_at value is equal to the item's current updated_at value.
71
+ # If they differ, it means the client is attempting to save an item which hasn't been updated.
72
+ # In this case, if the incoming_item.updated_at < server_item.updated_at, always conflict.
73
+ # We don't want old items overriding newer ones.
74
+ # incoming_item.updated_at > server_item.updated_at would seem to be impossible, as only servers are responsible for setting updated_at.
75
+ # But assuming a rogue client has gotten away with it,
76
+ # we should also conflict in this case if the difference between the dates is greater than MIN_CONFLICT_INTERVAL seconds.
77
+
78
+ save_incoming = true
79
+
80
+ our_updated_at = item.updated_at
81
+ difference = incoming_updated_at.to_f - our_updated_at.to_f
82
+
83
+ if difference < 0
84
+ # incoming is less than ours. This implies stale data. Don't save if greater than interval
85
+ save_incoming = difference.abs < MIN_CONFLICT_INTERVAL
86
+ elsif difference > 0
87
+ # incoming is greater than ours. Should never be the case. If so though, don't save.
88
+ save_incoming = difference.abs < MIN_CONFLICT_INTERVAL
89
+ else
90
+ # incoming is equal to ours (which is desired, healthy behavior), continue with saving.
91
+ save_incoming = true
92
+ end
93
+
94
+ if !save_incoming
95
+ # Dont save incoming and send it back. At this point the server item is likely to be included
96
+ # in retrieved_items in a subsequent sync, so when that value comes into the client,
97
+ server_value = item.as_json({})
98
+ conflicts.push({
99
+ :server_item => server_value, # as_json to get values as-is, befor modifying below,
100
+ :type => "sync_conflict"
101
+ })
102
+
103
+ retrieved_items.delete(item)
104
+ next
105
+ end
106
+ end
107
+
108
+ item.last_user_agent = request.user_agent
109
+ item.update(item_hash.permit(*permitted_params))
110
+
111
+ if item.deleted == true
112
+ set_deleted(item)
113
+ item.save
114
+ end
115
+
116
+ saved_items.push(item)
117
+ end
118
+
119
+ return saved_items, conflicts
120
+ end
121
+
122
+ def _sync_get(sync_token, input_cursor_token, limit, content_type)
123
+ cursor_token = nil
124
+ if limit == nil
125
+ limit = 100000
126
+ end
127
+
128
+ # if both are present, cursor_token takes precendence as that would eventually return all results
129
+ # the distinction between getting results for a cursor and a sync token is that cursor results use a
130
+ # >= comparison, while a sync token uses a > comparison. The reason for this is that cursor tokens are
131
+ # typically used for initial syncs or imports, where a bunch of notes could have the exact same updated_at
132
+ # by using >=, we don't miss those results on a subsequent call with a cursor token
133
+ if input_cursor_token
134
+ date = datetime_from_sync_token(input_cursor_token)
135
+ items = @user.items.order(:updated_at).where("updated_at >= ?", date)
136
+ elsif sync_token
137
+ date = datetime_from_sync_token(sync_token)
138
+ items = @user.items.order(:updated_at).where("updated_at > ?", date)
139
+ else
140
+ # if no cursor token and no sync token, this is an initial sync. No need to return deleted items.
141
+ items = @user.items.order(:updated_at).where(:deleted => false)
142
+ end
143
+
144
+ if content_type
145
+ items = items.where(:content_type => content_type)
146
+ end
147
+
148
+ items = items.sort_by{|m| m.updated_at}
149
+
150
+ if items.count > limit
151
+ items = items.slice(0, limit)
152
+ date = items.last.updated_at
153
+ cursor_token = sync_token_from_datetime(date)
154
+ end
155
+
156
+ return items, cursor_token
157
+ end
158
+
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,7 @@
1
+ module StandardFile
2
+ module V20190520
3
+ class UserManager < StandardFile::AbstractUserManager
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,61 @@
1
+ module StandardFile
2
+ class AbstractSyncManager
3
+
4
+ attr_accessor :sync_fields
5
+
6
+ def initialize(user)
7
+ @user = user
8
+ raise "User must be set" unless @user
9
+ end
10
+
11
+ def set_sync_fields(val)
12
+ @sync_fields = val
13
+ end
14
+
15
+ def sync_fields
16
+ return @sync_fields || [:content, :enc_item_key, :content_type, :auth_hash, :deleted, :created_at]
17
+ end
18
+
19
+ def destroy_items(uuids)
20
+ items = @user.items.where(uuid: uuids)
21
+ items.destroy_all
22
+ end
23
+
24
+ private
25
+
26
+ def sync_token_from_datetime(datetime)
27
+ version = 2
28
+ Base64.encode64("#{version}:" + "#{datetime.to_f}")
29
+ end
30
+
31
+ def datetime_from_sync_token(sync_token)
32
+ decoded = Base64.decode64(sync_token)
33
+ parts = decoded.rpartition(":")
34
+ timestamp_string = parts.last
35
+ version = parts.first
36
+ if version == "1"
37
+ date = DateTime.strptime(timestamp_string,'%s')
38
+ elsif version == "2"
39
+ date = Time.at(timestamp_string.to_f).to_datetime.utc
40
+ end
41
+
42
+ return date
43
+ end
44
+
45
+ def set_deleted(item)
46
+ item.deleted = true
47
+ item.content = nil if item.has_attribute?(:content)
48
+ item.enc_item_key = nil if item.has_attribute?(:enc_item_key)
49
+ item.auth_hash = nil if item.has_attribute?(:auth_hash)
50
+ end
51
+
52
+ def item_params
53
+ params.permit(*permitted_params)
54
+ end
55
+
56
+ def permitted_params
57
+ sync_fields
58
+ end
59
+
60
+ end
61
+ end
@@ -1,5 +1,5 @@
1
1
  module StandardFile
2
- class UserManager
2
+ class AbstractUserManager
3
3
 
4
4
  def initialize(user_class)
5
5
  @user_class = user_class
@@ -1,3 +1,3 @@
1
1
  module StandardFile
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard-file
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Standard File
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-10 00:00:00.000000000 Z
11
+ date: 2019-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -64,10 +64,14 @@ files:
64
64
  - README.md
65
65
  - Rakefile
66
66
  - lib/standard_file.rb
67
+ - lib/standard_file/2016_12_15/sync_manager.rb
68
+ - lib/standard_file/2016_12_15/user_manager.rb
69
+ - lib/standard_file/2019_05_20/sync_manager.rb
70
+ - lib/standard_file/2019_05_20/user_manager.rb
71
+ - lib/standard_file/abstract/sync_manager.rb
72
+ - lib/standard_file/abstract/user_manager.rb
67
73
  - lib/standard_file/engine.rb
68
74
  - lib/standard_file/jwt_helper.rb
69
- - lib/standard_file/sync_manager.rb
70
- - lib/standard_file/user_manager.rb
71
75
  - lib/standard_file/version.rb
72
76
  - lib/tasks/standard_file_tasks.rake
73
77
  homepage: https://standardnotes.org
@@ -1,197 +0,0 @@
1
- module StandardFile
2
- class SyncManager
3
-
4
- attr_accessor :sync_fields
5
-
6
- def initialize(user)
7
- @user = user
8
- raise "User must be set" unless @user
9
- end
10
-
11
- def set_sync_fields(val)
12
- @sync_fields = val
13
- end
14
-
15
- def sync_fields
16
- return @sync_fields || [:content, :enc_item_key, :content_type, :auth_hash, :deleted, :created_at]
17
- end
18
-
19
- def sync(item_hashes, options, request)
20
-
21
- in_sync_token = options[:sync_token]
22
- in_cursor_token = options[:cursor_token]
23
- limit = options[:limit]
24
- content_type = options[:content_type] # optional, only return items of these type if present
25
-
26
- retrieved_items, cursor_token = _sync_get(in_sync_token, in_cursor_token, limit, content_type).to_a
27
- last_updated = DateTime.now
28
- saved_items, unsaved_items = _sync_save(item_hashes, request)
29
- if saved_items.length > 0
30
- last_updated = saved_items.sort_by{|m| m.updated_at}.last.updated_at
31
- end
32
-
33
- check_for_conflicts(saved_items, retrieved_items, unsaved_items)
34
-
35
- # add 1 microsecond to avoid returning same object in subsequent sync
36
- last_updated = (last_updated.to_time + 1/100000.0).to_datetime.utc
37
-
38
- sync_token = sync_token_from_datetime(last_updated)
39
- return {
40
- :retrieved_items => retrieved_items,
41
- :saved_items => saved_items,
42
- :unsaved => unsaved_items,
43
- :sync_token => sync_token,
44
- :cursor_token => cursor_token
45
- }
46
- end
47
-
48
- def check_for_conflicts(saved_items, retrieved_items, unsaved_items)
49
- # conflicts occur when you are trying to save an item for which there is a pending change already
50
- min_conflict_interval = 20
51
-
52
- if Rails.env.development?
53
- min_conflict_interval = 1
54
- end
55
-
56
- saved_ids = saved_items.map{|x| x.uuid }
57
- retrieved_ids = retrieved_items.map{|x| x.uuid }
58
- conflicts = saved_ids & retrieved_ids # & is the intersection
59
- # saved items take precedence, retrieved items are duplicated with a new uuid
60
- conflicts.each do |conflicted_uuid|
61
- # if changes are greater than min_conflict_interval seconds apart,
62
- # push the retrieved item in the unsaved array so that the client can duplicate it
63
- saved = saved_items.find{|i| i.uuid == conflicted_uuid}
64
- conflicted = retrieved_items.find{|i| i.uuid == conflicted_uuid}
65
- if (saved.updated_at - conflicted.updated_at).abs > min_conflict_interval
66
- # puts "\n\n\n Creating conflicted copy of #{saved.uuid}\n\n\n"
67
-
68
- unsaved_items.push({
69
- :item => conflicted,
70
- :error => {:tag => "sync_conflict"}
71
- })
72
-
73
- end
74
-
75
- # We remove the item from retrieved items whether or not it satisfies the min_conflict_interval
76
- # This is because the 'saved' value takes precedence, since that's the current value in the database.
77
- # So by removing it from retrieved, we are forcing the client to ignore this change.
78
- retrieved_items.delete(conflicted)
79
- end
80
- end
81
-
82
- def destroy_items(uuids)
83
- items = @user.items.where(uuid: uuids)
84
- items.destroy_all
85
- end
86
-
87
-
88
- private
89
-
90
- def sync_token_from_datetime(datetime)
91
- version = 2
92
- Base64.encode64("#{version}:" + "#{datetime.to_f}")
93
- end
94
-
95
- def datetime_from_sync_token(sync_token)
96
- decoded = Base64.decode64(sync_token)
97
- parts = decoded.rpartition(":")
98
- timestamp_string = parts.last
99
- version = parts.first
100
- if version == "1"
101
- date = DateTime.strptime(timestamp_string,'%s')
102
- elsif version == "2"
103
- date = Time.at(timestamp_string.to_f).to_datetime.utc
104
- end
105
-
106
- return date
107
- end
108
-
109
- def _sync_save(item_hashes, request)
110
- if !item_hashes
111
- return [], []
112
- end
113
- saved_items = []
114
- unsaved = []
115
-
116
- item_hashes.each do |item_hash|
117
- begin
118
- item = @user.items.find_or_create_by(:uuid => item_hash[:uuid])
119
- rescue => error
120
- unsaved.push({
121
- :item => item_hash,
122
- :error => {:message => error.message, :tag => "uuid_conflict"}
123
- })
124
- next
125
- end
126
-
127
- item.last_user_agent = request.user_agent
128
- item.update(item_hash.permit(*permitted_params))
129
- # we want to force update the updated_at field, even if no changes were made
130
- # item.touch
131
-
132
-
133
- if item.deleted == true
134
- set_deleted(item)
135
- item.save
136
- end
137
-
138
- saved_items.push(item)
139
- end
140
-
141
- return saved_items, unsaved
142
- end
143
-
144
- def _sync_get(sync_token, input_cursor_token, limit, content_type)
145
- cursor_token = nil
146
- if limit == nil
147
- limit = 100000
148
- end
149
-
150
- # if both are present, cursor_token takes precendence as that would eventually return all results
151
- # the distinction between getting results for a cursor and a sync token is that cursor results use a
152
- # >= comparison, while a sync token uses a > comparison. The reason for this is that cursor tokens are
153
- # typically used for initial syncs or imports, where a bunch of notes could have the exact same updated_at
154
- # by using >=, we don't miss those results on a subsequent call with a cursor token
155
- if input_cursor_token
156
- date = datetime_from_sync_token(input_cursor_token)
157
- items = @user.items.order(:updated_at).where("updated_at >= ?", date)
158
- elsif sync_token
159
- date = datetime_from_sync_token(sync_token)
160
- items = @user.items.order(:updated_at).where("updated_at > ?", date)
161
- else
162
- # if no cursor token and no sync token, this is an initial sync. No need to return deleted items.
163
- items = @user.items.order(:updated_at).where(:deleted => false)
164
- end
165
-
166
- if content_type
167
- items = items.where(:content_type => content_type)
168
- end
169
-
170
- items = items.sort_by{|m| m.updated_at}
171
-
172
- if items.count > limit
173
- items = items.slice(0, limit)
174
- date = items.last.updated_at
175
- cursor_token = sync_token_from_datetime(date)
176
- end
177
-
178
- return items, cursor_token
179
- end
180
-
181
- def set_deleted(item)
182
- item.deleted = true
183
- item.content = nil if item.has_attribute?(:content)
184
- item.enc_item_key = nil if item.has_attribute?(:enc_item_key)
185
- item.auth_hash = nil if item.has_attribute?(:auth_hash)
186
- end
187
-
188
- def item_params
189
- params.permit(*permitted_params)
190
- end
191
-
192
- def permitted_params
193
- sync_fields
194
- end
195
-
196
- end
197
- end