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.
- checksums.yaml +4 -4
- data/app/controllers/wcc/contentful/webhook_controller.rb +2 -0
- data/app/jobs/wcc/contentful/webhook_enable_job.rb +1 -1
- data/lib/tasks/download_schema.rake +1 -1
- data/lib/wcc/contentful/active_record_shim.rb +2 -2
- data/lib/wcc/contentful/configuration.rb +37 -18
- data/lib/wcc/contentful/content_type_indexer.rb +2 -0
- data/lib/wcc/contentful/downloads_schema.rb +5 -4
- data/lib/wcc/contentful/engine.rb +2 -4
- data/lib/wcc/contentful/event.rb +4 -11
- data/lib/wcc/contentful/exceptions.rb +2 -3
- data/lib/wcc/contentful/indexed_representation.rb +3 -6
- data/lib/wcc/contentful/instrumentation.rb +2 -1
- data/lib/wcc/contentful/link.rb +1 -3
- data/lib/wcc/contentful/link_visitor.rb +2 -4
- data/lib/wcc/contentful/middleware/store/caching_middleware.rb +5 -9
- data/lib/wcc/contentful/middleware/store.rb +4 -6
- data/lib/wcc/contentful/model_api.rb +2 -4
- data/lib/wcc/contentful/model_builder.rb +8 -4
- data/lib/wcc/contentful/model_methods.rb +10 -12
- data/lib/wcc/contentful/model_singleton_methods.rb +2 -2
- data/lib/wcc/contentful/rich_text/node.rb +60 -0
- data/lib/wcc/contentful/rich_text.rb +105 -0
- data/lib/wcc/contentful/rspec.rb +1 -3
- data/lib/wcc/contentful/services.rb +9 -0
- data/lib/wcc/contentful/simple_client/cdn.rb +126 -0
- data/lib/wcc/contentful/simple_client/preview.rb +17 -0
- data/lib/wcc/contentful/simple_client/response.rb +47 -46
- data/lib/wcc/contentful/simple_client.rb +13 -118
- data/lib/wcc/contentful/store/base.rb +19 -27
- data/lib/wcc/contentful/store/cdn_adapter.rb +11 -7
- data/lib/wcc/contentful/store/factory.rb +1 -1
- data/lib/wcc/contentful/store/memory_store.rb +10 -7
- data/lib/wcc/contentful/store/postgres_store.rb +52 -42
- data/lib/wcc/contentful/store/query.rb +2 -2
- data/lib/wcc/contentful/store/rspec_examples/include_param.rb +6 -4
- data/lib/wcc/contentful/store/rspec_examples/operators.rb +1 -1
- data/lib/wcc/contentful/sync_engine.rb +58 -34
- data/lib/wcc/contentful/sys.rb +2 -1
- data/lib/wcc/contentful/test/double.rb +3 -5
- data/lib/wcc/contentful/test/factory.rb +4 -6
- data/lib/wcc/contentful/version.rb +1 -1
- data/lib/wcc/contentful.rb +10 -13
- data/wcc-contentful.gemspec +6 -6
- 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
|
data/lib/wcc/contentful/rspec.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
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
|
-
|
148
|
-
|
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
|
153
|
-
#
|
154
|
-
|
155
|
-
|
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
|
-
|
161
|
-
|
144
|
+
SyncResponse.parse_sync_token(raw['nextPageUrl'] || raw['nextSyncUrl'])
|
145
|
+
end
|
162
146
|
|
163
|
-
|
164
|
-
|
165
|
-
|
147
|
+
def each_page(&block)
|
148
|
+
if block_given?
|
149
|
+
super do |page|
|
150
|
+
@last_sync_token = page.next_sync_token
|
166
151
|
|
167
|
-
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
|