wcc-contentful 0.3.0.pre.rc3 → 0.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +51 -0
  3. data/.gitignore +26 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +242 -0
  6. data/.rubocop_todo.yml +19 -0
  7. data/.travis.yml +5 -0
  8. data/CHANGELOG.md +180 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Guardfile +58 -0
  11. data/LICENSE.txt +21 -0
  12. data/Rakefile +8 -0
  13. data/bin/console +3 -4
  14. data/bin/rails +0 -2
  15. data/lib/generators/wcc/USAGE +24 -0
  16. data/lib/generators/wcc/model_generator.rb +90 -0
  17. data/lib/generators/wcc/templates/.keep +0 -0
  18. data/lib/generators/wcc/templates/Procfile +3 -0
  19. data/lib/generators/wcc/templates/contentful_shell_wrapper +385 -0
  20. data/lib/generators/wcc/templates/menu/generated_add_menus.ts +192 -0
  21. data/lib/generators/wcc/templates/menu/models/menu.rb +23 -0
  22. data/lib/generators/wcc/templates/menu/models/menu_button.rb +23 -0
  23. data/lib/generators/wcc/templates/page/generated_add_pages.ts +50 -0
  24. data/lib/generators/wcc/templates/page/models/page.rb +23 -0
  25. data/lib/generators/wcc/templates/release +9 -0
  26. data/lib/generators/wcc/templates/wcc_contentful.rb +17 -0
  27. data/lib/wcc/contentful.rb +32 -2
  28. data/lib/wcc/contentful/exceptions.rb +33 -0
  29. data/lib/wcc/contentful/model.rb +1 -0
  30. data/lib/wcc/contentful/model/dropdown_menu.rb +7 -0
  31. data/lib/wcc/contentful/model/menu.rb +6 -0
  32. data/lib/wcc/contentful/model/menu_button.rb +16 -0
  33. data/lib/wcc/contentful/model/page.rb +8 -0
  34. data/lib/wcc/contentful/model/redirect.rb +19 -0
  35. data/lib/wcc/contentful/model_validators.rb +121 -0
  36. data/lib/wcc/contentful/model_validators/dsl.rb +166 -0
  37. data/lib/wcc/contentful/store/postgres_store.rb +2 -2
  38. data/lib/wcc/contentful/version.rb +1 -1
  39. data/wcc-contentful.gemspec +9 -3
  40. metadata +92 -7
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WCC::Contentful::Model::DropdownMenu < WCC::Contentful::Model
4
+ validate_field :name, :String
5
+ validate_field :label, :Link, link_to: 'menuButton'
6
+ validate_field :items, :Array, link_to: 'menuButton'
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WCC::Contentful::Model::Menu < WCC::Contentful::Model
4
+ validate_field :name, :String
5
+ validate_field :items, :Array, link_to: %w[dropdownMenu menuButton]
6
+ end
@@ -0,0 +1,16 @@
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
+
14
+ link&.try(:slug) || link&.try(:url)
15
+ end
16
+ end
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,121 @@
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
+ # Accepts a block which uses the {dry-validation DSL}[http://dry-rb.org/gems/dry-validation/]
49
+ # to validate the 'fields' object of a content type.
50
+ def validate_fields(&block)
51
+ raise ArgumentError, 'validate_fields requires a block' unless block_given?
52
+
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
+ # Validates a single field is of the expected type.
60
+ # Type expectations are one of:
61
+ #
62
+ # [:String] the field type must be `Symbol` or `Text`
63
+ # [:Int] the field type must be `Integer`
64
+ # [:Float] the field type must be `Number`
65
+ # [:DateTime] the field type must be 'Date'
66
+ # [:Asset] the field must be a link and the `linkType` must be `Asset`
67
+ # [:Link] the field must be a link and the `linkType` must be `Entry`.
68
+ # [:Location] the field type must be `Location`
69
+ # [:Boolean] the field type must be `Boolean`
70
+ # [:Json] the field type must be `Json` - a json blob.
71
+ # [:Array] the field must be a List.
72
+ #
73
+ # Additional validation options can be enforced:
74
+ #
75
+ # [:required] the 'Required Field' checkbox must be checked
76
+ # [:optional] the 'Required Field' checkbox must not be checked
77
+ # [:link_to] (only `:Link` or `:Array` type) the given content type(s) must be
78
+ # checked in the 'Accept only specified entry type' validations
79
+ # Example:
80
+ # validate_field :button, :Link, link_to: ['button', 'altButton']
81
+ #
82
+ # [:items] (only `:Array` type) the items of the list must be of the given type.
83
+ # Example:
84
+ # validate_field :my_strings, :Array, items: :String
85
+ #
86
+ # Examples:
87
+ # see WCC::Contentful::Model::Menu and WCC::Contentful::Model::MenuButton
88
+ def validate_field(field, type, *options)
89
+ dsl = FieldDsl.new(field, type, options)
90
+
91
+ ct = try(:content_type) || name.demodulize.camelize(:lower)
92
+ (validations[ct] ||= []) << dsl
93
+ end
94
+
95
+ def no_validate_field(field)
96
+ ct = try(:content_type) || name.demodulize.camelize(:lower)
97
+ return unless v = validations[ct]
98
+
99
+ field = field.to_s.camelize(:lower) unless field.is_a?(String)
100
+ v.reject! { |dsl| dsl.try(:field) == field }
101
+ end
102
+
103
+ # Accepts a content types response from the API and transforms it
104
+ # to be acceptible for the validator.
105
+ def self.transform_content_types_for_validation(content_types)
106
+ if !content_types.is_a?(Array) && items = content_types.try(:[], 'items')
107
+ content_types = items
108
+ end
109
+
110
+ # Transform the array into a hash keyed by content type ID
111
+ content_types.each_with_object({}) do |ct, ct_hash|
112
+ # Transform the fields into a hash keyed by field ID
113
+ ct['fields'] =
114
+ ct['fields'].each_with_object({}) do |f, f_hash|
115
+ f_hash[f['id']] = f
116
+ end
117
+
118
+ ct_hash[ct.dig('sys', 'id')] = ct
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,166 @@
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
+
107
+ proc {
108
+ required('items').schema do
109
+ required('type').value(eql?: 'Link')
110
+ instance_eval(&link_to_proc)
111
+ end
112
+ }
113
+ when :items
114
+ type_pred = parse_type_predicate(option_arg)
115
+ proc {
116
+ required('items').schema do
117
+ instance_eval(&type_pred)
118
+ end
119
+ }
120
+ else
121
+ raise ArgumentError, "unknown validation requirement: #{option}"
122
+ end
123
+ end
124
+
125
+ def parse_field_link_to(option_arg)
126
+ raise ArgumentError, 'validation link_to: requires an argument' unless option_arg
127
+
128
+ # this works because a Link can only have one validation in its "validations" array -
129
+ # this will fail if Contentful ever changes that.
130
+
131
+ # the 'validations' schema needs to be optional because if we get the content
132
+ # types from the CDN instead of the management API, sometimes the validations
133
+ # don't get sent back.
134
+
135
+ # "validations": [
136
+ # {
137
+ # "linkContentType": [
138
+ # "section-CardSearch",
139
+ # "section-Faq",
140
+ # "section-Testimonials",
141
+ # "section-VideoHighlight"
142
+ # ]
143
+ # }
144
+ # ]
145
+
146
+ if option_arg.is_a?(Regexp)
147
+ return proc {
148
+ optional('validations').each do
149
+ schema do
150
+ required('linkContentType').each(format?: option_arg)
151
+ end
152
+ end
153
+ }
154
+ end
155
+
156
+ option_arg = [option_arg] unless option_arg.is_a?(Array)
157
+ proc {
158
+ optional('validations').each do
159
+ schema do
160
+ required('linkContentType').value(eql?: option_arg)
161
+ end
162
+ end
163
+ }
164
+ end
165
+ end
166
+ end
@@ -109,7 +109,7 @@ module WCC::Contentful::Store
109
109
  resolve_includes(
110
110
  JSON.parse(row['data']),
111
111
  @options[:include]
112
- )
112
+ )
113
113
  )
114
114
  end
115
115
  arr
@@ -122,7 +122,7 @@ module WCC::Contentful::Store
122
122
  resolve_includes(
123
123
  JSON.parse(row['data']),
124
124
  @options[:include]
125
- )
125
+ )
126
126
  end
127
127
  arr
128
128
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module WCC
4
4
  module Contentful
5
- VERSION = '0.3.0-rc3'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Watermark Dev']
11
11
  spec.email = ['dev@watermark.org']
12
12
 
13
- spec.summary = File.readlines(File.expand_path('README.md', __dir__)).join
14
- spec.description = 'Contentful API wrapper library exposing an ActiveRecord-like interface'
15
- spec.homepage = 'https://github.com/watermarkchurch/wcc-contentful/wcc-contentful'
13
+ spec.summary = File.readlines('README.md').join
14
+ spec.description = 'Contentful API wrapper library for Watermark apps'
15
+ spec.homepage = 'https://github.com/watermarkchurch/wcc-contentful'
16
16
  spec.license = 'MIT'
17
17
 
18
18
  spec.required_ruby_version = '>= 2.3'
@@ -29,9 +29,15 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'rake', '~> 10.0'
30
30
  spec.add_development_dependency 'rspec', '~> 3.0'
31
31
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
32
+ spec.add_development_dependency 'rubocop', '~> 0.52'
32
33
  spec.add_development_dependency 'vcr', '~> 4.0'
33
34
  spec.add_development_dependency 'webmock', '~> 3.0'
34
35
 
36
+ # Makes testing easy via `bundle exec guard`
37
+ spec.add_development_dependency 'guard', '~> 2.14'
38
+ spec.add_development_dependency 'guard-rspec', '~> 4.7'
39
+ spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
40
+
35
41
  # for generators
36
42
  spec.add_development_dependency 'generator_spec', '~> 0.9.4'
37
43
  spec.add_development_dependency 'rails', '~> 5.1'
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.3.0.pre.rc3
4
+ version: 0.3.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-10-12 00:00:00.000000000 Z
11
+ date: 2018-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.3.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.52'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.52'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: vcr
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,48 @@ dependencies:
108
122
  - - "~>"
109
123
  - !ruby/object:Gem::Version
110
124
  version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: guard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.14'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.14'
139
+ - !ruby/object:Gem::Dependency
140
+ name: guard-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '4.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '4.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: guard-rubocop
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 1.3.0
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 1.3.0
111
167
  - !ruby/object:Gem::Dependency
112
168
  name: generator_spec
113
169
  requirement: !ruby/object:Gem::Requirement
@@ -310,16 +366,26 @@ dependencies:
310
366
  - - "~>"
311
367
  - !ruby/object:Gem::Version
312
368
  version: 0.3.1
313
- description: Contentful API wrapper library exposing an ActiveRecord-like interface
369
+ description: Contentful API wrapper library for Watermark apps
314
370
  email:
315
371
  - dev@watermark.org
316
372
  executables: []
317
373
  extensions: []
318
374
  extra_rdoc_files: []
319
375
  files:
376
+ - ".circleci/config.yml"
377
+ - ".gitignore"
320
378
  - ".rspec"
379
+ - ".rubocop.yml"
380
+ - ".rubocop_todo.yml"
381
+ - ".travis.yml"
382
+ - CHANGELOG.md
383
+ - CODE_OF_CONDUCT.md
321
384
  - Gemfile
385
+ - Guardfile
386
+ - LICENSE.txt
322
387
  - README.md
388
+ - Rakefile
323
389
  - app/controllers/wcc/contentful/application_controller.rb
324
390
  - app/controllers/wcc/contentful/webhook_controller.rb
325
391
  - app/jobs/wcc/contentful/delayed_sync_job.rb
@@ -330,6 +396,18 @@ files:
330
396
  - bin/setup
331
397
  - config/initializers/mime_types.rb
332
398
  - config/routes.rb
399
+ - lib/generators/wcc/USAGE
400
+ - lib/generators/wcc/model_generator.rb
401
+ - lib/generators/wcc/templates/.keep
402
+ - lib/generators/wcc/templates/Procfile
403
+ - lib/generators/wcc/templates/contentful_shell_wrapper
404
+ - lib/generators/wcc/templates/menu/generated_add_menus.ts
405
+ - lib/generators/wcc/templates/menu/models/menu.rb
406
+ - lib/generators/wcc/templates/menu/models/menu_button.rb
407
+ - lib/generators/wcc/templates/page/generated_add_pages.ts
408
+ - lib/generators/wcc/templates/page/models/page.rb
409
+ - lib/generators/wcc/templates/release
410
+ - lib/generators/wcc/templates/wcc_contentful.rb
333
411
  - lib/wcc/contentful.rb
334
412
  - lib/wcc/contentful/client_ext.rb
335
413
  - lib/wcc/contentful/configuration.rb
@@ -342,9 +420,16 @@ files:
342
420
  - lib/wcc/contentful/helpers.rb
343
421
  - lib/wcc/contentful/indexed_representation.rb
344
422
  - lib/wcc/contentful/model.rb
423
+ - lib/wcc/contentful/model/dropdown_menu.rb
424
+ - lib/wcc/contentful/model/menu.rb
425
+ - lib/wcc/contentful/model/menu_button.rb
426
+ - lib/wcc/contentful/model/page.rb
427
+ - lib/wcc/contentful/model/redirect.rb
345
428
  - lib/wcc/contentful/model_builder.rb
346
429
  - lib/wcc/contentful/model_methods.rb
347
430
  - lib/wcc/contentful/model_singleton_methods.rb
431
+ - lib/wcc/contentful/model_validators.rb
432
+ - lib/wcc/contentful/model_validators/dsl.rb
348
433
  - lib/wcc/contentful/rails.rb
349
434
  - lib/wcc/contentful/services.rb
350
435
  - lib/wcc/contentful/simple_client.rb
@@ -361,7 +446,7 @@ files:
361
446
  - lib/wcc/contentful/sys.rb
362
447
  - lib/wcc/contentful/version.rb
363
448
  - wcc-contentful.gemspec
364
- homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful
449
+ homepage: https://github.com/watermarkchurch/wcc-contentful
365
450
  licenses:
366
451
  - MIT
367
452
  metadata: {}
@@ -376,12 +461,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
376
461
  version: '2.3'
377
462
  required_rubygems_version: !ruby/object:Gem::Requirement
378
463
  requirements:
379
- - - ">"
464
+ - - ">="
380
465
  - !ruby/object:Gem::Version
381
- version: 1.3.1
466
+ version: '0'
382
467
  requirements: []
383
468
  rubyforge_project:
384
- rubygems_version: 2.6.11
469
+ rubygems_version: 2.5.2
385
470
  signing_key:
386
471
  specification_version: 4
387
472
  summary: '[![Gem Version](https://badge.fury.io/rb/wcc-contentful.svg)](https://badge.fury.io/rb/wcc-contentful)