wcc-contentful 0.1.0 → 0.2.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +8 -1
  6. data/Guardfile +23 -1
  7. data/app/controllers/wcc/contentful/application_controller.rb +7 -0
  8. data/app/controllers/wcc/contentful/webhook_controller.rb +30 -0
  9. data/app/jobs/wcc/contentful/delayed_sync_job.rb +14 -0
  10. data/bin/rails +14 -0
  11. data/config/initializers/mime_types.rb +3 -0
  12. data/config/routes.rb +5 -0
  13. data/lib/generators/wcc/menu_generator.rb +4 -4
  14. data/lib/generators/wcc/templates/contentful_shell_wrapper +109 -68
  15. data/lib/generators/wcc/templates/menu/menu.rb +4 -6
  16. data/lib/generators/wcc/templates/menu/menu_button.rb +4 -6
  17. data/lib/generators/wcc/templates/release +2 -2
  18. data/lib/generators/wcc/templates/wcc_contentful.rb +0 -1
  19. data/lib/wcc/contentful/client_ext.rb +1 -1
  20. data/lib/wcc/contentful/configuration.rb +76 -35
  21. data/lib/wcc/contentful/engine.rb +13 -0
  22. data/lib/wcc/contentful/exceptions.rb +6 -0
  23. data/lib/wcc/contentful/graphql/builder.rb +8 -3
  24. data/lib/wcc/contentful/helpers.rb +6 -0
  25. data/lib/wcc/contentful/indexed_representation.rb +31 -0
  26. data/lib/wcc/contentful/model.rb +82 -1
  27. data/lib/wcc/contentful/model_builder.rb +18 -8
  28. data/lib/wcc/contentful/model_validators.rb +69 -18
  29. data/lib/wcc/contentful/simple_client/http_adapter.rb +15 -0
  30. data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +30 -0
  31. data/lib/wcc/contentful/simple_client.rb +67 -18
  32. data/lib/wcc/contentful/store/base.rb +89 -0
  33. data/lib/wcc/contentful/store/cdn_adapter.rb +13 -19
  34. data/lib/wcc/contentful/store/lazy_cache_store.rb +76 -0
  35. data/lib/wcc/contentful/store/memory_store.rb +17 -23
  36. data/lib/wcc/contentful/store/postgres_store.rb +32 -19
  37. data/lib/wcc/contentful/store.rb +62 -0
  38. data/lib/wcc/contentful/version.rb +1 -1
  39. data/lib/wcc/contentful.rb +113 -24
  40. data/wcc-contentful.gemspec +4 -0
  41. metadata +75 -2
@@ -15,29 +15,80 @@ require 'wcc/contentful/model_validators'
15
15
  require 'wcc/contentful/model'
16
16
  require 'wcc/contentful/model_builder'
17
17
 
18
+ ##
19
+ # The root namespace of the wcc-contentful gem
20
+ #
21
+ # Initialize the gem with the `configure` and `init` methods inside your
22
+ # initializer.
18
23
  module WCC::Contentful
19
24
  class << self
25
+ ##
26
+ # Gets the current configuration, after calling WCC::Contentful.configure
20
27
  attr_reader :configuration
28
+
29
+ ##
30
+ # Gets the sync token that was returned by the Contentful CDN after the most
31
+ # recent invocation of WCC::Contentful.sync!
32
+ attr_reader :next_sync_token
21
33
  end
22
34
 
35
+ ##
36
+ # Gets a {CDN Client}[rdoc-ref:WCC::Contentful::SimpleClient::Cdn] which provides
37
+ # methods for getting and paging raw JSON data from the Contentful CDN.
23
38
  def self.client
24
39
  configuration&.client
25
40
  end
26
41
 
42
+ ##
43
+ # Gets the data-store which executes the queries run against the dynamic
44
+ # models in the WCC::Contentful::Model namespace.
45
+ # This is one of the following based on the configured content_delivery method:
46
+ #
47
+ # [:direct] an instance of WCC::Contentful::Store::CDNAdapter with a
48
+ # {CDN Client}[rdoc-ref:WCC::Contentful::SimpleClient::Cdn] to access the CDN.
49
+ #
50
+ # [:lazy_sync] an instance of WCC::Contentful::Store::LazyCacheStore
51
+ # with the configured ActiveSupport::Cache implementation and a
52
+ # {CDN Client}[rdoc-ref:WCC::Contentful::SimpleClient::Cdn] for when data
53
+ # cannot be found in the cache.
54
+ #
55
+ # [:eager_sync] an instance of the configured Store type, defined by
56
+ # WCC::Contentful::Configuration.sync_store
57
+ #
58
+ def self.store
59
+ WCC::Contentful::Model.store
60
+ end
61
+
62
+ ##
63
+ # Configures the WCC::Contentful gem to talk to a Contentful space.
64
+ # This must be called first in your initializer, before #init! or accessing the
65
+ # client.
27
66
  def self.configure
28
67
  @configuration ||= Configuration.new
68
+ @next_sync_token = nil
29
69
  yield(configuration)
30
70
 
31
- configuration.configure_contentful
32
-
33
- raise ArgumentError, 'Please provide "space" ID' unless configuration.space.present?
71
+ raise ArgumentError, 'Please provide "space"' unless configuration.space.present?
34
72
  raise ArgumentError, 'Please provide "access_token"' unless configuration.access_token.present?
35
73
 
74
+ configuration.configure_contentful
75
+
36
76
  configuration
37
77
  end
38
78
 
79
+ ##
80
+ # Initializes the WCC::Contentful model-space and backing store.
81
+ # This populates the WCC::Contentful::Model namespace with Ruby classes
82
+ # that represent content types in the configured Contentful space.
83
+ #
84
+ # These content types can be queried directly:
85
+ # WCC::Contentful::Model::Page.find('1xab...')
86
+ # Or you can inherit from them in your own app:
87
+ # class Page < WCC::Contentful::Model.page; end
88
+ # Page.find_by(slug: 'about-us')
39
89
  def self.init!
40
90
  raise ArgumentError, 'Please first call WCC:Contentful.configure' if configuration.nil?
91
+ @mutex ||= Mutex.new
41
92
 
42
93
  # we want as much as possible the raw JSON from the API
43
94
  content_types_resp =
@@ -54,18 +105,12 @@ module WCC::Contentful
54
105
  end
55
106
  @types = indexer.types
56
107
 
57
- case configuration.content_delivery
58
- when :eager_sync
59
- store = configuration.sync_store
108
+ store = configuration.store
109
+ WCC::Contentful::Model.store = store
60
110
 
61
- client.sync(initial: true).items.each do |item|
62
- # TODO: enrich existing type data using Sync::Indexer
63
- store.index(item.dig('sys', 'id'), item)
64
- end
65
- WCC::Contentful::Model.store = store
66
- when :direct
67
- store = Store::CDNAdapter.new(client)
68
- WCC::Contentful::Model.store = store
111
+ if store.respond_to?(:index)
112
+ @next_sync_token = store.find("sync:#{configuration.space}:token")
113
+ sync!
69
114
  end
70
115
 
71
116
  WCC::Contentful::ModelBuilder.new(@types).build_models
@@ -77,26 +122,70 @@ module WCC::Contentful
77
122
  end
78
123
 
79
124
  return unless defined?(Rails)
80
- Dir[Rails.root.join('lib/wcc/contentful/model/**/*.rb')].each { |file| require file }
125
+
126
+ # load up the engine so it gets automatically mounted
127
+ require 'wcc/contentful/engine'
81
128
  end
82
129
 
130
+ ##
131
+ # Runs validations over the content types returned from the Contentful API.
132
+ # Validations are configured on predefined model classes using the
133
+ # `validate_field` directive. Example:
134
+ # validate_field :top_button, :Link, :optional, link_to: 'menuButton'
135
+ # This results in a WCC::Contentful::ValidationError
136
+ # if the 'topButton' field in the 'menu' content type is not a link.
83
137
  def self.validate_models!
84
- schema =
85
- Dry::Validation.Schema do
86
- WCC::Contentful::Model.all_models.each do |klass|
87
- next unless klass.schema
88
- ct = klass.try(:content_type) || klass.name.demodulize
89
- required(ct).schema(klass.schema)
90
- end
91
- end
138
+ # Ensure application models are loaded before we validate
139
+ Dir[Rails.root.join('app/models/**/*.rb')].each { |file| require file } if defined?(Rails)
92
140
 
93
141
  content_types = WCC::Contentful::ModelValidators.transform_content_types_for_validation(
94
142
  @content_types
95
143
  )
96
- errors = schema.call(content_types)
144
+ errors = WCC::Contentful::Model.schema.call(content_types)
97
145
  raise WCC::Contentful::ValidationError, errors.errors unless errors.success?
98
146
  end
99
147
 
148
+ ##
149
+ # Calls the Contentful Sync API and updates the configured store with the returned
150
+ # data.
151
+ #
152
+ # up_to_id: An ID that we know has changed and should come back from the sync.
153
+ # If we don't find this ID in the sync data, then drop a job to try
154
+ # the sync again after a few minutes.
155
+ #
156
+ def self.sync!(up_to_id: nil)
157
+ return unless store.respond_to?(:index)
158
+
159
+ @mutex.synchronize do
160
+ sync_resp = client.sync(sync_token: next_sync_token)
161
+
162
+ id_found = up_to_id.nil?
163
+
164
+ sync_resp.items.each do |item|
165
+ id = item.dig('sys', 'id')
166
+ id_found ||= id == up_to_id
167
+ store.index(item)
168
+ end
169
+ store.set("sync:#{configuration.space}:token", sync_resp.next_sync_token)
170
+ @next_sync_token = sync_resp.next_sync_token
171
+
172
+ unless id_found
173
+ raise SyncError, "ID '#{up_to_id}' did not come back via sync." unless defined?(Rails)
174
+ sync_later!(up_to_id: up_to_id)
175
+ end
176
+ next_sync_token
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Drops an ActiveJob job to invoke WCC::Contentful.sync! after a given amount
182
+ # of time.
183
+ def self.sync_later!(up_to_id: nil, wait: 10.minutes)
184
+ raise NotImplementedError, 'Cannot sync_later! outside of a Rails app' unless defined?(Rails)
185
+
186
+ WCC::Contentful::DelayedSyncJob.set(wait: wait).perform_later(up_to_id)
187
+ end
188
+
100
189
  # TODO: https://zube.io/watermarkchurch/development/c/2234 init graphql
101
190
  # def self.init_graphql!
102
191
  # require 'wcc/contentful/graphql'
@@ -42,14 +42,18 @@ Gem::Specification.new do |spec|
42
42
  spec.add_development_dependency 'generator_spec', '~> 0.9.4'
43
43
  spec.add_development_dependency 'rails', '~> 5.1'
44
44
  spec.add_development_dependency 'rspec-rails', '~> 3.7'
45
+ spec.add_development_dependency 'sqlite3'
45
46
  spec.add_development_dependency 'timecop', '~> 0.9.1'
46
47
 
47
48
  # optional dependencies
48
49
  spec.add_development_dependency 'contentful', '>= 0.12.0'
49
50
  spec.add_development_dependency 'contentful-management', '>= 1.10.0'
50
51
  spec.add_development_dependency 'graphql', '~> 1.7'
52
+ spec.add_development_dependency 'http', '> 1.0', '< 3.0'
51
53
  spec.add_development_dependency 'pg', '~> 1.0'
54
+ spec.add_development_dependency 'typhoeus', '~> 1.3'
52
55
 
53
56
  spec.add_dependency 'activesupport', '>= 5'
54
57
  spec.add_dependency 'dry-validation', '~> 0.11.1'
58
+ spec.add_dependency 'wcc-base', '~> 0.3.1'
55
59
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wcc-contentful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Watermark Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-19 00:00:00.000000000 Z
11
+ date: 2018-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -206,6 +206,20 @@ dependencies:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
208
  version: '3.7'
209
+ - !ruby/object:Gem::Dependency
210
+ name: sqlite3
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
209
223
  - !ruby/object:Gem::Dependency
210
224
  name: timecop
211
225
  requirement: !ruby/object:Gem::Requirement
@@ -262,6 +276,26 @@ dependencies:
262
276
  - - "~>"
263
277
  - !ruby/object:Gem::Version
264
278
  version: '1.7'
279
+ - !ruby/object:Gem::Dependency
280
+ name: http
281
+ requirement: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - ">"
284
+ - !ruby/object:Gem::Version
285
+ version: '1.0'
286
+ - - "<"
287
+ - !ruby/object:Gem::Version
288
+ version: '3.0'
289
+ type: :development
290
+ prerelease: false
291
+ version_requirements: !ruby/object:Gem::Requirement
292
+ requirements:
293
+ - - ">"
294
+ - !ruby/object:Gem::Version
295
+ version: '1.0'
296
+ - - "<"
297
+ - !ruby/object:Gem::Version
298
+ version: '3.0'
265
299
  - !ruby/object:Gem::Dependency
266
300
  name: pg
267
301
  requirement: !ruby/object:Gem::Requirement
@@ -276,6 +310,20 @@ dependencies:
276
310
  - - "~>"
277
311
  - !ruby/object:Gem::Version
278
312
  version: '1.0'
313
+ - !ruby/object:Gem::Dependency
314
+ name: typhoeus
315
+ requirement: !ruby/object:Gem::Requirement
316
+ requirements:
317
+ - - "~>"
318
+ - !ruby/object:Gem::Version
319
+ version: '1.3'
320
+ type: :development
321
+ prerelease: false
322
+ version_requirements: !ruby/object:Gem::Requirement
323
+ requirements:
324
+ - - "~>"
325
+ - !ruby/object:Gem::Version
326
+ version: '1.3'
279
327
  - !ruby/object:Gem::Dependency
280
328
  name: activesupport
281
329
  requirement: !ruby/object:Gem::Requirement
@@ -304,6 +352,20 @@ dependencies:
304
352
  - - "~>"
305
353
  - !ruby/object:Gem::Version
306
354
  version: 0.11.1
355
+ - !ruby/object:Gem::Dependency
356
+ name: wcc-base
357
+ requirement: !ruby/object:Gem::Requirement
358
+ requirements:
359
+ - - "~>"
360
+ - !ruby/object:Gem::Version
361
+ version: 0.3.1
362
+ type: :runtime
363
+ prerelease: false
364
+ version_requirements: !ruby/object:Gem::Requirement
365
+ requirements:
366
+ - - "~>"
367
+ - !ruby/object:Gem::Version
368
+ version: 0.3.1
307
369
  description: Contentful API wrapper library for Watermark apps
308
370
  email:
309
371
  - dev@watermark.org
@@ -324,9 +386,15 @@ files:
324
386
  - LICENSE.txt
325
387
  - README.md
326
388
  - Rakefile
389
+ - app/controllers/wcc/contentful/application_controller.rb
390
+ - app/controllers/wcc/contentful/webhook_controller.rb
391
+ - app/jobs/wcc/contentful/delayed_sync_job.rb
327
392
  - bin/console
393
+ - bin/rails
328
394
  - bin/rspec
329
395
  - bin/setup
396
+ - config/initializers/mime_types.rb
397
+ - config/routes.rb
330
398
  - lib/generators/wcc/USAGE
331
399
  - lib/generators/wcc/menu_generator.rb
332
400
  - lib/generators/wcc/templates/.keep
@@ -341,6 +409,7 @@ files:
341
409
  - lib/wcc/contentful/client_ext.rb
342
410
  - lib/wcc/contentful/configuration.rb
343
411
  - lib/wcc/contentful/content_type_indexer.rb
412
+ - lib/wcc/contentful/engine.rb
344
413
  - lib/wcc/contentful/exceptions.rb
345
414
  - lib/wcc/contentful/graphql.rb
346
415
  - lib/wcc/contentful/graphql/builder.rb
@@ -354,9 +423,13 @@ files:
354
423
  - lib/wcc/contentful/model_validators.rb
355
424
  - lib/wcc/contentful/model_validators/dsl.rb
356
425
  - lib/wcc/contentful/simple_client.rb
426
+ - lib/wcc/contentful/simple_client/http_adapter.rb
357
427
  - lib/wcc/contentful/simple_client/response.rb
428
+ - lib/wcc/contentful/simple_client/typhoeus_adapter.rb
358
429
  - lib/wcc/contentful/store.rb
430
+ - lib/wcc/contentful/store/base.rb
359
431
  - lib/wcc/contentful/store/cdn_adapter.rb
432
+ - lib/wcc/contentful/store/lazy_cache_store.rb
360
433
  - lib/wcc/contentful/store/memory_store.rb
361
434
  - lib/wcc/contentful/store/postgres_store.rb
362
435
  - lib/wcc/contentful/version.rb