Add new GraphQL types, schema (with fields) & base mutation

The current consul GraphQL API has two problems.

1) It uses some unnecessary complicated magic to automatically create
   the GraphQL types and querys using an `api.yml` file. This approach
   is over-engineered, complex and has no benefits. It's just harder to
   understand the code for people which are not familiar with the
   project (like me, lol).

2) It uses a deprecated DSL [1] that is soon going to be removed from
   `graphql-ruby` completely. We are already seeing deprecation warning
   because of this (see References).

There was one problem. I wanted to create the API so that it is fully
backwards compatible with the old one, BUT the old one uses field names
which are directly derived from the ruby code, which results in
snake_case field names - not the GraphQL way. When I'm using the
graphql-ruby Class-based syntax, it automatically creates the fields in
camelCase, which breaks backwards-compatibility.

So I've added deprecated snake_case field names to keep it
backwards-compatible.

[1] https://graphql-ruby.org/schema/class_based_api.html
This commit is contained in:
Finn Heemeyer
2022-01-05 13:23:11 +01:00
committed by Javi Martín
parent 5c6ab81c38
commit c984e666ff
24 changed files with 315 additions and 363 deletions

View File

@@ -0,0 +1,29 @@
module Types
class BaseObject < GraphQL::Schema::Object
def self.field(*args, **kwargs, &block)
super(*args, **kwargs, &block)
# The old api contained non-camelized fields
# We want to support these for now, but throw a deprecation warning
#
# Example:
# proposal_notifications => Deprecation warning (Old api)
# proposalNotifications => No deprecation warning (New api)
field_name = args[0]
if field_name.to_s.include?("_")
reason = "Snake case fields are deprecated. Please use #{field_name.to_s.camelize(:lower)}."
kwargs = kwargs.merge({ camelize: false, deprecation_reason: reason })
super(*args, **kwargs, &block)
end
# Make sure associations only return public records
# by automatically calling 'public_for_api'
type_class = args[1]
if type_class.is_a?(Class) && type_class.ancestors.include?(GraphQL::Types::Relay::BaseConnection)
define_method(field_name) { object.send(field_name).public_for_api }
end
end
end
end

View File

@@ -0,0 +1,16 @@
module Types
class CommentType < Types::BaseObject
field :ancestry, String, null: true
field :body, String, null: true
field :cached_votes_down, Integer, null: true
field :cached_votes_total, Integer, null: true
field :cached_votes_up, Integer, null: true
field :commentable_id, Integer, null: true
field :commentable_type, String, null: true
field :confidence_score, Integer, null: false
field :id, ID, null: false
field :public_author, Types::UserType, null: true
field :public_created_at, String, null: true
field :votes_for, Types::VoteType.connection_type, null: true
end
end

View File

@@ -0,0 +1,22 @@
module Types
class DebateType < Types::BaseObject
field :cached_votes_down, Integer, null: true
field :cached_votes_total, Integer, null: true
field :cached_votes_up, Integer, null: true
field :comments, Types::CommentType.connection_type, null: true
field :comments_count, Integer, null: true
field :confidence_score, Integer, null: true
field :description, String, null: true
field :hot_score, Integer, null: true
field :id, ID, null: false
field :public_author, Types::UserType, null: true
field :public_created_at, String, null: true
field :tags, Types::TagType.connection_type, null: true
field :title, String, null: true
field :votes_for, Types::VoteType.connection_type, null: true
def tags
object.tags.public_for_api
end
end
end

View File

@@ -0,0 +1,6 @@
module Types
class GeozoneType < Types::BaseObject
field :id, ID, null: false
field :name, String, null: true
end
end

View File

@@ -0,0 +1,4 @@
module Types
class MutationType < Types::BaseObject
end
end

View File

@@ -0,0 +1,14 @@
module Types
class ProposalNotificationType < Types::BaseObject
field :body, String, null: true
field :id, ID, null: false
field :proposal, Types::ProposalType, null: true
field :proposal_id, Integer, null: true
field :public_created_at, String, null: true
field :title, String, null: true
def proposal
Proposal.public_for_api.find_by(id: object.proposal)
end
end
end

View File

@@ -0,0 +1,32 @@
module Types
class ProposalType < Types::BaseObject
field :cached_votes_up, Integer, null: true
field :comments, Types::CommentType.connection_type, null: true
field :comments_count, Integer, null: true
field :confidence_score, Integer, null: true
field :description, String, null: true
field :geozone, Types::GeozoneType, null: true
field :geozone_id, Integer, null: true
field :hot_score, Integer, null: true
field :id, ID, null: false
field :proposal_notifications, Types::ProposalNotificationType.connection_type, null: true
field :public_author, Types::UserType, null: true
field :public_created_at, String, null: true
field :retired_at, GraphQL::Types::ISO8601DateTime, null: true
field :retired_explanation, String, null: true
field :retired_reason, String, null: true
field :summary, String, null: true
field :tags, Types::TagType.connection_type, null: true
field :title, String, null: true
field :video_url, String, null: true
field :votes_for, Types::VoteType.connection_type, null: true
def tags
object.tags.public_for_api
end
def geozone
Geozone.public_for_api.find_by(id: object.geozone)
end
end
end

View File

@@ -0,0 +1,107 @@
module Types
class QueryType < Types::BaseObject
field :comments, Types::CommentType.connection_type, "Returns all comments", null: false
field :comment, Types::CommentType, "Returns comment for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :debates, Types::DebateType.connection_type, "Returns all debates", null: false
field :debate, Types::DebateType, "Returns debate for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :geozones, Types::GeozoneType.connection_type, "Returns all geozones", null: false
field :geozone, Types::GeozoneType, "Returns geozone for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :proposals, Types::ProposalType.connection_type, "Returns all proposals", null: false
field :proposal, Types::ProposalType, "Returns proposal for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :proposal_notifications, Types::ProposalNotificationType.connection_type, "Returns all proposal notifications", null: false
field :proposal_notification, Types::ProposalNotificationType, "Returns proposal notification for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :tags, Types::TagType.connection_type, "Returns all tags", null: false
field :tag, Types::TagType, "Returns tag for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :users, Types::UserType.connection_type, "Returns all users", null: false
field :user, Types::UserType, "Returns user for ID", null: false do
argument :id, ID, required: true, default_value: false
end
field :votes, Types::VoteType.connection_type, "Returns all votes", null: false
field :vote, Types::VoteType, "Returns vote for ID", null: false do
argument :id, ID, required: true, default_value: false
end
def comments
Comment.public_for_api
end
def comment(id:)
Comment.find(id)
end
def debates
Debate.public_for_api
end
def debate(id:)
Debate.find(id)
end
def geozones
Geozone.public_for_api
end
def geozone(id:)
Geozone.find(id)
end
def proposals
Proposal.public_for_api
end
def proposal(id:)
Proposal.find(id)
end
def proposal_notifications
ProposalNotification.public_for_api
end
def proposal_notification(id:)
ProposalNotification.find(id)
end
def tags
Tag.public_for_api
end
def tag(id:)
Tag.find(id)
end
def users
User.public_for_api
end
def user(id:)
User.find(id)
end
def votes
Vote.public_for_api
end
def vote(id:)
Vote.find(id)
end
end
end

View File

@@ -0,0 +1,8 @@
module Types
class TagType < Types::BaseObject
field :id, ID, null: false
field :kind, String, null: true
field :name, String, null: true
field :taggings_count, Integer, null: true
end
end

View File

@@ -0,0 +1,9 @@
module Types
class UserType < Types::BaseObject
field :id, ID, null: false
field :public_comments, Types::CommentType.connection_type, null: true
field :public_debates, Types::DebateType.connection_type, null: true
field :public_proposals, Types::ProposalType.connection_type, null: true
field :username, String, null: true
end
end

View File

@@ -0,0 +1,9 @@
module Types
class VoteType < Types::BaseObject
field :id, ID, null: false
field :public_created_at, String, null: true
field :votable_id, Integer, null: true
field :votable_type, String, null: true
field :vote_flag, Boolean, null: true
end
end