wcc-contentful-graphql 1.0.0.pre.rc1
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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/Guardfile +101 -0
- data/README.md +128 -0
- data/lib/wcc/contentful/ext/services.rb +24 -0
- data/lib/wcc/contentful/graphql.rb +16 -0
- data/lib/wcc/contentful/graphql/builder.rb +160 -0
- data/lib/wcc/contentful/graphql/federation.rb +98 -0
- data/lib/wcc/contentful/graphql/federation/builds_arguments.rb +30 -0
- data/lib/wcc/contentful/graphql/federation/namespaces_types.rb +74 -0
- data/lib/wcc/contentful/graphql/field_helper.rb +58 -0
- data/lib/wcc/contentful/graphql/types.rb +72 -0
- data/lib/wcc/contentful/graphql/version.rb +9 -0
- data/wcc-contentful-graphql.gemspec +52 -0
- metadata +347 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b2546f3684d711b927716615568c5a1d90994bd3242a91f056be643b0e9a8976
|
4
|
+
data.tar.gz: a396b86a4fe13e61b031b73c3f55889dfeaf0cc0406bcf7413d00159daf7cd8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8415a779ccb2d1997531d741b49e2b57a1d37f27f4283102047b8152ed7a5f2b234ef54661b2642d47ee1ae4f2b2c054698d71e225ae237815879cb55aa17c45
|
7
|
+
data.tar.gz: fe9ad8a75a6be28f8b2eb996601741238dc5a9df0f314555c069a330bf79139214a70c21d041ae069dc4a2411a69cd496fae21520aba07f8fde3894a3b1f659e
|
data/.rspec
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# A guardfile for making Danger Plugins
|
2
|
+
# For more info see https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
# To run, use `bundle exec guard`.
|
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
|
+
|
39
|
+
group :red_green_refactor, halt_on_fail: true do
|
40
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
41
|
+
require 'guard/rspec/dsl'
|
42
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
43
|
+
|
44
|
+
# RSpec files
|
45
|
+
rspec = dsl.rspec
|
46
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
47
|
+
# watch(rspec.spec_support) { rspec.spec_dir }
|
48
|
+
watch(rspec.spec_files)
|
49
|
+
|
50
|
+
# Ruby files
|
51
|
+
ruby = dsl.ruby
|
52
|
+
watch(%r{lib/wcc/(.+)\.rb$}) { |m| rspec.spec.call("wcc/#{m[1]}") }
|
53
|
+
watch(%r{lib/generators/(.+)\.rb$}) { |m| rspec.spec.call("generators/#{m[1]}") }
|
54
|
+
|
55
|
+
# Rails files
|
56
|
+
rails = dsl.rails(view_extensions: %w[erb haml slim])
|
57
|
+
dsl.watch_spec_files_for(rails.app_files)
|
58
|
+
dsl.watch_spec_files_for(rails.views)
|
59
|
+
|
60
|
+
watch(rails.controllers) do |m|
|
61
|
+
[
|
62
|
+
rspec.spec.call("routing/#{m[1]}_routing"),
|
63
|
+
rspec.spec.call("controllers/#{m[1]}_controller"),
|
64
|
+
rspec.spec.call("acceptance/#{m[1]}")
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Rails config changes
|
69
|
+
watch(rails.spec_helper) { rspec.spec_dir }
|
70
|
+
watch(rails.routes) { "#{rspec.spec_dir}/routing" }
|
71
|
+
watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
|
72
|
+
|
73
|
+
# Capybara features specs
|
74
|
+
watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
|
75
|
+
watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
|
76
|
+
end
|
77
|
+
|
78
|
+
guard :rubocop, cli: ['--display-cop-names'] do
|
79
|
+
watch(%r{.+\.rb$})
|
80
|
+
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
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
|
92
|
+
end
|
93
|
+
|
94
|
+
group :autofix do
|
95
|
+
guard :rubocop, all_on_start: false, cli: ['--auto-correct', '--display-cop-names'] do
|
96
|
+
watch(%r{.+\.rb$})
|
97
|
+
watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
scope group: :red_green_refactor
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
[](https://rubygems.org/gems/wcc-contentful-graphql)
|
2
|
+
[](https://travis-ci.org/watermarkchurch/wcc-contentful)
|
3
|
+
[](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master)
|
4
|
+
|
5
|
+
# WCC::Contentful::Graphql
|
6
|
+
|
7
|
+
This gem creates a GraphQL schema over your configured [data store](https://www.rubydoc.info/gems/wcc-contentful#Store_API).
|
8
|
+
You can execute queries against this GraphQL schema to get all your contentful
|
9
|
+
data. Under the hood, queries are executed against your backing store to
|
10
|
+
resolve all the requested data.
|
11
|
+
|
12
|
+
### Important note!
|
13
|
+
The GraphQL schema currently does not utilize the "include" parameter, so it is
|
14
|
+
a very good idea to configure your store to either `:direct`
|
15
|
+
or `:lazy_sync`. If you don't do this, you will see a lot of requests to
|
16
|
+
Contentful for specific entries by ID as the GraphQL resolver walks all your links!
|
17
|
+
|
18
|
+
[More info on configuration can be found here](https://www.rubydoc.info/gems/wcc-contentful/WCC%2FContentful%2FConfiguration:store=)
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Querying directly within your app
|
23
|
+
```rb
|
24
|
+
schema = WCC::Contentful::Services.instance.graphql_schema
|
25
|
+
=> #<GraphQL::Schema ...>
|
26
|
+
|
27
|
+
result = schema.execute(<<~QUERY)
|
28
|
+
{
|
29
|
+
allConference(filter: { code: { eq: "CLC2020" } }) {
|
30
|
+
title
|
31
|
+
startDate
|
32
|
+
code
|
33
|
+
}
|
34
|
+
}
|
35
|
+
QUERY
|
36
|
+
GET https://cdn.contentful.com/spaces/xxxxx/entries?content_type=conference&fields.code.en-US=CLC2020&locale=%2A
|
37
|
+
Status 200
|
38
|
+
=> #<GraphQL::Query::Result @query=... @to_h={"data"=>{"allConference"=>[{"title"=>"Church Leaders Conference", "startDate"=>"2020-04-28", "code"=>"CLC2020"}]}}>
|
39
|
+
result.to_h
|
40
|
+
=> {"data"=>
|
41
|
+
{"allConference"=>
|
42
|
+
[{"title"=>"Church Leaders Conference",
|
43
|
+
"startDate"=>"2020-04-28",
|
44
|
+
"code"=>"CLC2020"}]}}
|
45
|
+
```
|
46
|
+
|
47
|
+
Setting up a controller to respond to GraphQL queries
|
48
|
+
|
49
|
+
```rb
|
50
|
+
class Api::GraphqlController < Api::BaseController
|
51
|
+
include WCC::Contentful::ServiceAccessors
|
52
|
+
|
53
|
+
skip_before_action :authenticate_user!, only: :query
|
54
|
+
|
55
|
+
def query
|
56
|
+
result = graphql_schema.execute(
|
57
|
+
params[:query],
|
58
|
+
variables: params[:variables]
|
59
|
+
)
|
60
|
+
render json: result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Advanced Configuration
|
66
|
+
|
67
|
+
### Including your Contentful schema inside another GraphQL schema
|
68
|
+
|
69
|
+
```rb
|
70
|
+
QueryType = GraphQL::ObjectType.define do
|
71
|
+
# extend this to get 'schema_stitch'
|
72
|
+
extend WCC::Contentful::Graphql::Federation
|
73
|
+
|
74
|
+
name 'RootQuery'
|
75
|
+
|
76
|
+
field 'a', types.String
|
77
|
+
|
78
|
+
schema_stitch(WCC::Contentful::Services.instance.graphql_schema,
|
79
|
+
namespace: 'contentful')
|
80
|
+
end
|
81
|
+
|
82
|
+
Schema = GraphQL::Schema.define do
|
83
|
+
query QueryType
|
84
|
+
|
85
|
+
resolve_type ->(type, obj, ctx) {
|
86
|
+
raise StandardError, "Cannot resolve type #{type} #{obj.inspect} #{ctx.inspect}"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
File.write('schema.gql', GraphQL::Schema::Printer.print_schema(Schema))
|
91
|
+
```
|
92
|
+
results in...
|
93
|
+
```gql
|
94
|
+
schema {
|
95
|
+
query: RootQuery
|
96
|
+
}
|
97
|
+
|
98
|
+
type Contentful {
|
99
|
+
"""
|
100
|
+
Find a Asset
|
101
|
+
"""
|
102
|
+
Asset(_content_type: Contentful_StringQueryOperatorInput, description: Contentful_StringQueryOperatorInput, id: ID, title: Contentful_StringQueryOperatorInput): Contentful_Asset
|
103
|
+
|
104
|
+
"""
|
105
|
+
Find a CallToAction
|
106
|
+
"""
|
107
|
+
CallToAction(_content_type: Contentful_StringQueryOperatorInput, id: ID, internalTitle: Contentful_StringQueryOperatorInput, style: Contentful_StringQueryOperatorInput, text: Contentful_StringQueryOperatorInput, title: Contentful_StringQueryOperatorInput): Contentful_CallToAction
|
108
|
+
...
|
109
|
+
```
|
110
|
+
|
111
|
+
### Limiting the schema to only a few fields
|
112
|
+
|
113
|
+
```rb
|
114
|
+
store = WCC::Contentful::Services.instance.store
|
115
|
+
builder =
|
116
|
+
WCC::Contentful::Graphql::Builder.new(
|
117
|
+
WCC::Contentful.types,
|
118
|
+
store,
|
119
|
+
).configure do
|
120
|
+
root_types.slice!('conference')
|
121
|
+
|
122
|
+
schema_types['conference'].define do
|
123
|
+
# change the types of some fields, undefine fields, etc...
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
@schema = builder.build_schema
|
128
|
+
```
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful
|
4
|
+
class Services
|
5
|
+
# A GraphQL schema that will query Contentful using your configured store.
|
6
|
+
#
|
7
|
+
# @api Store
|
8
|
+
def graphql_schema
|
9
|
+
@graphql_schema ||=
|
10
|
+
ensure_configured do |_config|
|
11
|
+
WCC::Contentful::Graphql::Builder.new(
|
12
|
+
WCC::Contentful.types,
|
13
|
+
store
|
14
|
+
).build_schema
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ServiceAccessors
|
20
|
+
def graphql_schema
|
21
|
+
Services.instance.graphql_schema
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
gem 'graphql', '~> 1.7'
|
4
|
+
require 'graphql'
|
5
|
+
|
6
|
+
module WCC::Contentful
|
7
|
+
# This module builds a GraphQL schema out of our IndexedRepresentation.
|
8
|
+
# It is currently unused and not hooked up in the WCC::Contentful.init! method.
|
9
|
+
# TODO: https://github.com/watermarkchurch/wcc-contentful/issues/14 hook it up
|
10
|
+
module Graphql
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require_relative 'graphql/builder'
|
15
|
+
require_relative 'graphql/federation'
|
16
|
+
require_relative 'ext/services'
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
|
5
|
+
require_relative 'types'
|
6
|
+
require_relative 'field_helper'
|
7
|
+
|
8
|
+
GraphQL::Define::DefinedObjectProxy.__send__(:include, WCC::Contentful::Graphql::FieldHelper)
|
9
|
+
|
10
|
+
module WCC::Contentful::Graphql
|
11
|
+
class Builder
|
12
|
+
attr_reader :schema_types
|
13
|
+
attr_reader :root_types
|
14
|
+
|
15
|
+
def initialize(types, store)
|
16
|
+
@types = types if types.is_a? WCC::Contentful::IndexedRepresentation
|
17
|
+
@types ||=
|
18
|
+
if types.is_a?(String) && File.exist?(types)
|
19
|
+
WCC::Contentful::ContentTypeIndexer.load(types).types
|
20
|
+
end
|
21
|
+
|
22
|
+
unless @types
|
23
|
+
raise ArgumentError, 'Cannot parse types - not an IndexedRepresentation ' \
|
24
|
+
"nor a schema file on disk: #{types}"
|
25
|
+
end
|
26
|
+
|
27
|
+
@store = store
|
28
|
+
|
29
|
+
@schema_types = build_schema_types
|
30
|
+
@root_types = @schema_types.dup
|
31
|
+
end
|
32
|
+
|
33
|
+
def configure(&block)
|
34
|
+
instance_exec(&block)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_schema
|
39
|
+
root_query_type = build_root_query(root_types)
|
40
|
+
|
41
|
+
builder = self
|
42
|
+
GraphQL::Schema.define do
|
43
|
+
query root_query_type
|
44
|
+
|
45
|
+
resolve_type ->(_type, obj, _ctx) {
|
46
|
+
content_type = WCC::Contentful::Helpers.content_type_from_raw(obj)
|
47
|
+
builder.schema_types[content_type]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def build_root_query(schema_types)
|
55
|
+
store = @store
|
56
|
+
|
57
|
+
GraphQL::ObjectType.define do
|
58
|
+
name 'Query'
|
59
|
+
description 'The query root of this schema'
|
60
|
+
|
61
|
+
schema_types.each do |content_type, schema_type|
|
62
|
+
field schema_type.name.to_sym do
|
63
|
+
type schema_type
|
64
|
+
argument :id, types.ID
|
65
|
+
description "Find a #{schema_type.name}"
|
66
|
+
|
67
|
+
schema_type.fields.each do |(name, field)|
|
68
|
+
next unless input_type = Types::QueryOperatorInput.call(field.type)
|
69
|
+
|
70
|
+
argument name, input_type
|
71
|
+
end
|
72
|
+
|
73
|
+
resolve ->(_obj, args, _ctx) {
|
74
|
+
if args['id'].nil?
|
75
|
+
store.find_by(content_type: content_type, filter: args.to_h)
|
76
|
+
else
|
77
|
+
store.find(args['id'])
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
field "all#{schema_type.name}".to_sym do
|
83
|
+
type schema_type.to_list_type
|
84
|
+
argument :filter, Types::FilterInputType.call(schema_type)
|
85
|
+
|
86
|
+
resolve ->(_obj, args, ctx) {
|
87
|
+
relation = store.find_all(content_type: content_type)
|
88
|
+
relation = relation.apply(args[:filter].to_h, ctx) if args[:filter]
|
89
|
+
relation.to_enum
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_schema_types
|
97
|
+
@types.each_with_object({}) do |(k, v), h|
|
98
|
+
h[k] = build_schema_type(v)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_schema_type(typedef)
|
103
|
+
store = @store
|
104
|
+
builder = self
|
105
|
+
content_type = typedef.content_type
|
106
|
+
|
107
|
+
GraphQL::ObjectType.define do
|
108
|
+
name(typedef.name)
|
109
|
+
|
110
|
+
description("Generated from content type #{content_type}")
|
111
|
+
|
112
|
+
field :id, !types.ID do
|
113
|
+
resolve ->(obj, _args, _ctx) {
|
114
|
+
obj.dig('sys', 'id')
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
field :_content_type, !types.String do
|
119
|
+
resolve ->(_, _, _) {
|
120
|
+
content_type
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Make a field for each column:
|
125
|
+
typedef.fields.each_value do |f|
|
126
|
+
case f.type
|
127
|
+
when :Asset
|
128
|
+
field(f.name.to_sym, -> {
|
129
|
+
type = builder.schema_types['Asset']
|
130
|
+
type = type.to_list_type if f.array
|
131
|
+
type
|
132
|
+
}) do
|
133
|
+
resolve contentful_link_resolver(f.name, store: store)
|
134
|
+
end
|
135
|
+
when :Link
|
136
|
+
field(f.name.to_sym, -> {
|
137
|
+
type =
|
138
|
+
if f.link_types.nil? || f.link_types.empty?
|
139
|
+
builder.schema_types['AnyContentful'] ||=
|
140
|
+
Types::BuildUnionType.call(builder.schema_types, 'AnyContentful')
|
141
|
+
elsif f.link_types.length == 1
|
142
|
+
builder.schema_types[f.link_types.first]
|
143
|
+
else
|
144
|
+
from_types = builder.schema_types.select { |key| f.link_types.include?(key) }
|
145
|
+
name = "#{typedef.name}_#{f.name}"
|
146
|
+
builder.schema_types[name] ||= Types::BuildUnionType.call(from_types, name)
|
147
|
+
end
|
148
|
+
type = type.to_list_type if f.array
|
149
|
+
type
|
150
|
+
}) do
|
151
|
+
resolve contentful_link_resolver(f.name, store: store)
|
152
|
+
end
|
153
|
+
else
|
154
|
+
contentful_field(f.name, f.type, array: f.array)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extend this module inside a root query definition to do schema federation.
|
4
|
+
# https://blog.apollographql.com/apollo-federation-f260cf525d21
|
5
|
+
#
|
6
|
+
# This handles only queries, not mutations or subscriptions.
|
7
|
+
module WCC::Contentful::Graphql::Federation
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Accepts an externally defined schema with a root query, and "stitches" it's
|
11
|
+
# query root into the current GraphQL::ObjectType definition.
|
12
|
+
# All fields on the external query object like `resource()`, `allResource()`
|
13
|
+
# will be inserted into the current object. The `resolve` method for those
|
14
|
+
# fields will execute a query on the external schema, returning the results.
|
15
|
+
def schema_stitch(schema, namespace: nil)
|
16
|
+
ns_titleized = namespace&.titleize
|
17
|
+
ns = NamespacesTypes.new(namespace: ns_titleized)
|
18
|
+
|
19
|
+
def_fields =
|
20
|
+
proc {
|
21
|
+
schema.query.fields.each do |(key, field_def)|
|
22
|
+
field key, ns.namespaced(field_def.type) do
|
23
|
+
description field_def.description
|
24
|
+
|
25
|
+
field_def.arguments.each do |(arg_name, arg)|
|
26
|
+
argument arg_name, ns.namespaced(arg.type)
|
27
|
+
end
|
28
|
+
|
29
|
+
resolve delegate_to_schema(schema)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
|
34
|
+
if namespace
|
35
|
+
stub_class = Struct.new(:name)
|
36
|
+
namespaced_type =
|
37
|
+
GraphQL::ObjectType.define do
|
38
|
+
name ns_titleized
|
39
|
+
|
40
|
+
instance_exec(&def_fields)
|
41
|
+
end
|
42
|
+
|
43
|
+
field namespace, namespaced_type do
|
44
|
+
resolve ->(_obj, _arguments, _context) { stub_class.new(namespace) }
|
45
|
+
end
|
46
|
+
else
|
47
|
+
def_fields.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def delegate_to_schema(schema, field_name: nil, arguments: nil)
|
52
|
+
->(obj, inner_args, context) {
|
53
|
+
field_name ||= context.ast_node.name
|
54
|
+
|
55
|
+
arguments = arguments.call(obj, inner_args, context) if arguments&.respond_to?(:call)
|
56
|
+
arguments = BuildsArguments.call(arguments) if arguments
|
57
|
+
arguments ||= context.ast_node.arguments
|
58
|
+
|
59
|
+
field_node = GraphQL::Language::Nodes::Field.new(
|
60
|
+
name: field_name,
|
61
|
+
arguments: arguments,
|
62
|
+
selections: context.ast_node.selections,
|
63
|
+
directives: context.ast_node.directives
|
64
|
+
)
|
65
|
+
|
66
|
+
query_node = GraphQL::Language::Nodes::OperationDefinition.new(
|
67
|
+
name: context.query.operation_name,
|
68
|
+
operation_type: 'query',
|
69
|
+
variables: context.query.selected_operation.variables,
|
70
|
+
selections: [
|
71
|
+
field_node
|
72
|
+
]
|
73
|
+
)
|
74
|
+
|
75
|
+
# the ast_node.to_query_string prints the relevant section of the query to
|
76
|
+
# a string. We build a query out of that which we execute on the external
|
77
|
+
# schema.
|
78
|
+
query = query_node.to_query_string
|
79
|
+
|
80
|
+
result = schema.execute(query,
|
81
|
+
variables: context.query.variables)
|
82
|
+
|
83
|
+
if result['errors'].present?
|
84
|
+
raise GraphQL::ExecutionError.new(
|
85
|
+
result.dig('errors', 0, 'message'),
|
86
|
+
ast_node: context.ast_node
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
result.dig('data', field_name)
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
GraphQL::Define::DefinedObjectProxy.__send__(:include, WCC::Contentful::Graphql::Federation)
|
96
|
+
|
97
|
+
require_relative './federation/namespaces_types'
|
98
|
+
require_relative './federation/builds_arguments'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful::Graphql::Federation
|
4
|
+
BuildsArguments =
|
5
|
+
Struct.new(:argument) do
|
6
|
+
def self.call(arguments)
|
7
|
+
arguments.map { |arg| new(arg).call }
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return argument if argument.is_a? GraphQL::Language::Nodes::Argument
|
12
|
+
|
13
|
+
GraphQL::Language::Nodes::Argument.new(name: key, value: value)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def key
|
19
|
+
argument[0]
|
20
|
+
end
|
21
|
+
|
22
|
+
def value
|
23
|
+
if argument[1].is_a? Hash
|
24
|
+
return GraphQL::Language::Nodes::InputObject.new(arguments: BuildsArguments.call(argument[1]))
|
25
|
+
end
|
26
|
+
|
27
|
+
argument[1]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This GraphQL type definition wraps a type definition from an external schema
|
4
|
+
# and redefines it in our top-level schema, so that names do not clash.
|
5
|
+
# ex. "Campus" in the events schema becomes "Event_Campus"
|
6
|
+
class WCC::Contentful::Graphql::Federation::NamespacesTypes
|
7
|
+
class << self
|
8
|
+
def registry
|
9
|
+
@registry ||= {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :namespace
|
14
|
+
|
15
|
+
def initialize(namespace:)
|
16
|
+
@namespace = namespace
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets the graphql type definition for the externally resolved field
|
20
|
+
def namespaced(type)
|
21
|
+
return type if type.default_scalar?
|
22
|
+
return namespaced(type.of_type).to_list_type if type.is_a?(GraphQL::ListType)
|
23
|
+
return namespaced(type.of_type).to_non_null_type if type.is_a?(GraphQL::NonNullType)
|
24
|
+
|
25
|
+
me = self
|
26
|
+
ns = namespace
|
27
|
+
typename = [namespace, type.to_s].compact.join('_')
|
28
|
+
self.class.registry[typename] ||=
|
29
|
+
if type.is_a?(GraphQL::UnionType)
|
30
|
+
possible_types =
|
31
|
+
type.possible_types.map { |t| me.namespaced(t) }
|
32
|
+
GraphQL::UnionType.define do
|
33
|
+
name typename
|
34
|
+
possible_types possible_types
|
35
|
+
end
|
36
|
+
elsif type.is_a?(GraphQL::InputObjectType)
|
37
|
+
GraphQL::InputObjectType.define do
|
38
|
+
name typename
|
39
|
+
type.arguments.each do |(name, arg)|
|
40
|
+
argument name, me.namespaced(arg.type)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
elsif type.is_a?(GraphQL::ScalarType)
|
44
|
+
GraphQL::ScalarType.define do
|
45
|
+
name typename
|
46
|
+
|
47
|
+
coerce_input type.method(:coerce_input)
|
48
|
+
coerce_result type.method(:coerce_result)
|
49
|
+
end
|
50
|
+
elsif type.is_a?(GraphQL::ObjectType)
|
51
|
+
GraphQL::ObjectType.define do
|
52
|
+
name typename
|
53
|
+
description "#{type.name} from remote#{ns ? ' ' + ns : ''}"
|
54
|
+
|
55
|
+
type.fields.each do |(name, field_def)|
|
56
|
+
field name, me.namespaced(field_def.type) do
|
57
|
+
field_def.arguments.each do |(arg_name, arg)|
|
58
|
+
argument arg_name, me.namespaced(arg.type)
|
59
|
+
end
|
60
|
+
|
61
|
+
resolve ->(obj, _args, _ctx) do
|
62
|
+
# The object is a JSON response that came back from the
|
63
|
+
# external schema. Resolve the value by using the hash key.
|
64
|
+
obj[name]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise ArgumentError, "Cannot namespace type #{type} (#{type.class})"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
# rubocop:enable
|
74
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful::Graphql::FieldHelper
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def contentful_field_resolver(field_name)
|
7
|
+
field_name = field_name.to_s
|
8
|
+
|
9
|
+
->(obj, _args, ctx) {
|
10
|
+
if obj.is_a? Array
|
11
|
+
obj.map { |o| o.dig('fields', field_name, ctx[:locale] || 'en-US') }
|
12
|
+
else
|
13
|
+
obj.dig('fields', field_name, ctx[:locale] || 'en-US')
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def contentful_field(field_name, type, array: false, &block)
|
19
|
+
field_name = field_name.to_s
|
20
|
+
|
21
|
+
type =
|
22
|
+
case type
|
23
|
+
when :DateTime
|
24
|
+
types.String
|
25
|
+
when :Coordinates
|
26
|
+
WCC::Contentful::Graphql::Types::CoordinatesType
|
27
|
+
when :Json
|
28
|
+
WCC::Contentful::Graphql::Types::HashType
|
29
|
+
else
|
30
|
+
if type.is_a?(Symbol) || type.is_a?(String)
|
31
|
+
types.public_send(type)
|
32
|
+
elsif type.is_a?(GraphQL::BaseType)
|
33
|
+
type
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Unknown type arg '#{type}' for field #{field_name}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
type = type.to_list_type if array
|
39
|
+
field(field_name.to_sym, type) do
|
40
|
+
resolve contentful_field_resolver(field_name)
|
41
|
+
|
42
|
+
instance_exec(&block) if block_given?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def contentful_link_resolver(field_name, store:)
|
47
|
+
->(obj, _args, ctx) {
|
48
|
+
links = obj.dig('fields', field_name, ctx[:locale] || 'en-US')
|
49
|
+
return if links.nil?
|
50
|
+
|
51
|
+
if links.is_a? Array
|
52
|
+
links.reject(&:nil?).map { |l| store.find(l.dig('sys', 'id')) }
|
53
|
+
else
|
54
|
+
store.find(links.dig('sys', 'id'))
|
55
|
+
end
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WCC::Contentful::Graphql::Types
|
4
|
+
DateTimeType =
|
5
|
+
GraphQL::ScalarType.define do
|
6
|
+
name 'DateTime'
|
7
|
+
|
8
|
+
coerce_result ->(value, _ctx) { Time.zone.parse(value) }
|
9
|
+
end
|
10
|
+
|
11
|
+
HashType =
|
12
|
+
GraphQL::ScalarType.define do
|
13
|
+
name 'Hash'
|
14
|
+
|
15
|
+
coerce_result ->(value, _ctx) {
|
16
|
+
return value if value.is_a? Array
|
17
|
+
return value.to_h if value.respond_to?(:to_h)
|
18
|
+
return JSON.parse(value) if value.is_a? String
|
19
|
+
|
20
|
+
raise ArgumentError, "Cannot coerce value '#{value}' to a hash"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
CoordinatesType =
|
25
|
+
GraphQL::ObjectType.define do
|
26
|
+
name 'Coordinates'
|
27
|
+
|
28
|
+
field :lat, !types.Float, hash_key: 'lat'
|
29
|
+
field :lon, !types.Float, hash_key: 'lon'
|
30
|
+
end
|
31
|
+
|
32
|
+
StringQueryOperatorInput =
|
33
|
+
GraphQL::InputObjectType.define do
|
34
|
+
name 'StringQueryOperatorInput'
|
35
|
+
|
36
|
+
argument :eq, types.String
|
37
|
+
end
|
38
|
+
|
39
|
+
QueryOperatorInput =
|
40
|
+
->(type) do
|
41
|
+
map = {
|
42
|
+
'String' => StringQueryOperatorInput
|
43
|
+
# 'Int' =>
|
44
|
+
# 'Boolean' =>
|
45
|
+
}
|
46
|
+
|
47
|
+
map[type.unwrap.name]
|
48
|
+
end
|
49
|
+
|
50
|
+
FilterInputType =
|
51
|
+
->(schema_type) do
|
52
|
+
GraphQL::InputObjectType.define do
|
53
|
+
name "#{schema_type.name}FilterInput"
|
54
|
+
|
55
|
+
schema_type.fields.each do |(name, field)|
|
56
|
+
next unless input_type = QueryOperatorInput.call(field.type)
|
57
|
+
|
58
|
+
argument name, input_type
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
BuildUnionType =
|
64
|
+
->(from_types, union_type_name) do
|
65
|
+
possible_types = from_types.values.reject { |t| t.is_a? GraphQL::UnionType }
|
66
|
+
|
67
|
+
GraphQL::UnionType.define do
|
68
|
+
name union_type_name
|
69
|
+
possible_types possible_types
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'wcc/contentful/graphql/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'wcc-contentful-graphql'
|
9
|
+
spec.version = WCC::Contentful::Graphql::VERSION
|
10
|
+
spec.authors = ['Watermark Dev']
|
11
|
+
spec.email = ['dev@watermark.org']
|
12
|
+
|
13
|
+
spec.summary = File.readlines(File.expand_path('README.md', __dir__)).join
|
14
|
+
spec.description = 'GraphQL interface over WCC::Contentful store'
|
15
|
+
spec.homepage = 'https://github.com/watermarkchurch/wcc-contentful/wcc-contentful-graphql'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.required_ruby_version = '>= 2.3'
|
19
|
+
|
20
|
+
spec.files =
|
21
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
f.match(%r{^(test|spec|features)/})
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.require_paths = ['lib']
|
26
|
+
|
27
|
+
spec.add_development_dependency 'coveralls'
|
28
|
+
spec.add_development_dependency 'dotenv', '~> 2.2'
|
29
|
+
spec.add_development_dependency 'httplog', '~> 1.0'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
spec.add_development_dependency 'rspec_junit_formatter', '~> 0.3.0'
|
33
|
+
spec.add_development_dependency 'rubocop', '0.68'
|
34
|
+
spec.add_development_dependency 'simplecov', '~> 0.16.1'
|
35
|
+
spec.add_development_dependency 'webmock', '~> 3.0'
|
36
|
+
|
37
|
+
# Makes testing easy via `bundle exec guard`
|
38
|
+
spec.add_development_dependency 'guard', '~> 2.14'
|
39
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
40
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1.3.0'
|
41
|
+
spec.add_development_dependency 'guard-shell', '~> 0.7.1'
|
42
|
+
|
43
|
+
# for generators
|
44
|
+
spec.add_development_dependency 'generator_spec', '~> 0.9.4'
|
45
|
+
# spec.add_development_dependency 'rails', '~> 5.1'
|
46
|
+
# spec.add_development_dependency 'rspec-rails', '~> 3.7'
|
47
|
+
spec.add_development_dependency 'sqlite3', '~> 1.3.6'
|
48
|
+
spec.add_development_dependency 'timecop', '~> 0.9.1'
|
49
|
+
|
50
|
+
spec.add_dependency 'graphql', '~> 1.7'
|
51
|
+
spec.add_dependency 'wcc-contentful', "~> #{WCC::Contentful::Graphql::VERSION}"
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wcc-contentful-graphql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Watermark Dev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: coveralls
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httplog
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec_junit_formatter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.3.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.3.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.68'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.68'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.16.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.16.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: guard
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '2.14'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '2.14'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: guard-rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '4.7'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '4.7'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: guard-rubocop
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.3.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 1.3.0
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: guard-shell
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 0.7.1
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 0.7.1
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: generator_spec
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 0.9.4
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 0.9.4
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: sqlite3
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - "~>"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 1.3.6
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - "~>"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 1.3.6
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: timecop
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - "~>"
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: 0.9.1
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - "~>"
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: 0.9.1
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: graphql
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - "~>"
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '1.7'
|
244
|
+
type: :runtime
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - "~>"
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '1.7'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: wcc-contentful
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - "~>"
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: 1.0.0.pre.rc1
|
258
|
+
type: :runtime
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - "~>"
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: 1.0.0.pre.rc1
|
265
|
+
description: GraphQL interface over WCC::Contentful store
|
266
|
+
email:
|
267
|
+
- dev@watermark.org
|
268
|
+
executables: []
|
269
|
+
extensions: []
|
270
|
+
extra_rdoc_files: []
|
271
|
+
files:
|
272
|
+
- ".rspec"
|
273
|
+
- Guardfile
|
274
|
+
- README.md
|
275
|
+
- lib/wcc/contentful/ext/services.rb
|
276
|
+
- lib/wcc/contentful/graphql.rb
|
277
|
+
- lib/wcc/contentful/graphql/builder.rb
|
278
|
+
- lib/wcc/contentful/graphql/federation.rb
|
279
|
+
- lib/wcc/contentful/graphql/federation/builds_arguments.rb
|
280
|
+
- lib/wcc/contentful/graphql/federation/namespaces_types.rb
|
281
|
+
- lib/wcc/contentful/graphql/field_helper.rb
|
282
|
+
- lib/wcc/contentful/graphql/types.rb
|
283
|
+
- lib/wcc/contentful/graphql/version.rb
|
284
|
+
- wcc-contentful-graphql.gemspec
|
285
|
+
homepage: https://github.com/watermarkchurch/wcc-contentful/wcc-contentful-graphql
|
286
|
+
licenses:
|
287
|
+
- MIT
|
288
|
+
metadata: {}
|
289
|
+
post_install_message:
|
290
|
+
rdoc_options: []
|
291
|
+
require_paths:
|
292
|
+
- lib
|
293
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
294
|
+
requirements:
|
295
|
+
- - ">="
|
296
|
+
- !ruby/object:Gem::Version
|
297
|
+
version: '2.3'
|
298
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
299
|
+
requirements:
|
300
|
+
- - ">"
|
301
|
+
- !ruby/object:Gem::Version
|
302
|
+
version: 1.3.1
|
303
|
+
requirements: []
|
304
|
+
rubyforge_project:
|
305
|
+
rubygems_version: 2.7.6.2
|
306
|
+
signing_key:
|
307
|
+
specification_version: 4
|
308
|
+
summary: '[](https://rubygems.org/gems/wcc-contentful-graphql)
|
309
|
+
[](https://travis-ci.org/watermarkchurch/wcc-contentful)
|
310
|
+
[](https://coveralls.io/github/watermarkchurch/wcc-contentful?branch=master) #
|
311
|
+
WCC::Contentful::Graphql This gem creates a GraphQL schema over your configured
|
312
|
+
[data store](https://www.rubydoc.info/gems/wcc-contentful#Store_API). You can execute
|
313
|
+
queries against this GraphQL schema to get all your contentful data. Under the
|
314
|
+
hood, queries are executed against your backing store to resolve all the requested
|
315
|
+
data. ### Important note! The GraphQL schema currently does not utilize the "include"
|
316
|
+
parameter, so it is a very good idea to configure your store to either `:direct`
|
317
|
+
or `:lazy_sync`. If you don''t do this, you will see a lot of requests to Contentful
|
318
|
+
for specific entries by ID as the GraphQL resolver walks all your links! [More
|
319
|
+
info on configuration can be found here](https://www.rubydoc.info/gems/wcc-contentful/WCC%2FContentful%2FConfiguration:store=) ##
|
320
|
+
Usage Querying directly within your app ```rb schema = WCC::Contentful::Services.instance.graphql_schema
|
321
|
+
=> #<GraphQL::Schema ...> result = schema.execute(<<~QUERY) { allConference(filter:
|
322
|
+
{ code: { eq: "CLC2020" } }) { title startDate code } } QUERY GET https://cdn.contentful.com/spaces/xxxxx/entries?content_type=conference&fields.code.en-US=CLC2020&locale=%2A
|
323
|
+
Status 200 => #<GraphQL::Query::Result @query=... @to_h={"data"=>{"allConference"=>[{"title"=>"Church
|
324
|
+
Leaders Conference", "startDate"=>"2020-04-28", "code"=>"CLC2020"}]}}> result.to_h
|
325
|
+
=> {"data"=> {"allConference"=> [{"title"=>"Church Leaders Conference", "startDate"=>"2020-04-28",
|
326
|
+
"code"=>"CLC2020"}]}} ``` Setting up a controller to respond to GraphQL queries ```rb
|
327
|
+
class Api::GraphqlController < Api::BaseController include WCC::Contentful::ServiceAccessors skip_before_action
|
328
|
+
:authenticate_user!, only: :query def query result = graphql_schema.execute( params[:query],
|
329
|
+
variables: params[:variables] ) render json: result end end ``` ## Advanced Configuration ###
|
330
|
+
Including your Contentful schema inside another GraphQL schema ```rb QueryType
|
331
|
+
= GraphQL::ObjectType.define do # extend this to get ''schema_stitch'' extend WCC::Contentful::Graphql::Federation name
|
332
|
+
''RootQuery'' field ''a'', types.String schema_stitch(WCC::Contentful::Services.instance.graphql_schema,
|
333
|
+
namespace: ''contentful'') end Schema = GraphQL::Schema.define do query QueryType resolve_type
|
334
|
+
->(type, obj, ctx) { raise StandardError, "Cannot resolve type #{type} #{obj.inspect}
|
335
|
+
#{ctx.inspect}" } end File.write(''schema.gql'', GraphQL::Schema::Printer.print_schema(Schema))
|
336
|
+
``` results in... ```gql schema { query: RootQuery } type Contentful { """ Find
|
337
|
+
a Asset """ Asset(_content_type: Contentful_StringQueryOperatorInput, description:
|
338
|
+
Contentful_StringQueryOperatorInput, id: ID, title: Contentful_StringQueryOperatorInput):
|
339
|
+
Contentful_Asset """ Find a CallToAction """ CallToAction(_content_type: Contentful_StringQueryOperatorInput,
|
340
|
+
id: ID, internalTitle: Contentful_StringQueryOperatorInput, style: Contentful_StringQueryOperatorInput,
|
341
|
+
text: Contentful_StringQueryOperatorInput, title: Contentful_StringQueryOperatorInput):
|
342
|
+
Contentful_CallToAction ... ``` ### Limiting the schema to only a few fields ```rb
|
343
|
+
store = WCC::Contentful::Services.instance.store builder = WCC::Contentful::Graphql::Builder.new(
|
344
|
+
WCC::Contentful.types, store, ).configure do root_types.slice!(''conference'') schema_types[''conference''].define
|
345
|
+
do # change the types of some fields, undefine fields, etc... end end @schema =
|
346
|
+
builder.build_schema ```'
|
347
|
+
test_files: []
|