wcc-contentful 0.2.2 → 0.3.0.pre.rc

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/README.md +181 -8
  4. data/app/controllers/wcc/contentful/webhook_controller.rb +42 -2
  5. data/app/jobs/wcc/contentful/delayed_sync_job.rb +52 -3
  6. data/app/jobs/wcc/contentful/webhook_enable_job.rb +43 -0
  7. data/bin/console +4 -3
  8. data/bin/rails +2 -0
  9. data/config/initializers/mime_types.rb +10 -1
  10. data/lib/wcc/contentful.rb +14 -142
  11. data/lib/wcc/contentful/client_ext.rb +17 -4
  12. data/lib/wcc/contentful/configuration.rb +25 -84
  13. data/lib/wcc/contentful/engine.rb +19 -0
  14. data/lib/wcc/contentful/exceptions.rb +25 -28
  15. data/lib/wcc/contentful/graphql.rb +0 -1
  16. data/lib/wcc/contentful/graphql/types.rb +1 -1
  17. data/lib/wcc/contentful/helpers.rb +3 -2
  18. data/lib/wcc/contentful/indexed_representation.rb +6 -0
  19. data/lib/wcc/contentful/model.rb +68 -34
  20. data/lib/wcc/contentful/model_builder.rb +65 -67
  21. data/lib/wcc/contentful/model_methods.rb +189 -0
  22. data/lib/wcc/contentful/model_singleton_methods.rb +83 -0
  23. data/lib/wcc/contentful/services.rb +146 -0
  24. data/lib/wcc/contentful/simple_client.rb +35 -33
  25. data/lib/wcc/contentful/simple_client/http_adapter.rb +9 -0
  26. data/lib/wcc/contentful/simple_client/management.rb +81 -0
  27. data/lib/wcc/contentful/simple_client/response.rb +61 -37
  28. data/lib/wcc/contentful/simple_client/typhoeus_adapter.rb +12 -0
  29. data/lib/wcc/contentful/store.rb +45 -18
  30. data/lib/wcc/contentful/store/base.rb +128 -8
  31. data/lib/wcc/contentful/store/cdn_adapter.rb +92 -22
  32. data/lib/wcc/contentful/store/lazy_cache_store.rb +94 -9
  33. data/lib/wcc/contentful/store/memory_store.rb +13 -8
  34. data/lib/wcc/contentful/store/postgres_store.rb +44 -11
  35. data/lib/wcc/contentful/sys.rb +28 -0
  36. data/lib/wcc/contentful/version.rb +1 -1
  37. data/wcc-contentful.gemspec +3 -9
  38. metadata +87 -107
  39. data/.circleci/config.yml +0 -51
  40. data/.gitignore +0 -26
  41. data/.rubocop.yml +0 -243
  42. data/.rubocop_todo.yml +0 -13
  43. data/.travis.yml +0 -5
  44. data/CHANGELOG.md +0 -45
  45. data/CODE_OF_CONDUCT.md +0 -74
  46. data/Guardfile +0 -58
  47. data/LICENSE.txt +0 -21
  48. data/Rakefile +0 -8
  49. data/lib/generators/wcc/USAGE +0 -24
  50. data/lib/generators/wcc/model_generator.rb +0 -90
  51. data/lib/generators/wcc/templates/.keep +0 -0
  52. data/lib/generators/wcc/templates/Procfile +0 -3
  53. data/lib/generators/wcc/templates/contentful_shell_wrapper +0 -385
  54. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +0 -90
  55. data/lib/generators/wcc/templates/menu/models/menu.rb +0 -23
  56. data/lib/generators/wcc/templates/menu/models/menu_button.rb +0 -23
  57. data/lib/generators/wcc/templates/page/generated_add_pages.ts +0 -50
  58. data/lib/generators/wcc/templates/page/models/page.rb +0 -23
  59. data/lib/generators/wcc/templates/release +0 -9
  60. data/lib/generators/wcc/templates/wcc_contentful.rb +0 -17
  61. data/lib/wcc/contentful/model/menu.rb +0 -7
  62. data/lib/wcc/contentful/model/menu_button.rb +0 -15
  63. data/lib/wcc/contentful/model/page.rb +0 -8
  64. data/lib/wcc/contentful/model/redirect.rb +0 -19
  65. data/lib/wcc/contentful/model_validators.rb +0 -115
  66. data/lib/wcc/contentful/model_validators/dsl.rb +0 -165
@@ -1,90 +0,0 @@
1
-
2
- import Migration from 'contentful-migration-cli'
3
-
4
- export = function (migration: Migration) {
5
- const menu = migration.createContentType('menu')
6
- .name('Menu')
7
- .description('A Menu contains a number of Menu Buttons or other Menus, which ' +
8
- 'will be rendered as drop-downs.')
9
- .displayField('name')
10
-
11
- menu.createField('name')
12
- .name('Menu Name')
13
- .type('Symbol')
14
- .required(true)
15
-
16
- menu.createField('topButton')
17
- .name('Top Button')
18
- .type('Link')
19
- .linkType('Entry')
20
- .validations([
21
- {
22
- linkContentType: [ 'menuButton' ],
23
- message: 'The Top Button must be a button linking to a URL or page. ' +
24
- 'If the menu is a dropdown, this button is visible when it is collapsed.'
25
- }
26
- ])
27
-
28
- menu.createField('items')
29
- .name('Items')
30
- .type('Array')
31
- .items({
32
- type: 'Link',
33
- linkType: 'Entry',
34
- validations: [
35
- {
36
- linkContentType: [ 'menu', 'menuButton' ],
37
- message: 'The items must be either buttons or drop-down menus'
38
- }
39
- ]
40
- })
41
-
42
- const menuButton = migration.createContentType('menuButton')
43
- .name('Menu Button')
44
- .description('A Menu Button is a clickable button that goes on a Menu. ' +
45
- 'It has a link to a Page or a URL.')
46
- .displayField('text')
47
-
48
- menuButton.createField('text')
49
- .name('Text')
50
- .type('Symbol')
51
- .required(true)
52
- .validations([
53
- {
54
- size: { min: 1, max: 60 },
55
- message: 'A Menu Button should have a very short text field - ideally a ' +
56
- 'single word. Please limit the text to 60 characters.'
57
- }
58
- ])
59
-
60
- menuButton.createField('icon')
61
- .name('Icon')
62
- .type('Link')
63
- .linkType('Asset')
64
- .validations([
65
- {
66
- linkMimetypeGroup: ['image']
67
- }
68
- ])
69
-
70
- menuButton.createField('externalLink')
71
- .name('External Link')
72
- .type('Symbol')
73
- .validations([
74
- {
75
- regexp: { pattern: "^(ftp|http|https):\\/\\/(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(\\/|\\/([\\w#!:.?+=&%@!\\-\\/]))?$" },
76
- message: "The external link must be a URL like 'https://www.watermark.org/'"
77
- }
78
- ])
79
-
80
- menuButton.createField('link')
81
- .name('Page Link')
82
- .type('Link')
83
- .linkType('Entry')
84
- .validations([
85
- {
86
- linkContentType: [ 'page' ],
87
- message: 'The Page Link must be a link to a Page which has a slug.'
88
- }
89
- ])
90
- }
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This model represents the 'menu' content type in Contentful. Any linked
4
- # entries of the 'menu' content type will be resolved as instances of this class.
5
- # It exposes #find, #find_by, and #find_all methods to query Contentful.
6
- class Menu < WCC::Contentful::Model::Menu
7
- # Add custom validations to ensure that app-specific properties exist:
8
- # validate_field :foo, :String, :required
9
- # validate_field :bar_links, :Array, link_to: %w[bar baz]
10
-
11
- # Override functionality or add utilities
12
- #
13
- # # Example: override equality
14
- # def ===(other)
15
- # ...
16
- # end
17
- #
18
- # # Example: override "name" attribute to always be camelized.
19
- # # `@name` is populated by the gem in the initializer.
20
- # def name
21
- # @name_camelized ||= @name.camelize(true)
22
- # end
23
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This model represents the 'menuButton' content type in Contentful. Any linked
4
- # entries of the 'menuButton' content type will be resolved as instances of this class.
5
- # It exposes #find, #find_by, and #find_all methods to query Contentful.
6
- class MenuButton < WCC::Contentful::Model::MenuButton
7
- # Add custom validations to ensure that app-specific properties exist:
8
- # validate_field :foo, :String, :required
9
- # validate_field :bar_links, :Array, link_to: %w[bar baz]
10
-
11
- # Override functionality or add utilities
12
- #
13
- # # Example: override equality
14
- # def ===(other)
15
- # ...
16
- # end
17
- #
18
- # # Example: override "text" attribute to always be camelized.
19
- # # `@text` is populated by the gem in the initializer.
20
- # def text
21
- # @text_camelized ||= @text.camelize(true)
22
- # end
23
- end
@@ -1,50 +0,0 @@
1
- import Migration from 'contentful-migration-cli'
2
-
3
- export = function (migration: Migration) {
4
- const page = migration.createContentType('page')
5
- .name('Page')
6
- .description('A page describes a collection of sections that correspond' +
7
- 'to a URL slug')
8
- .displayField('title')
9
-
10
- page.createField('title')
11
- .name('Title')
12
- .type('Symbol')
13
- .required(true)
14
-
15
- page.createField('slug')
16
- .name('Slug')
17
- .type('Symbol')
18
- .required(true)
19
- .validations([
20
- {
21
- unique: true
22
- },
23
- {
24
- regexp: { pattern: "(\\/|\\/([\w#!:.?+=&%@!\\-\\/]))?$" },
25
- message: "The slug must look like the path part of a URL and begin with a forward slash, example: '/my-page-slug'"
26
- }
27
- ])
28
-
29
- page.createField('sections')
30
- .name('Sections')
31
- .type('Array')
32
- .items({
33
- type: 'Link',
34
- linkType: 'Entry'
35
- })
36
-
37
- page.createField('subpages')
38
- .name('Subpages')
39
- .type('Array')
40
- .items({
41
- type: 'Link',
42
- linkType: 'Entry',
43
- validations: [
44
- {
45
- linkContentType: [ 'page' ]
46
- }
47
- ]
48
- })
49
-
50
- }
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # This model represents the 'page' content type in Contentful. Any linked
4
- # entries of the 'page' content type will be resolved as instances of this class.
5
- # It exposes #find, #find_by, and #find_all methods to query Contentful.
6
- class Page < WCC::Contentful::Model::Page
7
- # Add custom validations to ensure that app-specific properties exist:
8
- # validate_field :foo, :String, :required
9
- # validate_field :bar_links, :Array, link_to: %w[bar baz]
10
-
11
- # Override functionality or add utilities
12
- #
13
- # # Example: override equality
14
- # def ===(other)
15
- # ...
16
- # end
17
- #
18
- # # Example: override "title" attribute to always be titlecase.
19
- # # `@title` is populated by the gem in the initializer.
20
- # def title
21
- # @title_titlecased ||= @title.titlecase
22
- # end
23
- end
@@ -1,9 +0,0 @@
1
- #!/bin/sh
2
-
3
- set -e
4
-
5
- echo "Migrating database..."
6
- bundle exec rake db:migrate
7
-
8
- DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
9
- $DIR/contentful migrate -y
@@ -1,17 +0,0 @@
1
-
2
- WCC::Contentful.configure do |config|
3
- # Required
4
- config.access_token = # Contentful CDN access token
5
- config.space = # Contentful Space ID
6
-
7
- # Optional
8
- config.management_token = # Contentful API management token
9
- config.default_locale = # Set default locale, if left blank this is 'en-US'
10
- config.content_delivery = # :direct, :eager_sync, or :lazy_sync
11
- end
12
-
13
- # Download content types, build models, and sync content
14
- WCC::Contentful.init!
15
-
16
- # Validate that models conform to a defined specification
17
- WCC::Contentful.validate_models! unless defined?(Rails) && Rails.env.development?
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WCC::Contentful::Model::Menu < WCC::Contentful::Model
4
- validate_field :name, :String
5
- validate_field :top_button, :Link, :optional, link_to: 'menuButton'
6
- validate_field :items, :Array, link_to: %w[menu menuButton]
7
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WCC::Contentful::Model::MenuButton < WCC::Contentful::Model
4
- validate_field :text, :String, :required
5
- validate_field :icon, :Asset, :optional
6
- validate_field :external_link, :String, :optional
7
- validate_field :link, :Link, :optional, link_to: 'page'
8
-
9
- # Gets either the external link or the slug from the referenced page.
10
- # Example usage: `<%= link_to button.title, button.href %>`
11
- def href
12
- return external_link if external_link
13
- link&.try(:slug) || link&.try(:url)
14
- end
15
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WCC::Contentful::Model::Page < WCC::Contentful::Model
4
- validate_field :title, :String
5
- validate_field :slug, :String
6
- validate_field :subpages, :Array, link_to: %w[page]
7
- validate_field :sections, :Array, link_to: /^section/
8
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class WCC::Contentful::Model::Redirect < WCC::Contentful::Model
4
- def href
5
- if !url.nil?
6
- url
7
- elsif valid_page_reference?(pageReference)
8
- "/#{pageReference.url}"
9
- end
10
- end
11
-
12
- def valid_page_reference?(page_ref)
13
- if !page_ref.nil? && !defined?(page_ref.url).nil?
14
- true
15
- else
16
- false
17
- end
18
- end
19
- end
@@ -1,115 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry-validation'
4
-
5
- require_relative 'model_validators/dsl'
6
-
7
- module WCC::Contentful::ModelValidators
8
- def schema
9
- return if validations.nil? || validations.empty?
10
-
11
- all_field_validations =
12
- validations.each_with_object({}) do |(content_type, procs), h|
13
- next if procs.empty?
14
-
15
- # "page": {
16
- # "sys": { ... }
17
- # "fields": {
18
- # "title": { ... },
19
- # "sections": { ... },
20
- # ...
21
- # }
22
- # }
23
- h[content_type] =
24
- Dry::Validation.Schema do
25
- # Had to dig through the internals of Dry::Validation to find
26
- # this magic incantation
27
- procs.each { |dsl| instance_eval(&dsl.to_proc) }
28
- end
29
- end
30
-
31
- Dry::Validation.Schema do
32
- all_field_validations.each do |content_type, fields_schema|
33
- required(content_type).schema do
34
- required('fields').schema(fields_schema)
35
- end
36
- end
37
- end
38
- end
39
-
40
- def validations
41
- # This needs to be a class variable so that subclasses defined in application
42
- # code can add to the total package of model validations
43
- # rubocop:disable Style/ClassVars
44
- @@validations ||= {}
45
- # rubocop:enable Style/ClassVars
46
- end
47
-
48
- ##
49
- # Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
50
- # to validate the 'fields' object of a content type.
51
- def validate_fields(&block)
52
- raise ArgumentError, 'validate_fields requires a block' unless block_given?
53
- dsl = ProcDsl.new(Proc.new(&block))
54
-
55
- ct = try(:content_type) || name.demodulize.camelize(:lower)
56
- (validations[ct] ||= []) << dsl
57
- end
58
-
59
- ##
60
- # Validates a single field is of the expected type.
61
- # Type expectations are one of:
62
- #
63
- # [:String] the field type must be `Symbol` or `Text`
64
- # [:Int] the field type must be `Integer`
65
- # [:Float] the field type must be `Number`
66
- # [:DateTime] the field type must be 'Date'
67
- # [:Asset] the field must be a link and the `linkType` must be `Asset`
68
- # [:Link] the field must be a link and the `linkType` must be `Entry`.
69
- # [:Location] the field type must be `Location`
70
- # [:Boolean] the field type must be `Boolean`
71
- # [:Json] the field type must be `Json` - a json blob.
72
- # [:Array] the field must be a List.
73
- #
74
- # Additional validation options can be enforced:
75
- #
76
- # [:required] the 'Required Field' checkbox must be checked
77
- # [:optional] the 'Required Field' checkbox must not be checked
78
- # [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
79
- # checked in the 'Accept only specified entry type' validations
80
- # Example:
81
- # validate_field :button, :Link, link_to: ['button', 'altButton']
82
- #
83
- # [:items] (only `:Array` type) the items of the list must be of the given type.
84
- # Example:
85
- # validate_field :my_strings, :Array, items: :String
86
- #
87
- # Examples:
88
- # see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
89
- def validate_field(field, type, *options)
90
- dsl = FieldDsl.new(field, type, options)
91
-
92
- ct = try(:content_type) || name.demodulize.camelize(:lower)
93
- (validations[ct] ||= []) << dsl
94
- end
95
-
96
- ##
97
- # Accepts a content types response from the API and transforms it
98
- # to be acceptible for the validator.
99
- def self.transform_content_types_for_validation(content_types)
100
- if !content_types.is_a?(Array) && items = content_types.try(:[], 'items')
101
- content_types = items
102
- end
103
-
104
- # Transform the array into a hash keyed by content type ID
105
- content_types.each_with_object({}) do |ct, ct_hash|
106
- # Transform the fields into a hash keyed by field ID
107
- ct['fields'] =
108
- ct['fields'].each_with_object({}) do |f, f_hash|
109
- f_hash[f['id']] = f
110
- end
111
-
112
- ct_hash[ct.dig('sys', 'id')] = ct
113
- end
114
- end
115
- end
@@ -1,165 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module WCC::Contentful::ModelValidators
4
- class ProcDsl
5
- def to_proc
6
- @proc
7
- end
8
-
9
- def initialize(proc)
10
- @proc = proc
11
- end
12
- end
13
-
14
- class FieldDsl
15
- attr_reader :field
16
-
17
- # "sections": {
18
- # "id": "sections",
19
- # "name": "Sections",
20
- # "type": "Array",
21
- # "localized": false,
22
- # "required": false,
23
- # "validations": [],
24
- # "disabled": false,
25
- # "omitted": false,
26
- # "items": {
27
- # "type": "Link",
28
- # "validations": [
29
- # {
30
- # "linkContentType": [
31
- # "Section"
32
- # ]
33
- # }
34
- # ],
35
- # "linkType": "Entry"
36
- # }
37
- # }
38
-
39
- def schema
40
- return @field_schema if @field_schema
41
-
42
- # example: required('type').value(...)
43
- type_pred = parse_type_predicate(@type)
44
-
45
- # example: [required('required').value(eq?: true), ...]
46
- procs =
47
- @options.map do |opt|
48
- if opt.is_a?(Hash)
49
- opt.map { |k, v| parse_option(k, v) }
50
- else
51
- parse_option(opt)
52
- end
53
- end
54
-
55
- @field_schema =
56
- Dry::Validation.Schema do
57
- instance_eval(&type_pred)
58
-
59
- procs.flatten.each { |p| instance_eval(&p) }
60
- end
61
- end
62
-
63
- def to_proc
64
- f = field
65
- s = schema
66
- proc { required(f).schema(s) }
67
- end
68
-
69
- def initialize(field, field_type, options)
70
- @field = field.to_s.camelize(:lower) unless field.is_a?(String)
71
- @type = field_type
72
- @options = options
73
- end
74
-
75
- private
76
-
77
- def parse_type_predicate(type)
78
- case type
79
- when :String
80
- proc { required('type').value(included_in?: %w[Symbol Text]) }
81
- when :Int
82
- proc { required('type').value(eql?: 'Integer') }
83
- when :Float
84
- proc { required('type').value(eql?: 'Number') }
85
- when :DateTime
86
- proc { required('type').value(eql?: 'Date') }
87
- when :Asset
88
- proc {
89
- required('type').value(eql?: 'Link')
90
- required('linkType').value(eql?: 'Asset')
91
- }
92
- else
93
- proc { required('type').value(eql?: type.to_s.camelize) }
94
- end
95
- end
96
-
97
- def parse_option(option, option_arg = nil)
98
- case option
99
- when :required
100
- proc { required('required').value(eql?: true) }
101
- when :optional
102
- proc { required('required').value(eql?: false) }
103
- when :link_to
104
- link_to_proc = parse_field_link_to(option_arg)
105
- return link_to_proc unless @type.to_s.camelize == 'Array'
106
- proc {
107
- required('items').schema do
108
- required('type').value(eql?: 'Link')
109
- instance_eval(&link_to_proc)
110
- end
111
- }
112
- when :items
113
- type_pred = parse_type_predicate(option_arg)
114
- proc {
115
- required('items').schema do
116
- instance_eval(&type_pred)
117
- end
118
- }
119
- else
120
- raise ArgumentError, "unknown validation requirement: #{option}"
121
- end
122
- end
123
-
124
- def parse_field_link_to(option_arg)
125
- raise ArgumentError, 'validation link_to: requires an argument' unless option_arg
126
-
127
- # this works because a Link can only have one validation in its "validations" array -
128
- # this will fail if Contentful ever changes that.
129
-
130
- # the 'validations' schema needs to be optional because if we get the content
131
- # types from the CDN instead of the management API, sometimes the validations
132
- # don't get sent back.
133
-
134
- # "validations": [
135
- # {
136
- # "linkContentType": [
137
- # "section-CardSearch",
138
- # "section-Faq",
139
- # "section-Testimonials",
140
- # "section-VideoHighlight"
141
- # ]
142
- # }
143
- # ]
144
-
145
- if option_arg.is_a?(Regexp)
146
- return proc {
147
- optional('validations').each do
148
- schema do
149
- required('linkContentType').each(format?: option_arg)
150
- end
151
- end
152
- }
153
- end
154
-
155
- option_arg = [option_arg] unless option_arg.is_a?(Array)
156
- proc {
157
- optional('validations').each do
158
- schema do
159
- required('linkContentType').value(eql?: option_arg)
160
- end
161
- end
162
- }
163
- end
164
- end
165
- end