vodka 0.0.1

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 (81) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +14 -0
  3. data/Gemfile +4 -0
  4. data/LICENCE +13 -0
  5. data/README.md +119 -0
  6. data/lib/party.rb +9 -0
  7. data/lib/vodka/client/exceptions/exception.rb +6 -0
  8. data/lib/vodka/client/exceptions/failed_action_exception.rb +6 -0
  9. data/lib/vodka/client/exceptions/forbidden_exception.rb +11 -0
  10. data/lib/vodka/client/exceptions/not_found_exception.rb +11 -0
  11. data/lib/vodka/client/exceptions/resource_exception.rb +6 -0
  12. data/lib/vodka/client/middleware/error_aware.rb +21 -0
  13. data/lib/vodka/client/middleware/signed_request.rb +34 -0
  14. data/lib/vodka/client.rb +16 -0
  15. data/lib/vodka/configuration.rb +25 -0
  16. data/lib/vodka/her/extensions/extended_orm.rb +92 -0
  17. data/lib/vodka/her/extensions/will_paginate.rb +32 -0
  18. data/lib/vodka/server/controllers/vodka_controller.rb +32 -0
  19. data/lib/vodka/server/handlers/resource.rb +36 -0
  20. data/lib/vodka/server/handlers/response.rb +34 -0
  21. data/lib/vodka/server/handlers/scaffold.rb +63 -0
  22. data/lib/vodka/server/middleware/signed_request.rb +39 -0
  23. data/lib/vodka/server/plugins/presentable.rb +20 -0
  24. data/lib/vodka/server/railtie.rb +11 -0
  25. data/lib/vodka/server/response.rb +34 -0
  26. data/lib/vodka/server.rb +15 -0
  27. data/lib/vodka/version.rb +3 -0
  28. data/lib/vodka.rb +5 -0
  29. data/script/bundle_dummy +4 -0
  30. data/script/ci +4 -0
  31. data/script/setup_db +7 -0
  32. data/script/show_logs +22 -0
  33. data/script/spec +14 -0
  34. data/script/start_server +6 -0
  35. data/script/stop_server +6 -0
  36. data/spec/client/article.rb +4 -0
  37. data/spec/dummy/Gemfile +9 -0
  38. data/spec/dummy/Gemfile.lock +113 -0
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy/app/controllers/vodka/articles_controller.rb +8 -0
  42. data/spec/dummy/app/models/article.rb +10 -0
  43. data/spec/dummy/config/application.rb +64 -0
  44. data/spec/dummy/config/boot.rb +6 -0
  45. data/spec/dummy/config/database.yml +17 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +32 -0
  48. data/spec/dummy/config/environments/production.rb +54 -0
  49. data/spec/dummy/config/environments/test.rb +37 -0
  50. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/dummy/config/initializers/inflections.rb +15 -0
  52. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  53. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  54. data/spec/dummy/config/initializers/session_store.rb +8 -0
  55. data/spec/dummy/config/initializers/vodka_setup.rb +3 -0
  56. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/spec/dummy/config/locales/en.yml +2 -0
  58. data/spec/dummy/config/locales/ru.yml +2 -0
  59. data/spec/dummy/config/routes.rb +7 -0
  60. data/spec/dummy/config/thin.yml +10 -0
  61. data/spec/dummy/config.ru +4 -0
  62. data/spec/dummy/db/migrate/20130217185353_create_articles.rb +9 -0
  63. data/spec/dummy/db/schema.rb +22 -0
  64. data/spec/dummy/db/seeds.rb +1 -0
  65. data/spec/dummy/log/.gitkeep +0 -0
  66. data/spec/dummy/script/rails +6 -0
  67. data/spec/her/extensions/extended_orm/create_spec.rb +33 -0
  68. data/spec/her/extensions/extended_orm/delete_spec.rb +23 -0
  69. data/spec/her/extensions/extended_orm/destroy_spec.rb +35 -0
  70. data/spec/her/extensions/extended_orm/first_spec.rb +11 -0
  71. data/spec/her/extensions/extended_orm/last_spec.rb +11 -0
  72. data/spec/her/extensions/extended_orm/update_attribute_spec.rb +44 -0
  73. data/spec/her/extensions/extended_orm/update_attributes_spec.rb +44 -0
  74. data/spec/her/extensions/extended_orm/where_spec.rb +39 -0
  75. data/spec/her/extensions/will_paginate/paginate_spec.rb +16 -0
  76. data/spec/middleware/error_aware_spec.rb +19 -0
  77. data/spec/middleware/response_locale_spec.rb +12 -0
  78. data/spec/middleware/signed_request_spec.rb +9 -0
  79. data/spec/spec_helper.rb +24 -0
  80. data/vodka.gemspec +21 -0
  81. metadata +190 -0
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ /*.gem
2
+ /.bundle
3
+ /Gemfile.lock
4
+
5
+ /spec/dummy/db/*.sqlite3
6
+ /spec/dummy/log/*
7
+ /spec/dummy/tmp/pids/*
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ rvm:
2
+ - 1.9.3
3
+ script: ./script/ci
4
+ before_script:
5
+ - ./script/bundle_dummy
6
+ - ./script/setup_db
7
+ - ./script/start_server
8
+ after_script:
9
+ - ./script/stop_server
10
+ after_failure:
11
+ - ./script/show_logs
12
+ notifications:
13
+ hipchat:
14
+ secure: "SUoVBD+3cNXuzHK/xiww4e2jWdo9GHz+YE09s+z0K51nPtlTy9NFaBF41O7W\nMc4sz0ZxktnVrpVvyOGmLj933uyyRjhfK9X3R6Fn/BMzWDI1S4Z2NqhKq3xy\nGpLjEAX8UPtWXR0Cs4GxTwxTM6rjIW/I/5IpM6hiwRErSBXBin0="
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vodka.gemspec
4
+ gemspec
data/LICENCE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENCE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Vodka
2
+ Vodka makes communication easier. Always.
3
+
4
+ Vodka uses [Her](https://github.com/remiprev/her) as a REST client.
5
+
6
+ It currently supports ORM's on server:
7
+ - [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord)
8
+ - [MongoMapper](https://github.com/jnunemaker/mongomapper)
9
+
10
+ Plugins:
11
+ - [WillPaginate](https://github.com/mislav/will_paginate)
12
+
13
+ It is strongly recommended *NOT* to use this gem in production (yet).
14
+
15
+ ## Installation
16
+ Add this gem to both server and client application Gemfiles:
17
+ ```ruby
18
+ # Server
19
+ gem 'vodka', require: 'vodka/server'
20
+
21
+ # Client
22
+ gem 'vodka', require: 'vodka/client'
23
+ ```
24
+
25
+ ## Configuring server
26
+ Add initializer `vodka_setup.rb` to `config/initializers`:
27
+
28
+ ```ruby
29
+ Vodka::Server.configure do |c|
30
+ c.secret = 'whatever'
31
+ end
32
+ ```
33
+
34
+ Add vodka namespaced resources to your `config/routes.rb`
35
+ ```ruby
36
+ namespace :vodka do
37
+ resources :articles do
38
+ collection { get :best }
39
+ resources :comments do
40
+ member { put :approve }
41
+ end
42
+ end
43
+ end
44
+ ```
45
+
46
+ Create controllers in `app/controllers/vodka`
47
+ ```ruby
48
+ # articles_controller.rb
49
+ class ArticlesController < VodkaController
50
+ def best
51
+ respond_with_collection(Article.best)
52
+ end
53
+ end
54
+
55
+ # comments_controller.rb
56
+ class CommentsController < Vodka::Server::VodkaController
57
+ def approve
58
+ vodka_response.success = resource.approve
59
+ respond_with_resource
60
+ end
61
+ end
62
+ ```
63
+
64
+ Modify your models:
65
+ ```ruby
66
+ # article.rb
67
+ class Article < ActiveRecord::Base
68
+ has_many :comments
69
+ scope :best, ->{ where('rating > 100') }
70
+ validates_presence_of :title, :body
71
+
72
+ # Defines fields and actions that would be used in :as_json method
73
+ present_with :id, :title, :body, :created_at
74
+ end
75
+
76
+ # comment.rb
77
+ class Comment < ActiveRecord::Base
78
+ belongs_to :article
79
+ belongs_to :user
80
+ validates_presence_of :body
81
+
82
+ # Defines fields and actions that would be used in :as_json method
83
+ present_with :id, :body, :author_name, :created_at
84
+
85
+ def approve
86
+ update_attributes(status: 'approved')
87
+ end
88
+
89
+ def author_name
90
+ [user.first_name, user.last_name].join(' ')
91
+ end
92
+ end
93
+ ```
94
+
95
+ ## Configuring client
96
+ Add initializer `vodka_setup.rb` to `config/initializers`:
97
+
98
+ ```ruby
99
+ Vodka::Client.configure do |c|
100
+ c.api_url = 'https://api.myproject.org/vodka'
101
+ c.secret = 'whatever' # Same as server's
102
+ end
103
+ Vodka::Client.configure_her!
104
+ ```
105
+
106
+ ## Usage
107
+ After all the configuration is done, you can use your Her-applied models with all the new possibilities.
108
+
109
+ Vodka adds some convinient methods to client and supports them on server:
110
+ - `.create!` (throws exception on error)
111
+ - `.paginate` (same as `.all`, for WillPaginate compatibility)
112
+ - `.where` (supports chaining the way you expect)
113
+ - `#update_attribute`
114
+ - `#update_attribute!` (throws exception on error)
115
+ - `#update_attributes`
116
+ - `#update_attributes!` (throws exception on error)
117
+ - `#destroy!` (throws exception on error)
118
+ - `#delete` (acts as `#destroy`)
119
+ - `#delete!` (acts as `#destroy!`)
data/lib/party.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'i18n'
2
+ require 'vodka/client'
3
+ require './spec/client/article'
4
+
5
+ Vodka::Client.configure do |c|
6
+ c.api_url = 'http://0.0.0.0:3000/vodka'
7
+ c.secret = 'whatever'
8
+ end
9
+ Vodka::Client.configure_her!
@@ -0,0 +1,6 @@
1
+ module Vodka
2
+ module Client
3
+ class Exception < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Vodka
2
+ module Client
3
+ class FailedActionException < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module Vodka
2
+ module Client
3
+ class ForbiddenException < ::Exception
4
+ MESSAGE = '403 Forbidden'
5
+
6
+ def initialize
7
+ super(MESSAGE)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Vodka
2
+ module Client
3
+ class NotFoundException < ::Exception
4
+ MESSAGE = '404 Not Found'
5
+
6
+ def initialize
7
+ super(MESSAGE)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module Vodka
2
+ module Client
3
+ class ResourceException < ::Exception
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Vodka
2
+ module Client
3
+ module Middleware
4
+ class ErrorAware < Faraday::Response::Middleware
5
+ def on_complete(env)
6
+ env[:body] = MultiJson.load(env[:body]) unless env[:body].is_a?(Hash)
7
+ return unless env[:body][:errors].present? && env[:body][:errors][:vodka_error].present?
8
+
9
+ case env[:body][:errors][:vodka_error]
10
+ when ForbiddenException::MESSAGE
11
+ raise ForbiddenException.new
12
+ when NotFoundException::MESSAGE
13
+ raise NotFoundException.new
14
+ else
15
+ raise Exception.new(env[:body][:errors][:vodka_error])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,34 @@
1
+ module Vodka
2
+ module Client
3
+ module Middleware
4
+ class SignedRequest < Faraday::Middleware
5
+ attr_reader :app, :env
6
+
7
+ def initialize(app, options = {})
8
+ @app = app
9
+ end
10
+
11
+ def call(_env)
12
+ @env = _env
13
+
14
+ env[:request_headers]['X-Request-Id'] = request_id
15
+ env[:request_headers]['X-Request-Signature'] = request_signature
16
+ env[:request_headers]['X-Response-Locale'] = I18n.locale.to_s if defined?(I18n)
17
+
18
+ @app.call(env)
19
+ end
20
+
21
+ def request_id
22
+ @request_id ||= [Time.now.to_i, (rand * 100_000_000).to_i].join(?_)
23
+ end
24
+
25
+ def request_signature
26
+ Digest::SHA512.hexdigest([
27
+ env[:url].scheme, env[:url].host, env[:url].port, env[:url].path,
28
+ request_id, Vodka::Client.config.secret
29
+ ].join)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ require 'vodka'
2
+ require 'vodka/client/exceptions/exception'
3
+ require 'vodka/client/exceptions/failed_action_exception'
4
+ require 'vodka/client/exceptions/forbidden_exception'
5
+ require 'vodka/client/exceptions/not_found_exception'
6
+ require 'vodka/client/exceptions/resource_exception'
7
+ require 'vodka/client/middleware/signed_request'
8
+ require 'vodka/client/middleware/error_aware'
9
+ require 'vodka/her/extensions/extended_orm'
10
+ require 'vodka/her/extensions/will_paginate'
11
+
12
+ module Vodka
13
+ module Client
14
+ extend Configurable
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Vodka
2
+ class Configuration
3
+ attr_accessor :secret, :api_url
4
+ end
5
+
6
+ module Configurable
7
+ def config
8
+ @config ||= Configuration.new
9
+ end
10
+
11
+ def configure
12
+ yield config if block_given?
13
+ end
14
+
15
+ def configure_her!
16
+ ::Her::API.setup(url: Vodka::Client.config.api_url) do |c|
17
+ c.use Vodka::Client::Middleware::ErrorAware
18
+ c.use Vodka::Client::Middleware::SignedRequest
19
+ c.use Faraday::Request::UrlEncoded
20
+ c.use ::Her::Middleware::SecondLevelParseJSON
21
+ c.use Faraday::Adapter::NetHttp
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,92 @@
1
+ module Vodka
2
+ module Her
3
+ module Extensions
4
+ module ExtendedOrm
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def create!(*args)
9
+ resource = create(*args)
10
+ unless resource.errors.empty?
11
+ error = [resource.errors.keys.first, resource.errors.values.first].join(' ')
12
+ raise Vodka::Client::ResourceException.new(error)
13
+ end
14
+
15
+ resource
16
+ end
17
+
18
+ def first
19
+ resources = all(vodka_special_action: 'first')
20
+ resource = resources.first
21
+ resource.errors = resources.errors
22
+ resource.metadata = resources.metadata
23
+ resource
24
+ end
25
+
26
+ def last
27
+ resources = all(vodka_special_action: 'last')
28
+ resource = resources.first
29
+ resource.errors = resources.errors
30
+ resource.metadata = resources.metadata
31
+ resource
32
+ end
33
+
34
+ def where(*args)
35
+ @where_conditions ||= []
36
+ @where_conditions << args
37
+ self
38
+ end
39
+
40
+ def all(params = {})
41
+ unless @where_conditions.nil?
42
+ params[:vodka_special_where] = MultiJson.dump(@where_conditions)
43
+ @where_conditions = nil
44
+ end
45
+ super(params)
46
+ end
47
+ end
48
+
49
+ def update_attributes(params)
50
+ params.each do |attribute, value|
51
+ send(:"#{attribute}=", value)
52
+ end
53
+ save
54
+ end
55
+
56
+ def update_attributes!(params)
57
+ update_attributes(params)
58
+ unless errors.empty?
59
+ error = [errors.keys.first, errors.values.first].join(' ')
60
+ raise Vodka::Client::ResourceException.new(error)
61
+ end
62
+
63
+ self
64
+ end
65
+
66
+ def update_attribute(attribute, value)
67
+ update_attributes(attribute => value)
68
+ end
69
+
70
+ def update_attribute!(attribute, value)
71
+ update_attributes!(attribute => value)
72
+ end
73
+
74
+ def destroy!
75
+ destroy
76
+ raise Vodka::Client::FailedActionException.new('Destroy failed') if metadata[:vodka_action_success] == false
77
+ self
78
+ end
79
+
80
+ def delete
81
+ destroy
82
+ end
83
+
84
+ def delete!
85
+ destroy!
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ Her::Model.send(:include, Vodka::Her::Extensions::ExtendedOrm)
@@ -0,0 +1,32 @@
1
+ module Vodka
2
+ module Her
3
+ module Extensions
4
+ module WillPaginate
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def paginate(params)
9
+ response = all(params)
10
+ PaginatedCollection.new(response, response.metadata, response.errors)
11
+ end
12
+ end
13
+ end
14
+
15
+ class PaginatedCollection < ::Her::Collection
16
+ def current_page
17
+ metadata[:page].to_i
18
+ end
19
+
20
+ def per_page
21
+ metadata[:per_page].to_i
22
+ end
23
+
24
+ def total_entries
25
+ metadata[:total].to_i
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ Her::Model.send(:include, Vodka::Her::Extensions::WillPaginate)
@@ -0,0 +1,32 @@
1
+ require 'action_controller' unless defined?(::ActionController)
2
+
3
+ module Vodka
4
+ module Server
5
+ class VodkaController < ::ActionController::Base
6
+ include Handlers::Scaffold
7
+ include Handlers::Resource
8
+ include Handlers::Response
9
+
10
+ before_filter :fix_params_names, :set_locale
11
+ before_filter :handle_not_found, only: [:show, :update, :destroy]
12
+
13
+ private
14
+
15
+ def set_locale
16
+ return unless defined?(I18n)
17
+ locale = request.headers['X-Response-Locale']
18
+ I18n.locale = locale if locale.present? && locale.to_sym.in?(I18n.available_locales)
19
+ end
20
+
21
+ def fix_params_names
22
+ params[:password] = params.delete(:buzzword) unless params[:buzzword].nil?
23
+ end
24
+
25
+ def handle_not_found
26
+ not_found if resource.nil? && vodka_response.success == false
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ VodkaController = Vodka::Server::VodkaController
@@ -0,0 +1,36 @@
1
+ module Vodka
2
+ module Server
3
+ module Handlers
4
+ module Resource
5
+ private
6
+
7
+ def resource
8
+ @resource ||= extract_resource
9
+ end
10
+
11
+ def extract_resource
12
+ return unless params.has_key?(:id)
13
+
14
+ new_resource = resource_class.find_by_id(params[:id])
15
+ vodka_response.success = false if new_resource.nil?
16
+
17
+ new_resource
18
+ end
19
+
20
+ def filtered_params
21
+ params.slice(*resource_class.accessible_attributes.to_a)
22
+ end
23
+
24
+ # Resource magick
25
+
26
+ def resource_name
27
+ self.class.name.demodulize.underscore.gsub('_controller', '').singularize
28
+ end
29
+
30
+ def resource_class
31
+ resource_name.camelcase.constantize
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ module Vodka
2
+ module Server
3
+ module Handlers
4
+ module Response
5
+
6
+ def vodka_response
7
+ @vodka_response ||= Vodka::Server::Response.new
8
+ end
9
+
10
+ private
11
+
12
+ def respond_with_resource(custom_resource = nil)
13
+ vodka_response.data = custom_resource || resource
14
+ respond!
15
+ end
16
+
17
+ def respond_with_collection(resources)
18
+ vodka_response.data = resources
19
+ respond!
20
+ end
21
+
22
+ def respond!
23
+ return render text: vodka_response.json, status: vodka_response.code, content_type: 'application/json'
24
+ end
25
+
26
+ def not_found
27
+ vodka_response.errors[:vodka_error] = '404 Not Found'
28
+ vodka_response.code = 404
29
+ respond!
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,63 @@
1
+ module Vodka
2
+ module Server
3
+ module Handlers
4
+ module Scaffold
5
+ def index
6
+ if params[:vodka_special_action] == 'first'
7
+ resources = [resource_class.first]
8
+ elsif params[:vodka_special_action] == 'last'
9
+ resources = [resource_class.last]
10
+ elsif params[:vodka_special_where].present?
11
+ resources = vodka_special_where
12
+ elsif params[:page].present? && defined?(::WillPaginate)
13
+ resources = vodka_special_paginate
14
+ else
15
+ resources = resource_class.all
16
+ end
17
+
18
+ respond_with_collection(resources)
19
+ end
20
+
21
+ def show
22
+ respond_with_resource
23
+ end
24
+
25
+ def create
26
+ respond_with_resource(resource_class.create(filtered_params))
27
+ end
28
+
29
+ def update
30
+ vodka_response.success = resource.update_attributes(filtered_params)
31
+ respond_with_resource
32
+ end
33
+
34
+ def destroy
35
+ resource.destroy
36
+ vodka_response.success = resource.destroyed?
37
+ respond_with_resource
38
+ end
39
+
40
+ private
41
+
42
+ def vodka_special_where
43
+ relation = resource_class
44
+ conditions = MultiJson.load(params[:vodka_special_where])
45
+ conditions.each do |condition|
46
+ if condition.is_a?(Hash)
47
+ relation = relation.where(condition)
48
+ else
49
+ relation = relation.where(*condition)
50
+ end
51
+ end
52
+ relation.all
53
+ end
54
+
55
+ def vodka_special_paginate
56
+ data = resource_class.paginate(page: params[:page], per_page: params[:per_page]).all
57
+ vodka_response.metadata = { page: data.current_page, per_page: data.per_page, total: resource_class.count }
58
+ data
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,39 @@
1
+ module Vodka
2
+ module Server
3
+ module Middleware
4
+ class SignedRequest
5
+ attr_reader :app, :env, :request
6
+
7
+ def initialize(app, options = {})
8
+ @app, @options = app, options
9
+ end
10
+
11
+ def call(env)
12
+ @env = env
13
+ @request = Rack::Request.new(env)
14
+
15
+ request_signature_valid? ? app.call(env) : forbidden
16
+ end
17
+
18
+ def request_signature_valid?
19
+ env['HTTP_X_REQUEST_SIGNATURE'] == request_signature
20
+ end
21
+
22
+ def request_signature
23
+ Digest::SHA512.hexdigest([
24
+ request.scheme, request.host, request.port, request.path,
25
+ env['HTTP_X_REQUEST_ID'], Vodka::Server.config.secret
26
+ ].join)
27
+ end
28
+
29
+ def forbidden
30
+ [
31
+ 403,
32
+ { 'Content-Type' => 'application/json; charset=utf-8' },
33
+ ['{"data":null,"errors":{"vodka_error":"403 Forbidden"},"metadata":{}}']
34
+ ]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ module Vodka
2
+ module Server
3
+ module Plugins
4
+ module Presentable
5
+ def present_with(*methods)
6
+ define_method 'present_vodka' do
7
+ json = Hash[methods.map{ |method| [method, send(method)] }]
8
+ json[:errors] = errors.messages
9
+ json[:metadata] = {}
10
+ json
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ ActiveRecord::Base.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(ActiveRecord)
19
+ MongoMapper::Document.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(MongoMapper)
20
+ MongoMapper::EmbeddedDocument.send(:extend, Vodka::Server::Plugins::Presentable) if defined?(MongoMapper)
@@ -0,0 +1,11 @@
1
+ require 'rails' unless defined?(::Rails)
2
+
3
+ module Vodka
4
+ module Server
5
+ class Railtie < ::Rails::Railtie
6
+ initializer 'vodka.inject_middleware' do |app|
7
+ app.config.middleware.use 'Vodka::Server::Middleware::SignedRequest'
8
+ end
9
+ end
10
+ end
11
+ end