wcc-contentful 0.4.0.pre.beta → 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.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/Guardfile +43 -0
  3. data/README.md +205 -17
  4. data/Rakefile +5 -0
  5. data/app/controllers/wcc/contentful/webhook_controller.rb +25 -24
  6. data/app/jobs/wcc/contentful/webhook_enable_job.rb +36 -2
  7. data/config/routes.rb +1 -1
  8. data/doc-static/wcc-contentful.png +0 -0
  9. data/lib/tasks/download_schema.rake +12 -0
  10. data/lib/wcc/contentful.rb +70 -16
  11. data/lib/wcc/contentful/active_record_shim.rb +72 -0
  12. data/lib/wcc/contentful/configuration.rb +177 -46
  13. data/lib/wcc/contentful/content_type_indexer.rb +14 -0
  14. data/lib/wcc/contentful/downloads_schema.rb +112 -0
  15. data/lib/wcc/contentful/engine.rb +33 -14
  16. data/lib/wcc/contentful/event.rb +171 -0
  17. data/lib/wcc/contentful/events.rb +41 -0
  18. data/lib/wcc/contentful/exceptions.rb +3 -0
  19. data/lib/wcc/contentful/indexed_representation.rb +2 -2
  20. data/lib/wcc/contentful/instrumentation.rb +31 -0
  21. data/lib/wcc/contentful/link.rb +28 -0
  22. data/lib/wcc/contentful/link_visitor.rb +122 -0
  23. data/lib/wcc/contentful/middleware.rb +7 -0
  24. data/lib/wcc/contentful/middleware/store.rb +158 -0
  25. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +114 -0
  26. data/lib/wcc/contentful/model.rb +37 -3
  27. data/lib/wcc/contentful/model_builder.rb +1 -0
  28. data/lib/wcc/contentful/model_methods.rb +40 -15
  29. data/lib/wcc/contentful/model_singleton_methods.rb +47 -30
  30. data/lib/wcc/contentful/rake.rb +4 -0
  31. data/lib/wcc/contentful/rspec.rb +13 -8
  32. data/lib/wcc/contentful/services.rb +61 -27
  33. data/lib/wcc/contentful/simple_client.rb +81 -25
  34. data/lib/wcc/contentful/simple_client/management.rb +43 -10
  35. data/lib/wcc/contentful/simple_client/response.rb +61 -22
  36. data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +17 -17
  37. data/lib/wcc/contentful/store.rb +7 -66
  38. data/lib/wcc/contentful/store/README.md +85 -0
  39. data/lib/wcc/contentful/store/base.rb +34 -119
  40. data/lib/wcc/contentful/store/cdn_adapter.rb +71 -12
  41. data/lib/wcc/contentful/store/factory.rb +186 -0
  42. data/lib/wcc/contentful/store/instrumentation.rb +55 -0
  43. data/lib/wcc/contentful/store/interface.rb +82 -0
  44. data/lib/wcc/contentful/store/memory_store.rb +27 -24
  45. data/lib/wcc/contentful/store/postgres_store.rb +253 -107
  46. data/lib/wcc/contentful/store/postgres_store/schema_1.sql +73 -0
  47. data/lib/wcc/contentful/store/postgres_store/schema_2.sql +21 -0
  48. data/lib/wcc/contentful/store/query.rb +246 -0
  49. data/lib/wcc/contentful/store/query/interface.rb +63 -0
  50. data/lib/wcc/contentful/store/rspec_examples.rb +48 -0
  51. data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +629 -0
  52. data/lib/wcc/contentful/store/rspec_examples/include_param.rb +283 -0
  53. data/lib/wcc/contentful/store/rspec_examples/nested_queries.rb +342 -0
  54. data/lib/wcc/contentful/sync_engine.rb +181 -0
  55. data/lib/wcc/contentful/test/attributes.rb +17 -5
  56. data/lib/wcc/contentful/test/factory.rb +22 -46
  57. data/lib/wcc/contentful/version.rb +1 -1
  58. data/wcc-contentful.gemspec +22 -11
  59. metadata +295 -144
  60. data/Gemfile +0 -6
  61. data/app/jobs/wcc/contentful/delayed_sync_job.rb +0 -63
  62. data/lib/wcc/contentful/client_ext.rb +0 -28
  63. data/lib/wcc/contentful/graphql.rb +0 -14
  64. data/lib/wcc/contentful/graphql/builder.rb +0 -177
  65. data/lib/wcc/contentful/graphql/types.rb +0 -54
  66. data/lib/wcc/contentful/simple_client/http_adapter.rb +0 -24
  67. data/lib/wcc/contentful/store/lazy_cache_store.rb +0 -161
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ff6f351c2042a4a584360d284e9079944fb3345f
4
- data.tar.gz: 511988b09e647cf69c7b6c9accf4098fce140d08
2
+ SHA256:
3
+ metadata.gz: ad34af3480e2e993b8046826ef3ca65b6f28ccf28e57e19cca79061a17766877
4
+ data.tar.gz: e0e214bf19721287af20502260033f595dcf461d2b5d31fd943c838b3ced204e
5
5
  SHA512:
6
- metadata.gz: b3fac31b5af8c0f24e9bb91139946819134f5f68d38dfa2b90c70e30aacd6d9933bee1a716f5717b1d1769118875af4eb0f7ca6657fc6b09b736141c6cf4640d
7
- data.tar.gz: 30141b535a65ade870b48573efee983310ad4c3ff704332f0cee15ceaf8e033e94d4cfa3bda4f42f36e3812fa7be49bf2d807375fa695231cf50b925cdfd92f8
6
+ metadata.gz: 1e67eacbb406c6269c0a43b6b7a1284836a357c9b9ff0ffe297ee188aeea6a2c96107b9a6fc127d2e622223f38797528e18e93485575068d7eae4a73a77f9375
7
+ data.tar.gz: c16f075caa26c9a9a0f0c22fe725eabf0b0c2900f4c58bac33fcb8ad42a10dee09ae257e2a91d71e06e878d1c7b8165dea28b1a5a791480f5c03e328f8635355
data/Guardfile CHANGED
@@ -3,6 +3,39 @@
3
3
 
4
4
  # To run, use `bundle exec guard`.
5
5
 
6
+ def watch_async(regexp)
7
+ raise ArgumentError, "No block given" unless block_given?
8
+ match_queue = Queue.new
9
+
10
+ watch(regexp) do |match|
11
+ # Producer - add matches to the match queue
12
+ match_queue << match
13
+ nil
14
+ end
15
+
16
+ # Consumer - process matches as a batch
17
+ Thread.new do
18
+ loop do
19
+ matches = []
20
+ matches << match_queue.pop
21
+
22
+ loop do
23
+ begin
24
+ matches << match_queue.pop(true)
25
+ rescue ThreadError
26
+ break
27
+ end
28
+ end
29
+
30
+ begin
31
+ yield matches if matches.length > 0
32
+ rescue StandardError => ex
33
+ STDERR.puts "Error! #{ex}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+
6
39
  group :red_green_refactor, halt_on_fail: true do
7
40
  guard :rspec, cmd: 'bundle exec rspec' do
8
41
  require 'guard/rspec/dsl'
@@ -46,6 +79,16 @@ group :red_green_refactor, halt_on_fail: true do
46
79
  watch(%r{.+\.rb$})
47
80
  watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
48
81
  end
82
+
83
+ guard :shell, all_on_start: false do
84
+ watch_async(%r{app/views/(.+\.html.*\.erb)}) { |matches|
85
+
86
+ matches = matches.map { |m| File.absolute_path(m[0]) }
87
+ Dir.chdir('..') {
88
+ system("bundle exec erblint #{matches.join(' ')}")
89
+ }
90
+ }
91
+ end
49
92
  end
50
93
 
51
94
  group :autofix do
data/README.md CHANGED
@@ -1,11 +1,106 @@
1
- [![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://badge.fury.io/rb/wcc-contentful)
2
- [![CircleCI](https://circleci.com/gh/watermarkchurch/wcc-contentful.svg?style=svg)](https://circleci.com/gh/watermarkchurch/wcc-contentful)
1
+ [![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://rubygems.org/gems/wcc-contentful)
2
+ [![Build Status](https://circleci.com/gh/watermarkchurch/wcc-contentful.svg?style=svg)](https://circleci.com/gh/watermarkchurch/wcc-contentful)
3
3
  [![Coverage Status](https://coveralls.io/repos/github/watermarkchurch/wcc-contentful/badge.svg?branch=master)](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master)
4
4
 
5
- Full documentation: https://www.rubydoc.info/github/watermarkchurch/wcc-contentful
5
+ Full documentation: https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/
6
6
 
7
7
  # WCC::Contentful
8
8
 
9
+ An alternative to Contentful's [contentful.rb ruby client](https://github.com/contentful/contentful.rb/), [contentful_model](https://github.com/contentful/contentful_model), and [contentful_rails](https://github.com/contentful/contentful_rails) gems all in one.
10
+
11
+ Table of Contents:
12
+
13
+ 1. [Why?](#why-did-you-rewrite-the-contentful-ruby-stack)
14
+ 2. [Installation](#installation)
15
+ 3. [Configuration](#configure)
16
+ 4. [Usage](#usage)
17
+ 1. [Model API](#wcccontentfulmodel-api)
18
+ 2. [Store API](#store-api)
19
+ 3. [Direct CDN client](#direct-cdn-api-simpleclient)
20
+ 4. [Accessing the APIs](#accessing-the-apis-within-application-code)
21
+ 5. [Architecture](#architecture)
22
+ 6. [Test Helpers](#test-helpers)
23
+ 7. [Advanced Configuration Example](#advanced-configuration-example)
24
+ 8. [Development](#development)
25
+ 9. [Contributing](#contributing)
26
+ 10. [License](#license)
27
+
28
+
29
+ ## Why did you rewrite the Contentful ruby stack?
30
+
31
+ We started working with Contentful almost 3 years ago. Since that time, Contentful's ruby stack has improved, but there are still a number of pain points that we feel we have addressed better with our gem. These are:
32
+
33
+ * [Low-level caching](#low-level-caching)
34
+ * [Better integration with Rails & Rails models](#better-rails-integration)
35
+ * [Automatic pagination and Automatic link resolution](#automatic-pagination-and-link-resolution)
36
+ * [Automatic webhook management](#automatic-webhook-management)
37
+
38
+ Our gem no longer depends on any of the Contentful gems and interacts directly with the [Contentful CDA](https://www.contentful.com/developers/docs/references/content-delivery-api/) and [Content Management API](https://www.contentful.com/developers/docs/references/content-management-api/) over HTTPS.
39
+
40
+ ### Low-level caching
41
+
42
+ The wcc-contentful gem enables caching at two levels: the HTTP response using [Faraday HTTP cache middleware](https://github.com/sourcelevel/faraday-http-cache), and at the Entry level using the Rails cache and the [Sync API](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/synchronization) to keep it up to date. We've found these two cache layers to be very effective at reducing both round trip latency to the Content Delivery API, as well as reducing our monthly API request usage. (which reduces our overage charges. Hooray!)
43
+
44
+ #### At the request/response level
45
+ By default, the contentful.rb gem requires the [HTTP library](https://rubygems.org/gems/http). While simple and straightforward to use, it is not as powerful for caching. We decided to make our client conform to the [Faraday gem's API](https://github.com/lostisland/faraday). If you prefer not to use Faraday, you can choose to supply your own HTTP adapter that "quacks like" Faraday (see the [TyphoeusAdapter](https://github.com/watermarkchurch/wcc-contentful/blob/master/wcc-contentful/lib/wcc/contentful/simple_client/typhoeus_adapter.rb) for one implementation).
46
+
47
+ Using Faraday makes it easy to add Middleware. As an example, our flagship Rails app that powers watermark.org uses the following configuration in Production, which provides us with instrumentation through statsd, logging, and caching:
48
+ ```rb
49
+ config.connection = Faraday.new do |builder|
50
+ builder.use :http_cache,
51
+ shared_cache: false,
52
+ store: ActiveSupport::Cache::MemoryStore.new(size: 512.megabytes),
53
+ logger: Rails.logger,
54
+ serializer: Marshal,
55
+ instrumenter: ActiveSupport::Notifications
56
+
57
+ builder.use :gzip
58
+ builder.response :logger, Rails.logger, headers: false, bodies: false if Rails.env.development?
59
+ builder.request :instrumentation
60
+ builder.adapter :typhoeus
61
+ end
62
+ ```
63
+
64
+ #### At the Entry level
65
+
66
+ Our stack has three layers, the middle layer being essentially a cache for individual Entry hashes parsed out of responses from the Delivery API. We were able to add a caching layer here which stores entries retrieved over the Sync API, and responds to queries with cached versions of local content when possible. We consider this to be our best innovation on the Contentful ruby stack.
67
+
68
+ We have successfully created caching layers using Memcached, Postgres, and an in-memory hash. The architecture allows other caching implementations to be created fairly easily, and we have a set of rspec specs that can verify that a cache store behaves appropriately. For more information, [see the documentation on the caching modes here](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Store.html).
69
+
70
+ ### Better Rails Integration
71
+
72
+ When we initially got started with the Contentful ruby models, we encountered one problem that was more frustrating than all others: If a field exists in the content model, but the particular entry we're working with does not have that field populated, then accessing that field raised a `NoMethodError`. This caused us to litter our code with `if defined?(entry.my_field)` which is bad practice. (Note: this has since been fixed in contentful.rb v2).
73
+
74
+ We decided it was better to not rely on `method_missing?` (what contentful.rb does), and instead to use `define_method` in an initializer to generate the methods for our models. This has the advantage that calling `.instance_methods` on a model class includes all the fields present in the content model.
75
+
76
+ We also took advantage of Rails' naming conventions to automatically infer the content type name based on the class name. Thus in our code, we have `app/models/page.rb` which defines `class Page << WCC::Contentful::Model::Page`, and is automatically linked to the `page` content type ID. (Note: this is overridable on a per-model basis)
77
+
78
+ All our models are automatically generated at startup which improves response times at the expense of initialization time. In addition, our content model registry allows easy definition of custom models in your `app/models` directory to override fields. This plays nice with other gems like algoliasearch-rails, which allows you to declaratively manage your Algolia indexes. Another example from our flagship watermark.org:
79
+
80
+ ```rb
81
+ class Page < WCC::Contentful::Model::Page
82
+ include AlgoliaSearch
83
+
84
+ algoliasearch(index_name: 'pages') do
85
+ attribute(:title, :slug)
86
+ ...
87
+ end
88
+ ```
89
+
90
+ ### Automatic Pagination and Link Resolution
91
+
92
+ Using the `contentful_model` gem, calling `Page.all.load` does not give you all Page entries if there are more than 100. To get the next page you must call `.paginate` on the response. By contrast, `Page.find_all` in the `wcc-contentful` gem gives you a [Lazy Enumerator](https://ruby-doc.org/core-2.5.0/Enumerator/Lazy.html). As you iterate past the 100th entry, the enumerator will automatically fetch the next page. If you only enumerate 99 entries (say with `.take(99)`), then the second page will never be fetched.
93
+
94
+ Similarly, if your Page references an asset, say `hero_image`, that field returns a `Link` object rather than the actual `Asset`. You must either predefine how many links you need using `Page.load_children(3).all.load`, or detect that `hero_image` is a `Link` like `if @page.hero_image.is_a? Contentful::Link` and then call `.resolve` on the link. We found all of that to be too cumbersome when we are down in a nested partial view template that may be invoked from multiple places.
95
+
96
+ The `wcc-contentful` gem, by contrast, automatically resolves a link when accessing the associated attribute. So in our example above, `wcc-contentful` will **always** return a `WCC::Contentful::Asset` when calling `@page.hero_image`, even if it has to execute a query to cdn.contentful.com in order to fetch it.
97
+
98
+ Warning: This can easily lead to you exhausting your Contentful API quota if you do not carefully tune your cache, which you should be doing anyways! The default settings will use the Rails cache to try to cache these resolutions, but *you are ultimately responsible for how many queries you execute!*
99
+
100
+ ### Automatic webhook management
101
+
102
+ The `wcc-contentful` gem, just like `contentful_rails`, provides an Engine to be mounted in your Rails routes file. Unlike `contentful_rails`, if you also configure `wcc-contentful` with a Contentful Management Token and a public `app_url`, then on startup the `wcc-contentful` engine will reach out to the Contentful Management API and ensure that a webhook is configured to point to your app. This is one less devops burden on you, and plays very nicely in with Heroku review apps.
103
+
9
104
  ## Installation
10
105
 
11
106
  Add this line to your application's Gemfile:
@@ -14,17 +109,30 @@ Add this line to your application's Gemfile:
14
109
  gem 'wcc-contentful', require: 'wcc/contentful/rails'
15
110
  ```
16
111
 
112
+ If you're not using rails, exclude the `require:` parameter.
113
+
114
+ ```ruby
115
+ gem 'wcc-contentful'
116
+ ```
117
+
17
118
  And then execute:
18
119
 
19
- $ bundle
120
+ ```
121
+ $ bundle
122
+ ```
20
123
 
21
- Or install it yourself as:
124
+ Or install it yourself:
22
125
 
23
- $ gem install wcc-contentful
126
+ ```
127
+ $ gem install wcc-contentful
128
+ ```
24
129
 
25
130
  ## Configure
26
131
 
132
+ Put this in an initializer:
133
+
27
134
  ```ruby
135
+ # config/initializers/wcc_contentful.rb
28
136
  WCC::Contentful.configure do |config|
29
137
  config.access_token = <CONTENTFUL_ACCESS_TOKEN>
30
138
  config.space = <CONTENTFUL_SPACE_ID>
@@ -33,13 +141,16 @@ end
33
141
  WCC::Contentful.init!
34
142
  ```
35
143
 
144
+ All configuration options can be found [in the rubydoc under
145
+ WCC::Contentful::Configuration](https://watermarkchurch.github.io/wcc-contentful/latest/wcc-contentful/WCC/Contentful/Configuration)
146
+
36
147
  ## Usage
37
148
 
38
149
  ### WCC::Contentful::Model API
39
150
 
40
151
  The WCC::Contentful::Model API exposes Contentful data as a set of dynamically
41
152
  generated Ruby objects. These objects are based on the content types in your
42
- Contentful space. All these objects are generated by WCC::Contentful.init!
153
+ Contentful space. All these objects are generated by `WCC::Contentful.init!`
43
154
 
44
155
  The following examples show how to use this API to find entries of the `page`
45
156
  content type:
@@ -164,7 +275,8 @@ response.includes
164
275
 
165
276
  The client handles Paging automatically within the lazy iterator returned by #items.
166
277
  This lazy iterator does not respect the `limit` param - that param is only passed
167
- through to the API to set the page size.
278
+ through to the API to set the page size. If you truly want a limited subset of
279
+ response items, use [`response.items.take(n)`](https://ruby-doc.org/core-2.5.3/Enumerable.html#method-i-take)
168
280
 
169
281
  Entries included via the `include` parameter are made available on the #includes
170
282
  field. This is a hash of `<entry ID> => <raw entry>` and makes it easy to grab
@@ -211,6 +323,10 @@ class MyJob < ApplicationJob
211
323
  end
212
324
  ```
213
325
 
326
+ ## Architecture
327
+
328
+ ![wcc-contentful diagram](./doc-static/wcc-contentful.png)
329
+
214
330
  ## Test Helpers
215
331
 
216
332
  To use the test helpers, include the following in your rails_helper.rb:
@@ -239,7 +355,7 @@ instance.other_optional_field
239
355
  # => nil
240
356
 
241
357
  instance.not_a_field
242
- # NoMethodError: undefined method `not_a_field' for #<Menu:0x00007fbac81ee490>
358
+ # NoMethodError: undefined method `not_a_field' for #<MyContentType:0x00007fbac81ee490>
243
359
 
244
360
  ##
245
361
  # Builds a rspec double of the Contentful model for the given content_type.
@@ -251,6 +367,9 @@ dbl = contentful_double('my-content-type', my_field: 'other-value')
251
367
  dbl.my_field
252
368
  # => "other-value"
253
369
 
370
+ dbl.other_optional_field
371
+ # => nil
372
+
254
373
  dbl.not_a_field
255
374
  # => #<Double (anonymous)> received unexpected message :not_a_field with (no args)
256
375
 
@@ -270,21 +389,90 @@ MyContentType.find_by(my_field: 'test') == stubbed
270
389
  # => true
271
390
  ```
272
391
 
392
+ ## Advanced Configuration Example
273
393
 
274
- ## Development
394
+ Here's an example containing all the configuration options, and a sample setup for
395
+ automatic deployment to Heroku. This is intended to make you aware of what is possible,
396
+ and not as a general recommendation of what your setup should look like.
275
397
 
276
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
398
+ ```ruby
399
+ # config/initializers/wcc_contentful.rb
400
+ WCC::Contentful.configure do |config|
401
+ config.access_token = ENV['CONTENTFUL_ACCESS_TOKEN']
402
+ config.space = ENV['CONTENTFUL_SPACE_ID']
403
+ config.environment = ENV['CONTENTFUL_ENVIRONMENT']
404
+ config.preview_token = ENV['CONTENTFUL_PREVIEW_ACCESS_TOKEN']
405
+
406
+ # You may or may not want to provide this to your production server...
407
+ config.management_token = ENV['CONTENTFUL_MANAGEMENT_TOKEN'] unless Rails.env.production?
408
+
409
+ config.app_url = "https://#{ENV['HOSTNAME']}"
410
+ config.webhook_username = 'my-app-webhook'
411
+ config.webhook_password = Rails.application.secrets.webhook_password
412
+ config.webhook_jobs << MyOnWebhookJob
413
+
414
+ config.store = :lazy_sync, Rails.cache if Rails.env.production?
415
+ # config.store = MyCustomStore.new
416
+
417
+ # Use a custom Faraday connection
418
+ config.connection = Faraday.new do |builder|
419
+ f.request :retry
420
+ f.request MyFaradayRequestAdapter.new
421
+ ...
422
+ end
423
+ # OR implement some adapter like this to use another HTTP client
424
+ config.connection = MyNetHttpAdapter.new
277
425
 
278
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
426
+ config.update_schema_file = :never
427
+ end
428
+
429
+ WCC::Contentful.init!
430
+ ```
431
+
432
+ For Heroku:
433
+
434
+ ```yaml
435
+ # Procfile
436
+ web: bundle exec rails s
437
+ worker: bundle exec sidekiq
438
+ release: bin/release
439
+ ```
440
+
441
+ ```sh
442
+ # bin/release
443
+ #!/bin/sh
444
+
445
+ set -e
446
+
447
+ echo "Migrating database..."
448
+ bin/rake db:migrate
449
+
450
+ echo "Migrating contentful..."
451
+ migrations_to_be_run=$( ... ) # somehow figure this out
452
+ node_modules/.bin/contentful-migration \
453
+ -s $CONTENTFUL_SPACE_ID -a $CONTENTFUL_MANAGEMENT_TOKEN \
454
+ -y -p "$migrations_to_be_run"
455
+
456
+ echo "Updating schema file..."
457
+ rake wcc_contentful:download_schema
458
+ ```
459
+
460
+ All configuration options can be found [in the rubydoc](https://www.rubydoc.info/gems/wcc-contentful/WCC/Contentful/Configuration) under
461
+ {WCC::Contentful::Configuration}
462
+
463
+
464
+ ## Development
465
+
466
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
279
467
 
280
468
  ## Contributing
281
469
 
282
- Bug reports and pull requests are welcome on GitHub at https://github.com/watermarkchurch/wcc-contentful. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
470
+ Bug reports and pull requests are welcome on GitHub at https://github.com/watermarkchurch/wcc-contentful.
283
471
 
284
- ## License
472
+ The developers at Watermark Community Church have pledged to govern their interactions with each other, with their clients, and with the larger wcc-contentful user community in accordance with the "instruments of good works" from chapter 4 of The Rule of St. Benedict (hereafter: "The Rule"). This code of ethics has proven its mettle in thousands of diverse communities for over 1,500 years, and has served as a baseline for many civil law codes since the time of Charlemagne.
285
473
 
286
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
474
+ [See the full Code of Ethics](https://github.com/watermarkchurch/wcc-contentful/blob/master/CODE_OF_ETHICS.md)
287
475
 
288
- ## Code of Conduct
476
+ ## License
289
477
 
290
- Everyone interacting in the WCC::Contentful project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/watermarkchurch/wcc-contentful/blob/master/CODE_OF_CONDUCT.md).
478
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ task :release do
4
+ raise StandardError, 'Please run rake release only from the root folder.'
5
+ end
@@ -8,6 +8,7 @@ module WCC::Contentful
8
8
  # the jobs configured in {WCC::Contentful::Configuration WCC::Contentful::Configuration#webhook_jobs}
9
9
  class WebhookController < ApplicationController
10
10
  include WCC::Contentful::ServiceAccessors
11
+ include Wisper::Publisher
11
12
 
12
13
  before_action :authorize_contentful
13
14
  protect_from_forgery unless: -> { request.format.json? }
@@ -17,27 +18,17 @@ module WCC::Contentful
17
18
  end
18
19
 
19
20
  def receive
20
- event = params.require('webhook').permit!
21
- event.require('sys').require(%w[id type])
22
- event = event.to_h
23
-
24
- # Immediately update the store, we may update again later using DelayedSyncJob.
25
- store.index(event) if store.respond_to?(:index)
26
-
27
- jobs.each do |job|
28
- begin
29
- if job.respond_to?(:perform_later)
30
- job.perform_later(event)
31
- elsif job.respond_to?(:call)
32
- job.call(event)
33
- else
34
- Rails.logger.error "Misconfigured webhook job: #{job} does not respond to " \
35
- ':perform_later or :call'
36
- end
37
- rescue StandardError => e
38
- Rails.logger.error "Error in job #{job}: #{e}"
39
- end
40
- end
21
+ params.require('sys').require(%w[id type])
22
+ params.permit('sys', 'fields')
23
+ event = params.slice('sys', 'fields').permit!.to_h
24
+
25
+ return unless check_environment(event)
26
+
27
+ # Immediately update the store, we may update again later using SyncEngine::Job.
28
+ store.index(event) if store.index?
29
+
30
+ event = WCC::Contentful::Event.from_raw(event, source: self)
31
+ emit_event(event)
41
32
  end
42
33
 
43
34
  private
@@ -62,9 +53,19 @@ module WCC::Contentful
62
53
  render json: { msg: 'This endpoint only responds to webhooks from Contentful' }, status: 406
63
54
  end
64
55
 
65
- def jobs
66
- jobs = [WCC::Contentful::DelayedSyncJob]
67
- jobs.push(*WCC::Contentful.configuration.webhook_jobs)
56
+ def check_environment(event)
57
+ environment_id = event.dig('sys', 'environment', 'sys', 'id')
58
+ return true unless environment_id.present?
59
+
60
+ configured_environment = WCC::Contentful.configuration.environment.presence || 'master'
61
+ configured_environment.casecmp(environment_id) == 0
62
+ end
63
+
64
+ def emit_event(event)
65
+ type = event.dig('sys', 'type')
66
+ raise ArgumentError, "Unknown event type #{event}" unless type.present?
67
+
68
+ broadcast(type, event)
68
69
  end
69
70
  end
70
71
  end
@@ -7,7 +7,9 @@ module WCC::Contentful
7
7
  self.queue_adapter = :async
8
8
  queue_as :default
9
9
 
10
- def perform(args)
10
+ def perform(args = {})
11
+ args = default_configuration.merge!(args)
12
+
11
13
  client = WCC::Contentful::SimpleClient::Management.new(
12
14
  args
13
15
  )
@@ -26,7 +28,8 @@ module WCC::Contentful
26
28
  'topics' => [
27
29
  '*.publish',
28
30
  '*.unpublish'
29
- ]
31
+ ],
32
+ 'filters' => webhook_filters
30
33
  }
31
34
  body['httpBasicUsername'] = webhook_username if webhook_username.present?
32
35
  body['httpBasicPassword'] = webhook_password if webhook_password.present?
@@ -39,5 +42,36 @@ module WCC::Contentful
39
42
  raise
40
43
  end
41
44
  end
45
+
46
+ private
47
+
48
+ def default_configuration
49
+ return {} unless config = WCC::Contentful&.configuration
50
+
51
+ {
52
+ management_token: config.management_token,
53
+ app_url: config.app_url,
54
+ space: config.space,
55
+ environment: config.environment,
56
+ default_locale: config.default_locale,
57
+ connection: config.connection,
58
+ webhook_username: config.webhook_username,
59
+ webhook_password: config.webhook_password
60
+ }
61
+ end
62
+
63
+ def webhook_filters
64
+ filters = []
65
+
66
+ if (environment_id = WCC::Contentful.configuration&.environment).present?
67
+ filters << {
68
+ 'equals' => [
69
+ { 'doc' => 'sys.environment.sys.id' },
70
+ environment_id
71
+ ]
72
+ }
73
+ end
74
+ filters
75
+ end
42
76
  end
43
77
  end