Skip to content

Commit

Permalink
Merge pull request #49 from goinvo/adding-graphql
Browse files Browse the repository at this point in the history
Adds a first pass at a GraphQL API
  • Loading branch information
fermion authored Jan 5, 2024
2 parents 5f7790b + eb5caa6 commit de772af
Show file tree
Hide file tree
Showing 31 changed files with 483 additions and 23 deletions.
23 changes: 4 additions & 19 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,28 @@ ruby "3.2.0"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "7.1.2"

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"

# Use postgresql as the database for Active Record
gem "pg", "~> 1.1"

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"

# Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
gem "tailwindcss-rails"

# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"

# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"

# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]

gem "passwordless", "~> 1.1"
gem "graphql", "~> 2.2"
gem "view_component"

# Reduces boot times through caching; required in config/boot.rb
Expand All @@ -66,6 +52,7 @@ group :development do
# gem "rack-mini-profiler"

gem "letter_opener"
gem "graphiql-rails"
end

group :test do
Expand All @@ -77,6 +64,4 @@ group :test do
gem "factory_bot_rails"
gem 'shoulda-matchers', '~> 5.0'
gem "timecop"
end

gem "passwordless", "~> 1.1"
end
11 changes: 7 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ GEM
i18n (>= 1.8.11, < 2)
globalid (1.2.1)
activesupport (>= 6.1)
graphiql-rails (1.9.0)
railties
sprockets-rails
graphql (2.2.3)
racc (~> 1.4)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
importmap-rails (1.2.3)
Expand All @@ -130,9 +135,6 @@ GEM
irb (1.11.0)
rdoc
reline (>= 0.3.8)
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.8.1)
Expand Down Expand Up @@ -305,8 +307,9 @@ DEPENDENCIES
dotenv-rails
factory_bot_rails
faker
graphiql-rails
graphql (~> 2.2)
importmap-rails
jbuilder
letter_opener
passwordless (~> 1.1)
pg (~> 1.1)
Expand Down
55 changes: 55 additions & 0 deletions app/controllers/graphql_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

class GraphqlController < ApplicationController
# If accessing from outside this domain, nullify the session
# This allows for outside API access while preventing CSRF attacks,
# but you'll have to authenticate your user separately
# protect_from_forgery with: :null_session

skip_before_action :verify_authenticity_token

def execute
variables = prepare_variables(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
# Query context goes here, for example:
current_user: current_user,
current_company: current_company
}
result = StaffplanReduxSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
rescue StandardError => e
raise e unless Rails.env.development?
handle_error_in_development(e)
end

private

# Handle variables in form data, JSON body, or a blank value
def prepare_variables(variables_param)
case variables_param
when String
if variables_param.present?
JSON.parse(variables_param) || {}
else
{}
end
when Hash
variables_param
when ActionController::Parameters
variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables.
when nil
{}
else
raise ArgumentError, "Unexpected parameter: #{variables_param}"
end
end

def handle_error_in_development(e)
logger.error e.message
logger.error e.backtrace.join("\n")

render json: { errors: [{ message: e.message, backtrace: e.backtrace }], data: {} }, status: 500
end
end
Empty file added app/graphql/mutations/.keep
Empty file.
7 changes: 7 additions & 0 deletions app/graphql/mutations/base_mutation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Mutations
class BaseMutation < GraphQL::Schema::Mutation
null false
end
end
14 changes: 14 additions & 0 deletions app/graphql/mutations/create_work_week.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Mutations
class CreateWorkWeek < BaseMutation
# arguments passed to the `resolve` method
# argument :description, String, required: true
# argument :url, String, required: true

# return type from the mutation
type Types::StaffPlan::WorkWeekType

def resolve
# TODO: implement
end
end
end
13 changes: 13 additions & 0 deletions app/graphql/mutations/set_current_company.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Mutations
class SetCurrentCompany < BaseMutation
# arguments passed to the `resolve` method
argument :company_id, ID, required: true

# return type from the mutation
type Types::StaffPlan::CompanyType

def resolve
# TODO: implement
end
end
end
14 changes: 14 additions & 0 deletions app/graphql/mutations/update_work_week.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Mutations
class UpdateWorkWeek < BaseMutation
# arguments passed to the `resolve` method
# argument :description, String, required: true
# argument :url, String, required: true

# return type from the mutation
type Types::StaffPlan::WorkWeekType

def resolve
# TODO: implement
end
end
end
4 changes: 4 additions & 0 deletions app/graphql/resolvers/base_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
end
end
42 changes: 42 additions & 0 deletions app/graphql/staffplan_redux_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

class StaffplanReduxSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)

# For batch-loading (see https://graphql-ruby.org/dataloader/overview.html)
use GraphQL::Dataloader

# GraphQL-Ruby calls this when something goes wrong while running a query:
def self.type_error(err, context)
# if err.is_a?(GraphQL::InvalidNullError)
# # report to your bug tracker here
# return nil
# end
super
end

# Union and Interface Resolution
def self.resolve_type(abstract_type, obj, ctx)
# TODO: Implement this method
# to return the correct GraphQL object type for `obj`
raise(GraphQL::RequiredImplementationMissingError)
end

# Stop validating when it encounters this many errors:
validate_max_errors(100)

# Relay-style Object Identification:

# Return a string UUID for `object`
def self.id_from_object(object, type_definition, query_ctx)
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
object.to_gid_param
end

# Given a string UUID, find the object
def self.object_from_id(global_id, query_ctx)
# For example, use Rails' GlobalID library (https://github.com/rails/globalid):
GlobalID.find(global_id)
end
end
Empty file added app/graphql/types/.keep
Empty file.
6 changes: 6 additions & 0 deletions app/graphql/types/base_argument.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module Types
class BaseArgument < GraphQL::Schema::Argument
end
end
8 changes: 8 additions & 0 deletions app/graphql/types/base_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module Types
class BaseConnection < Types::BaseObject
# add `nodes` and `pageInfo` fields, as well as `edge_type(...)` and `node_nullable(...)` overrides
include GraphQL::Types::Relay::ConnectionBehaviors
end
end
8 changes: 8 additions & 0 deletions app/graphql/types/base_edge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module Types
class BaseEdge < Types::BaseObject
# add `node` and `cursor` fields, as well as `node_type(...)` override
include GraphQL::Types::Relay::EdgeBehaviors
end
end
6 changes: 6 additions & 0 deletions app/graphql/types/base_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module Types
class BaseEnum < GraphQL::Schema::Enum
end
end
7 changes: 7 additions & 0 deletions app/graphql/types/base_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Types
class BaseField < GraphQL::Schema::Field
argument_class Types::BaseArgument
end
end
7 changes: 7 additions & 0 deletions app/graphql/types/base_input_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Types
class BaseInputObject < GraphQL::Schema::InputObject
argument_class Types::BaseArgument
end
end
11 changes: 11 additions & 0 deletions app/graphql/types/base_interface.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Types
module BaseInterface
include GraphQL::Schema::Interface
edge_type_class(Types::BaseEdge)
connection_type_class(Types::BaseConnection)

field_class Types::BaseField
end
end
9 changes: 9 additions & 0 deletions app/graphql/types/base_object.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Types
class BaseObject < GraphQL::Schema::Object
edge_type_class(Types::BaseEdge)
connection_type_class(Types::BaseConnection)
field_class Types::BaseField
end
end
6 changes: 6 additions & 0 deletions app/graphql/types/base_scalar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

module Types
class BaseScalar < GraphQL::Schema::Scalar
end
end
8 changes: 8 additions & 0 deletions app/graphql/types/base_union.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module Types
class BaseUnion < GraphQL::Schema::Union
edge_type_class(Types::BaseEdge)
connection_type_class(Types::BaseConnection)
end
end
9 changes: 9 additions & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Types
class MutationType < Types::BaseObject
field :set_current_company, mutation: Mutations::SetCurrentCompany
field :create_work_week, mutation: Mutations::CreateWorkWeek
field :update_work_week, mutation: Mutations::UpdateWorkWeek
end
end
9 changes: 9 additions & 0 deletions app/graphql/types/node_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module Types
module NodeType
include Types::BaseInterface
# Add the `id` field
include GraphQL::Types::Relay::NodeBehaviors
end
end
Loading

0 comments on commit de772af

Please sign in to comment.