Skip to content

Commit

Permalink
implement GraphQL API
Browse files Browse the repository at this point in the history
  • Loading branch information
rutan committed Jan 8, 2017
1 parent f16522e commit 870eda2
Show file tree
Hide file tree
Showing 91 changed files with 372 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

AllCops:
Exclude:
- config.ru
Expand All @@ -10,6 +9,7 @@ AllCops:
- vendor/**/*
- spec/spec_helper.rb
- spec/rails_helper.rb
TargetRubyVersion: 2.3

AsciiComments:
Enabled: false
Expand Down
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '2.3.1'

gem 'rails', '4.2.5.1'
gem 'rails-i18n'

gem 'graphql'
gem 'pundit'

gem 'slim-rails'
gem 'sass-rails', '~> 5.0'
gem 'gemoji'
Expand Down
7 changes: 6 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ GEM
gemoji (2.1.0)
globalid (0.3.7)
activesupport (>= 4.1.0)
graphql (1.3.0)
hashdiff (0.3.0)
hashie (3.4.6)
html-pipeline (2.4.2)
Expand Down Expand Up @@ -209,6 +210,8 @@ GEM
pry-rails (0.3.4)
pry (>= 0.9.10)
puma (3.6.0)
pundit (1.1.0)
activesupport (>= 3.0.0)
quiet_assets (1.1.0)
railties (>= 3.1, < 5.0)
rack (1.6.4)
Expand Down Expand Up @@ -362,6 +365,7 @@ DEPENDENCIES
fakes3
foreman
gemoji
graphql
html-pipeline-nico_link
kaminari
konpow!
Expand All @@ -377,6 +381,7 @@ DEPENDENCIES
pry-byebug
pry-rails
puma
pundit
quiet_assets
rails (= 4.2.5.1)
rails-i18n
Expand All @@ -400,4 +405,4 @@ RUBY VERSION
ruby 2.3.1p112

BUNDLED WITH
1.13.6
1.13.7
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

Expand Down
7 changes: 7 additions & 0 deletions app/apis/graph/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true
module Graph
Schema = GraphQL::Schema.define do
query Graph::Types::RootQuery
max_depth 5
end
end
28 changes: 28 additions & 0 deletions app/apis/graph/types/article.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true
module Graph
module Types
Article = GraphQL::ObjectType.define do
name 'Article'
description 'A blog entry'

field :id, types.String
field :title, types.String
field :body, types.String do
resolve -> (obj, _args, _context) do
obj.newest_revision.body
end
end
field :html, types.String do
resolve -> (obj, _args, _context) do
obj.newest_revision.markdown_html
end
end
field :tags, types[Types::Tag] do
resolve -> (obj, _args, _context) do
obj.tags
end
end
field :user, -> { Types::User }
end
end
end
49 changes: 49 additions & 0 deletions app/apis/graph/types/root_query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true
module Graph
module Types
RootQuery = GraphQL::ObjectType.define do
name 'RootQuery'
description 'The query root'

field :article do
type -> { Types::Article }
description 'Get an article'

argument :id, types.String

resolve -> (_object, args, context) do
::Article.find(args[:id]).tap do |article|
context[:pundit].authorize(article, :show?)
end.decorate
end
end

field :articles do
type -> { types[Types::Article] }
description 'Get articles'

argument :size, types.Int, 'get size', default_value: 10
argument :page, types.Int, 'page', default_value: 1

resolve -> (_object, args, _context) do
size = [[args[:size], 25].min, 1].max
page = [[args[:page], 100].min, 1].max
::Article.public_items.page(page).per(size).decorate
end
end

field :user do
type -> { Types::User }
description 'Get a user'

argument :id, types.Int

resolve -> (_object, args, context) do
::User.find(args[:id]).tap do |user|
context[:pundit].authorize(user, :show?)
end.decorate
end
end
end
end
end
12 changes: 12 additions & 0 deletions app/apis/graph/types/tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true
module Graph
module Types
Tag = GraphQL::ObjectType.define do
name 'Tag'
description 'A tag'

field :name, types.String
field :code, types.String, property: :content
end
end
end
15 changes: 15 additions & 0 deletions app/apis/graph/types/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module Graph
module Types
User = GraphQL::ObjectType.define do
name 'User'
description 'A user'

field :id, types.Int
field :name, types.String
field :url, types.String
field :avatarURL, types.String, property: :avatar_url
field :stockCount, types.Int, property: :stock_count
end
end
end
1 change: 1 addition & 0 deletions app/builders/article_builder.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class ArticleBuilder
def initialize(article)
@article = article
Expand Down
1 change: 1 addition & 0 deletions app/builders/comment_builder.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class CommentBuilder
def initialize(article)
@article = article
Expand Down
19 changes: 18 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
include Pundit

# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
Expand All @@ -19,6 +22,14 @@ def current_user
@current_user ||= User.find_by(id: session[:user_id].to_i)
end

def current_access_token
AccessToken.generate_master(current_user) if current_user
end

def pundit_user
current_access_token
end

def render_json(target, status: 200, message: '')
render(json: {
meta: {
Expand All @@ -41,11 +52,13 @@ def set_page

rescue_from Exception, with: :render_500 unless Rails.env.development?
rescue_from Errors::BadRequest, with: :render_400
rescue_from Errors::Forbidden, with: :render_403
rescue_from Errors::Unauthorized, with: :render_401
rescue_from Errors::Forbidden, with: :render_403
rescue_from Pundit::NotAuthorizedError, with: :render_403
rescue_from ActiveRecord::RecordNotFound, with: :render_404
rescue_from ActionController::RoutingError, with: :render_404 unless Rails.env.development?
rescue_from Errors::NotFound, with: :render_404
rescue_from Errors::UnprocessableEntity, with: :render_422

def render_error(status, message = '')
@status = status
Expand All @@ -72,6 +85,10 @@ def render_404
render_error(404, 'Not Found')
end

def render_422
render_error(422, 'Unprocessable Entity')
end

def render_500
render_error(500, 'Internal Server Error')
end
Expand Down
1 change: 1 addition & 0 deletions app/controllers/articles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class ArticlesController < ApplicationController
before_action :set_user!
before_action :set_article!, only: [:show, :edit, :update, :destroy, :create_like, :destroy_like]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/attachment_files_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class AttachmentFilesController < ApplicationController
before_action :require_login!

Expand Down
1 change: 1 addition & 0 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class CommentsController < ApplicationController
before_action :require_login!
before_action :set_user_and_article!, only: [:create]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/lobbies_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class LobbiesController < ApplicationController
before_action :set_page

Expand Down
55 changes: 55 additions & 0 deletions app/controllers/queries_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true
class QueriesController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :check_csrf

# POST /graphql.json
def create
query = params[:query].to_s
render json: Graph::Schema.execute(
query,
context: {
variables: param_variables,
access_token: current_access_token,
pundit: self
}
)
end

private

def current_access_token
if params[:token].present?
AccessToken.find_by(token: params[:token])
else
super
end
end

def private_mode!
raise Errors::Unauthorized unless current_access_token.try(:user)
end

def check_csrf
raise Errors::UnprocessableEntity unless request.headers['Host'] == safe_host
x_from = request.headers['X-From']
raise Errors::UnprocessableEntity if x_from.blank?
origin = request.headers['Origin']
raise Errors::UnprocessableEntity if origin && origin != 'null' && !x_from.start_with?("#{origin}/")
end

def safe_host
GlobalSetting.root_url.match(/\/(?<host>[^\/]+)(?:\/|\z)/).try(:[], :host)
end

def param_variables
variables = params[:variables]
if variables.blank?
{}
elsif variables.is_a?(String)
JSON.parse(variables, quirks_mode: true)
else
variables
end
end
end
1 change: 1 addition & 0 deletions app/controllers/revisions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class RevisionsController < ApplicationController
before_action :set_user!
before_action :set_article!
Expand Down
1 change: 1 addition & 0 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class SessionsController < ApplicationController
skip_before_action :private_mode!, only: [:callback, :failure]

Expand Down
1 change: 1 addition & 0 deletions app/controllers/stocks_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class StocksController < ApplicationController
before_action :set_article!
before_action :set_stock
Expand Down
1 change: 1 addition & 0 deletions app/controllers/tags_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class TagsController < ApplicationController
before_action :set_tag, only: [:show, :popular, :edit, :update]
before_action :set_page, only: [:show, :popular]
Expand Down
1 change: 1 addition & 0 deletions app/controllers/users/access_tokens_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
module Users
class AccessTokensController < ::ApplicationController
before_action :require_login!
Expand Down
1 change: 1 addition & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class UsersController < ApplicationController
before_action :check_register_mode!, only: [:new, :create]
before_action :require_login!, only: [:edit, :update]
Expand Down
1 change: 1 addition & 0 deletions app/decorators/article_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class ArticleDecorator < Draper::Decorator
delegate_all
include DecorateSerializer
Expand Down
1 change: 1 addition & 0 deletions app/decorators/attachment_file_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class AttachmentFileDecorator < Draper::Decorator
delegate_all
include DecorateSerializer
Expand Down
1 change: 1 addition & 0 deletions app/decorators/comment_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class CommentDecorator < Draper::Decorator
delegate_all
include DecorateSerializer
Expand Down
1 change: 1 addition & 0 deletions app/decorators/revision_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
class RevisionDecorator < Draper::Decorator
delegate_all
include DecorateSerializer
Expand Down
1 change: 1 addition & 0 deletions app/decorators/user_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
require 'digest/md5'

class UserDecorator < Draper::Decorator
Expand Down
1 change: 1 addition & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# frozen_string_literal: true
module ApplicationHelper
def current_user
@current_user
Expand Down
Loading

0 comments on commit 870eda2

Please sign in to comment.