wcc-contentful 0.2.2 → 0.3.0.pre.rc

Sign up to get free protection for your applications and to get access to all the features.
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