wcc-contentful 1.1.2 → 1.3.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/wcc/contentful/webhook_controller.rb +2 -0
  3. data/app/jobs/wcc/contentful/webhook_enable_job.rb +1 -1
  4. data/lib/tasks/download_schema.rake +1 -1
  5. data/lib/wcc/contentful/active_record_shim.rb +2 -2
  6. data/lib/wcc/contentful/configuration.rb +37 -18
  7. data/lib/wcc/contentful/content_type_indexer.rb +2 -0
  8. data/lib/wcc/contentful/downloads_schema.rb +5 -4
  9. data/lib/wcc/contentful/engine.rb +2 -4
  10. data/lib/wcc/contentful/event.rb +4 -11
  11. data/lib/wcc/contentful/exceptions.rb +2 -3
  12. data/lib/wcc/contentful/indexed_representation.rb +3 -6
  13. data/lib/wcc/contentful/instrumentation.rb +2 -1
  14. data/lib/wcc/contentful/link.rb +1 -3
  15. data/lib/wcc/contentful/link_visitor.rb +2 -4
  16. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +5 -9
  17. data/lib/wcc/contentful/middleware/store.rb +4 -6
  18. data/lib/wcc/contentful/model_api.rb +2 -4
  19. data/lib/wcc/contentful/model_builder.rb +8 -4
  20. data/lib/wcc/contentful/model_methods.rb +10 -12
  21. data/lib/wcc/contentful/model_singleton_methods.rb +2 -2
  22. data/lib/wcc/contentful/rich_text/node.rb +60 -0
  23. data/lib/wcc/contentful/rich_text.rb +105 -0
  24. data/lib/wcc/contentful/rspec.rb +1 -3
  25. data/lib/wcc/contentful/services.rb +9 -0
  26. data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
  27. data/lib/wcc/contentful/simple_client/preview.rb +17 -0
  28. data/lib/wcc/contentful/simple_client/response.rb +47 -46
  29. data/lib/wcc/contentful/simple_client.rb +13 -118
  30. data/lib/wcc/contentful/store/base.rb +19 -27
  31. data/lib/wcc/contentful/store/cdn_adapter.rb +11 -7
  32. data/lib/wcc/contentful/store/factory.rb +1 -1
  33. data/lib/wcc/contentful/store/memory_store.rb +10 -7
  34. data/lib/wcc/contentful/store/postgres_store.rb +52 -42
  35. data/lib/wcc/contentful/store/query.rb +2 -2
  36. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +6 -4
  37. data/lib/wcc/contentful/store/rspec_examples/operators.rb +1 -1
  38. data/lib/wcc/contentful/sync_engine.rb +58 -34
  39. data/lib/wcc/contentful/sys.rb +2 -1
  40. data/lib/wcc/contentful/test/double.rb +3 -5
  41. data/lib/wcc/contentful/test/factory.rb +4 -6
  42. data/lib/wcc/contentful/version.rb +1 -1
  43. data/lib/wcc/contentful.rb +10 -13
  44. data/wcc-contentful.gemspec +6 -6
  45. metadata +15 -25
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './rich_text/node'
4
+
5
+ ##
6
+ # This module contains a number of structs representing nodes in a Contentful
7
+ # rich text field. When the Model layer parses a Rich Text field from
8
+ # Contentful, it is turned into a WCC::Contentful::RichText::Document node.
9
+ # The {WCC::Contentful::RichText::Document#content content} method of this
10
+ # node is an Array containing paragraph, blockquote, entry, and other nodes.
11
+ #
12
+ # The various structs in the RichText object model are designed to mimic the
13
+ # Hash interface, so that the indexing operator `#[]` and the `#dig` method
14
+ # can be used to traverse the data. The data can also be accessed by the
15
+ # attribute reader methods defined on the structs. Both of these are considered
16
+ # part of the public API of the model and will not change.
17
+ #
18
+ # In a future release we plan to implement automatic link resolution. When that
19
+ # happens, the `.data` attribute of embedded entries and assets will return a
20
+ # new class that is able to resolve the `.target` automatically into a full
21
+ # entry or asset. This future class will still respect the hash accessor methods
22
+ # `#[]`, `#dig`, `#keys`, and `#each`, so it is safe to use those.
23
+ module WCC::Contentful::RichText
24
+ ##
25
+ # Recursively converts a raw JSON-parsed hash into the RichText object model.
26
+ def self.tokenize(raw, context = nil)
27
+ return unless raw
28
+ return raw.map { |c| tokenize(c, context) } if raw.is_a?(Array)
29
+
30
+ klass =
31
+ case raw['nodeType']
32
+ when 'document'
33
+ Document
34
+ when 'paragraph'
35
+ Paragraph
36
+ when 'blockquote'
37
+ Blockquote
38
+ when 'text'
39
+ Text
40
+ when 'embedded-entry-inline'
41
+ EmbeddedEntryInline
42
+ when 'embedded-entry-block'
43
+ EmbeddedEntryBlock
44
+ when 'embedded-asset-block'
45
+ EmbeddedAssetBlock
46
+ when /heading-(\d+)/
47
+ size = Regexp.last_match(1)
48
+ const_get("Heading#{size}")
49
+ else
50
+ Unknown
51
+ end
52
+
53
+ klass.tokenize(raw, context)
54
+ end
55
+
56
+ Document =
57
+ Struct.new(:nodeType, :data, :content) do
58
+ include WCC::Contentful::RichText::Node
59
+ end
60
+
61
+ Paragraph =
62
+ Struct.new(:nodeType, :data, :content) do
63
+ include WCC::Contentful::RichText::Node
64
+ end
65
+
66
+ Blockquote =
67
+ Struct.new(:nodeType, :data, :content) do
68
+ include WCC::Contentful::RichText::Node
69
+ end
70
+
71
+ Text =
72
+ Struct.new(:nodeType, :value, :marks, :data) do
73
+ include WCC::Contentful::RichText::Node
74
+ end
75
+
76
+ EmbeddedEntryInline =
77
+ Struct.new(:nodeType, :data, :content) do
78
+ include WCC::Contentful::RichText::Node
79
+ end
80
+
81
+ EmbeddedEntryBlock =
82
+ Struct.new(:nodeType, :data, :content) do
83
+ include WCC::Contentful::RichText::Node
84
+ end
85
+
86
+ EmbeddedAssetBlock =
87
+ Struct.new(:nodeType, :data, :content) do
88
+ include WCC::Contentful::RichText::Node
89
+ end
90
+
91
+ (1..5).each do |i|
92
+ struct =
93
+ Struct.new(:nodeType, :data, :content) do
94
+ include WCC::Contentful::RichText::Node
95
+ end
96
+ sz = i
97
+ struct.define_singleton_method(:node_type) { "heading-#{sz}" }
98
+ const_set("Heading#{sz}", struct)
99
+ end
100
+
101
+ Unknown =
102
+ Struct.new(:nodeType, :data, :content) do
103
+ include WCC::Contentful::RichText::Node
104
+ end
105
+ end
@@ -13,9 +13,7 @@ module WCC::Contentful::RSpec
13
13
  # stubs the Model API to return that content type for `.find` and `.find_by`
14
14
  # query methods.
15
15
  def contentful_stub(const, **attrs)
16
- unless const.respond_to?(:content_type_definition)
17
- const = WCC::Contentful::Model.resolve_constant(const.to_s)
18
- end
16
+ const = WCC::Contentful::Model.resolve_constant(const.to_s) unless const.respond_to?(:content_type_definition)
19
17
  instance = contentful_create(const, **attrs)
20
18
 
21
19
  # mimic what's going on inside model_singleton_methods.rb
@@ -132,6 +132,15 @@ module WCC::Contentful
132
132
  # Allow it to be injected into a store
133
133
  alias_method :_instrumentation, :instrumentation
134
134
 
135
+ # Gets the configured logger, defaulting to Rails.logger in a rails context,
136
+ # or logging to STDERR in a non-rails context.
137
+ def logger
138
+ @logger ||=
139
+ configuration.logger ||
140
+ (Rails.logger if defined?(Rails)) ||
141
+ Logger.new($stderr)
142
+ end
143
+
135
144
  ##
136
145
  # This method enables simple dependency injection -
137
146
  # If the target has a setter matching the name of one of the services,
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
4
+ # JSON responses. It exposes methods to query entries, assets, and content_types.
5
+ # The responses are instances of WCC::Contentful::SimpleClient::Response
6
+ # which handles paging automatically.
7
+ #
8
+ # @api Client
9
+ class WCC::Contentful::SimpleClient::Cdn < WCC::Contentful::SimpleClient
10
+ def initialize(space:, access_token:, **options)
11
+ super(
12
+ api_url: options[:api_url] || 'https://cdn.contentful.com/',
13
+ space: space,
14
+ access_token: access_token,
15
+ **options
16
+ )
17
+ end
18
+
19
+ def client_type
20
+ 'cdn'
21
+ end
22
+
23
+ # Gets an entry by ID
24
+ def entry(key, query = {})
25
+ resp =
26
+ _instrument 'entries', id: key, type: 'Entry', query: query do
27
+ get("entries/#{key}", query)
28
+ end
29
+ resp.assert_ok!
30
+ end
31
+
32
+ # Queries entries with optional query parameters
33
+ def entries(query = {})
34
+ resp =
35
+ _instrument 'entries', type: 'Entry', query: query do
36
+ get('entries', query)
37
+ end
38
+ resp.assert_ok!
39
+ end
40
+
41
+ # Gets an asset by ID
42
+ def asset(key, query = {})
43
+ resp =
44
+ _instrument 'entries', type: 'Asset', id: key, query: query do
45
+ get("assets/#{key}", query)
46
+ end
47
+ resp.assert_ok!
48
+ end
49
+
50
+ # Queries assets with optional query parameters
51
+ def assets(query = {})
52
+ resp =
53
+ _instrument 'entries', type: 'Asset', query: query do
54
+ get('assets', query)
55
+ end
56
+ resp.assert_ok!
57
+ end
58
+
59
+ # Queries content types with optional query parameters
60
+ def content_types(query = {})
61
+ resp =
62
+ _instrument 'content_types', query: query do
63
+ get('content_types', query)
64
+ end
65
+ resp.assert_ok!
66
+ end
67
+
68
+ # Accesses the Sync API to get a list of items that have changed since
69
+ # the last sync. Accepts a block that receives each changed item, and returns
70
+ # the next sync token.
71
+ #
72
+ # If `sync_token` is nil, an initial sync is performed.
73
+ #
74
+ # @return String the next sync token parsed from nextSyncUrl
75
+ # @example
76
+ # my_sync_token = storage.get('sync_token')
77
+ # my_sync_token = client.sync(sync_token: my_sync_token) do |item|
78
+ # storage.put(item.dig('sys', 'id'), item) }
79
+ # end
80
+ # storage.put('sync_token', my_sync_token)
81
+ def sync(sync_token: nil, **query, &block)
82
+ return sync_old(sync_token: sync_token, **query) unless block_given?
83
+
84
+ sync_token =
85
+ if sync_token
86
+ { sync_token: sync_token }
87
+ else
88
+ { initial: true }
89
+ end
90
+ query = query.merge(sync_token)
91
+
92
+ _instrument 'sync', sync_token: sync_token, query: query do
93
+ resp = get('sync', query)
94
+ resp = SyncResponse.new(resp)
95
+ resp.assert_ok!
96
+
97
+ resp.each_page do |page|
98
+ page.page_items.each(&block)
99
+ sync_token = resp.next_sync_token
100
+ end
101
+ end
102
+
103
+ sync_token
104
+ end
105
+
106
+ private
107
+
108
+ def sync_old(sync_token: nil, **query)
109
+ ActiveSupport::Deprecation.warn('Sync without a block is deprecated, please use new block syntax instead')
110
+
111
+ sync_token =
112
+ if sync_token
113
+ { sync_token: sync_token }
114
+ else
115
+ { initial: true }
116
+ end
117
+ query = query.merge(sync_token)
118
+
119
+ resp =
120
+ _instrument 'sync', sync_token: sync_token, query: query do
121
+ get('sync', query)
122
+ end
123
+ resp = SyncResponse.new(resp, memoize: true)
124
+ resp.assert_ok!
125
+ end
126
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api Client
4
+ class WCC::Contentful::SimpleClient::Preview < WCC::Contentful::SimpleClient::Cdn
5
+ def initialize(space:, preview_token:, **options)
6
+ super(
7
+ **options,
8
+ api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
9
+ space: space,
10
+ access_token: preview_token
11
+ )
12
+ end
13
+
14
+ def client_type
15
+ 'preview'
16
+ end
17
+ end
@@ -6,9 +6,7 @@ class WCC::Contentful::SimpleClient
6
6
  class Response
7
7
  include ::WCC::Contentful::Instrumentation
8
8
 
9
- attr_reader :raw_response
10
- attr_reader :client
11
- attr_reader :request
9
+ attr_reader :raw_response, :client, :request
12
10
 
13
11
  delegate :status, to: :raw_response
14
12
  alias_method :code, :status
@@ -26,7 +24,7 @@ class WCC::Contentful::SimpleClient
26
24
  def error_message
27
25
  parsed_message =
28
26
  begin
29
- raw.dig('message')
27
+ raw['message']
30
28
  rescue JSON::ParserError
31
29
  nil
32
30
  end
@@ -49,7 +47,6 @@ class WCC::Contentful::SimpleClient
49
47
 
50
48
  def next_page
51
49
  return unless next_page?
52
- return @next_page if @next_page
53
50
 
54
51
  query = (@request[:query] || {}).merge({
55
52
  skip: page_items.length + skip
@@ -58,7 +55,7 @@ class WCC::Contentful::SimpleClient
58
55
  _instrument 'page', url: @request[:url], query: query do
59
56
  @client.get(@request[:url], query)
60
57
  end
61
- @next_page = np.assert_ok!
58
+ np.assert_ok!
62
59
  end
63
60
 
64
61
  def initialize(client, request, raw_response)
@@ -77,16 +74,7 @@ class WCC::Contentful::SimpleClient
77
74
  def each_page(&block)
78
75
  raise ArgumentError, 'Not a collection response' unless page_items
79
76
 
80
- ret =
81
- Enumerator.new do |y|
82
- y << self
83
-
84
- if next_page?
85
- next_page.each_page.each do |page|
86
- y << page
87
- end
88
- end
89
- end
77
+ ret = PaginatingEnumerable.new(self)
90
78
 
91
79
  if block_given?
92
80
  ret.map(&block)
@@ -115,20 +103,16 @@ class WCC::Contentful::SimpleClient
115
103
 
116
104
  def includes
117
105
  @includes ||=
118
- raw.dig('includes')&.each_with_object({}) do |(_t, entries), h|
106
+ raw['includes']&.each_with_object({}) do |(_t, entries), h|
119
107
  entries.each { |e| h[e.dig('sys', 'id')] = e }
120
108
  end || {}
121
-
122
- return @includes unless @next_page
123
-
124
- # This could be more efficient - maybe not worth worrying about
125
- @includes.merge(@next_page.includes)
126
109
  end
127
110
  end
128
111
 
129
112
  class SyncResponse < Response
130
- def initialize(response)
113
+ def initialize(response, memoize: false)
131
114
  super(response.client, response.request, response.raw_response)
115
+ @memoize = memoize
132
116
  end
133
117
 
134
118
  def next_page?
@@ -137,6 +121,7 @@ class WCC::Contentful::SimpleClient
137
121
 
138
122
  def next_page
139
123
  return unless next_page?
124
+ return @next_page if @next_page
140
125
 
141
126
  url = raw['nextPageUrl']
142
127
  next_page =
@@ -144,37 +129,33 @@ class WCC::Contentful::SimpleClient
144
129
  @client.get(url)
145
130
  end
146
131
 
147
- @next_page ||= SyncResponse.new(next_page)
148
- @next_page.assert_ok!
132
+ next_page = SyncResponse.new(next_page)
133
+ next_page.assert_ok!
134
+ @next_page = next_page if @memoize
135
+ next_page
149
136
  end
150
137
 
151
138
  def next_sync_token
152
- # If we haven't grabbed the next page yet, then our next "sync" will be getting
153
- # the next page. We could just as easily call sync again with that token.
154
- @next_page&.next_sync_token ||
155
- @next_sync_token ||= SyncResponse.parse_sync_token(
156
- raw['nextPageUrl'] || raw['nextSyncUrl']
157
- )
158
- end
139
+ # If we have iterated some pages, return the sync token of the final
140
+ # page that was iterated. Do this without maintaining a reference to
141
+ # all the pages.
142
+ return @last_sync_token if @last_sync_token
159
143
 
160
- def each_page
161
- raise ArgumentError, 'Not a collection response' unless page_items
144
+ SyncResponse.parse_sync_token(raw['nextPageUrl'] || raw['nextSyncUrl'])
145
+ end
162
146
 
163
- ret =
164
- Enumerator.new do |y|
165
- y << self
147
+ def each_page(&block)
148
+ if block_given?
149
+ super do |page|
150
+ @last_sync_token = page.next_sync_token
166
151
 
167
- if next_page?
168
- next_page.each_page.each do |page|
169
- y << page
170
- end
171
- end
152
+ yield page
172
153
  end
173
-
174
- if block_given?
175
- ret.map(&block)
176
154
  else
177
- ret.lazy
155
+ super.map do |page|
156
+ @last_sync_token = page.next_sync_token
157
+ page
158
+ end
178
159
  end
179
160
  end
180
161
 
@@ -190,6 +171,26 @@ class WCC::Contentful::SimpleClient
190
171
  end
191
172
  end
192
173
 
174
+ class PaginatingEnumerable
175
+ include Enumerable
176
+
177
+ def initialize(initial_page)
178
+ raise ArgumentError, 'Must provide initial page' unless initial_page.present?
179
+
180
+ @initial_page = initial_page
181
+ end
182
+
183
+ def each
184
+ page = @initial_page
185
+ yield page
186
+
187
+ while page.next_page?
188
+ page = page.next_page
189
+ yield page
190
+ end
191
+ end
192
+ end
193
+
193
194
  class ApiError < StandardError
194
195
  attr_reader :response
195
196
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative 'simple_client/response'
4
4
  require_relative 'simple_client/management'
5
+ require_relative 'simple_client/cdn'
6
+ require_relative 'simple_client/preview'
5
7
  require_relative 'instrumentation'
6
8
 
7
9
  module WCC::Contentful
@@ -24,8 +26,7 @@ module WCC::Contentful
24
26
  class SimpleClient
25
27
  include WCC::Contentful::Instrumentation
26
28
 
27
- attr_reader :api_url
28
- attr_reader :space
29
+ attr_reader :api_url, :space
29
30
 
30
31
  # Creates a new SimpleClient with the given configuration.
31
32
  #
@@ -41,7 +42,7 @@ module WCC::Contentful
41
42
  # @option options [Number] rate_limit_wait_timeout The maximum time to block the thread waiting
42
43
  # on a rate limit response. By default will wait for one 429 and then fail on the second 429.
43
44
  def initialize(api_url:, space:, access_token:, **options)
44
- @api_url = URI.join(api_url, '/spaces/', space + '/')
45
+ @api_url = URI.join(api_url, '/spaces/', "#{space}/")
45
46
  @space = space
46
47
  @access_token = access_token
47
48
 
@@ -57,7 +58,7 @@ module WCC::Contentful
57
58
 
58
59
  return unless options[:environment].present?
59
60
 
60
- @api_url = URI.join(@api_url, 'environments/', options[:environment] + '/')
61
+ @api_url = URI.join(@api_url, 'environments/', "#{options[:environment]}/")
61
62
  end
62
63
 
63
64
  # performs an HTTP GET request to the specified path within the configured
@@ -84,15 +85,13 @@ module WCC::Contentful
84
85
  case adapter
85
86
  when nil
86
87
  ADAPTERS.each do |a, spec|
87
- begin
88
- gem(*spec)
89
- return load_adapter(a)
90
- rescue Gem::LoadError
91
- next
92
- end
88
+ gem(*spec)
89
+ return load_adapter(a)
90
+ rescue Gem::LoadError
91
+ next
93
92
  end
94
- raise ArgumentError, 'Unable to load adapter! Please install one of '\
95
- "#{ADAPTERS.values.map(&:join).join(',')}"
93
+ raise ArgumentError, 'Unable to load adapter! Please install one of ' \
94
+ "#{ADAPTERS.values.map(&:join).join(',')}"
96
95
  when :faraday
97
96
  require 'faraday'
98
97
  ::Faraday.new do |faraday|
@@ -104,8 +103,8 @@ module WCC::Contentful
104
103
  TyphoeusAdapter.new
105
104
  else
106
105
  unless adapter.respond_to?(:get)
107
- raise ArgumentError, "Adapter #{adapter} is not invokeable! Please "\
108
- "pass use one of #{ADAPTERS.keys} or create a Faraday-compatible adapter"
106
+ raise ArgumentError, "Adapter #{adapter} is not invokeable! Please " \
107
+ "pass use one of #{ADAPTERS.keys} or create a Faraday-compatible adapter"
109
108
  end
110
109
  adapter
111
110
  end
@@ -149,109 +148,5 @@ module WCC::Contentful
149
148
  return resp
150
149
  end
151
150
  end
152
-
153
- # The CDN SimpleClient accesses 'https://cdn.contentful.com' to get raw
154
- # JSON responses. It exposes methods to query entries, assets, and content_types.
155
- # The responses are instances of WCC::Contentful::SimpleClient::Response
156
- # which handles paging automatically.
157
- #
158
- # @api Client
159
- class Cdn < SimpleClient
160
- def initialize(space:, access_token:, **options)
161
- super(
162
- api_url: options[:api_url] || 'https://cdn.contentful.com/',
163
- space: space,
164
- access_token: access_token,
165
- **options
166
- )
167
- end
168
-
169
- def client_type
170
- 'cdn'
171
- end
172
-
173
- # Gets an entry by ID
174
- def entry(key, query = {})
175
- resp =
176
- _instrument 'entries', id: key, type: 'Entry', query: query do
177
- get("entries/#{key}", query)
178
- end
179
- resp.assert_ok!
180
- end
181
-
182
- # Queries entries with optional query parameters
183
- def entries(query = {})
184
- resp =
185
- _instrument 'entries', type: 'Entry', query: query do
186
- get('entries', query)
187
- end
188
- resp.assert_ok!
189
- end
190
-
191
- # Gets an asset by ID
192
- def asset(key, query = {})
193
- resp =
194
- _instrument 'entries', type: 'Asset', id: key, query: query do
195
- get("assets/#{key}", query)
196
- end
197
- resp.assert_ok!
198
- end
199
-
200
- # Queries assets with optional query parameters
201
- def assets(query = {})
202
- resp =
203
- _instrument 'entries', type: 'Asset', query: query do
204
- get('assets', query)
205
- end
206
- resp.assert_ok!
207
- end
208
-
209
- # Queries content types with optional query parameters
210
- def content_types(query = {})
211
- resp =
212
- _instrument 'content_types', query: query do
213
- get('content_types', query)
214
- end
215
- resp.assert_ok!
216
- end
217
-
218
- # Accesses the Sync API to get a list of items that have changed since
219
- # the last sync.
220
- #
221
- # If `sync_token` is nil, an initial sync is performed.
222
- # Returns a WCC::Contentful::SimpleClient::SyncResponse
223
- # which handles paging automatically.
224
- def sync(sync_token: nil, **query)
225
- sync_token =
226
- if sync_token
227
- { sync_token: sync_token }
228
- else
229
- { initial: true }
230
- end
231
- query = query.merge(sync_token)
232
- resp =
233
- _instrument 'sync', sync_token: sync_token, query: query do
234
- get('sync', query)
235
- end
236
- resp = SyncResponse.new(resp)
237
- resp.assert_ok!
238
- end
239
- end
240
-
241
- # @api Client
242
- class Preview < Cdn
243
- def initialize(space:, preview_token:, **options)
244
- super(
245
- **options,
246
- api_url: options[:preview_api_url] || 'https://preview.contentful.com/',
247
- space: space,
248
- access_token: preview_token
249
- )
250
- end
251
-
252
- def client_type
253
- 'preview'
254
- end
255
- end
256
151
  end
257
152
  end
@@ -50,30 +50,30 @@ module WCC::Contentful::Store
50
50
  # implementation calls into #set and #delete to perform the appropriate
51
51
  # operations in the store.
52
52
  def index(json)
53
+ # This implementation assumes that #delete and #set are individually thread-safe.
54
+ # No mutex is needed so long as the revisions are accurate.
53
55
  # Subclasses can override to do this in a more performant thread-safe way.
54
56
  # Example: postgres_store could do this in a stored procedure for speed
55
- mutex.with_write_lock do
56
- prev =
57
- case type = json.dig('sys', 'type')
58
- when 'DeletedEntry', 'DeletedAsset'
59
- delete(json.dig('sys', 'id'))
60
- else
61
- set(json.dig('sys', 'id'), json)
62
- end
63
-
64
- if (prev_rev = prev&.dig('sys', 'revision')) && (next_rev = json.dig('sys', 'revision'))
65
- if next_rev < prev_rev
66
- # Uh oh! we overwrote an entry with a prior revision. Put the previous back.
67
- return index(prev)
68
- end
69
- end
70
-
71
- case type
57
+ prev =
58
+ case type = json.dig('sys', 'type')
72
59
  when 'DeletedEntry', 'DeletedAsset'
73
- nil
60
+ delete(json.dig('sys', 'id'))
74
61
  else
75
- json
62
+ set(json.dig('sys', 'id'), json)
76
63
  end
64
+
65
+ if (prev_rev = prev&.dig('sys', 'revision')) &&
66
+ (next_rev = json.dig('sys', 'revision')) &&
67
+ (next_rev < prev_rev)
68
+ # Uh oh! we overwrote an entry with a prior revision. Put the previous back.
69
+ return index(prev)
70
+ end
71
+
72
+ case type
73
+ when 'DeletedEntry', 'DeletedAsset'
74
+ nil
75
+ else
76
+ json
77
77
  end
78
78
  end
79
79
 
@@ -107,17 +107,9 @@ module WCC::Contentful::Store
107
107
  )
108
108
  end
109
109
 
110
- def initialize
111
- @mutex = Concurrent::ReentrantReadWriteLock.new
112
- end
113
-
114
110
  def ensure_hash(val)
115
111
  raise ArgumentError, 'Value must be a Hash' unless val.is_a?(Hash)
116
112
  end
117
-
118
- private
119
-
120
- attr_reader :mutex
121
113
  end
122
114
  end
123
115