parse-stack 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +77 -0
- data/LICENSE +20 -0
- data/README.md +1281 -0
- data/Rakefile +12 -0
- data/bin/console +20 -0
- data/bin/server +10 -0
- data/bin/setup +7 -0
- data/lib/parse/api/all.rb +13 -0
- data/lib/parse/api/analytics.rb +16 -0
- data/lib/parse/api/apps.rb +37 -0
- data/lib/parse/api/batch.rb +148 -0
- data/lib/parse/api/cloud_functions.rb +18 -0
- data/lib/parse/api/config.rb +22 -0
- data/lib/parse/api/files.rb +21 -0
- data/lib/parse/api/hooks.rb +68 -0
- data/lib/parse/api/objects.rb +77 -0
- data/lib/parse/api/push.rb +16 -0
- data/lib/parse/api/schemas.rb +25 -0
- data/lib/parse/api/sessions.rb +11 -0
- data/lib/parse/api/users.rb +43 -0
- data/lib/parse/client.rb +225 -0
- data/lib/parse/client/authentication.rb +59 -0
- data/lib/parse/client/body_builder.rb +69 -0
- data/lib/parse/client/caching.rb +103 -0
- data/lib/parse/client/protocol.rb +15 -0
- data/lib/parse/client/request.rb +43 -0
- data/lib/parse/client/response.rb +116 -0
- data/lib/parse/model/acl.rb +182 -0
- data/lib/parse/model/associations/belongs_to.rb +121 -0
- data/lib/parse/model/associations/collection_proxy.rb +202 -0
- data/lib/parse/model/associations/has_many.rb +218 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +71 -0
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -0
- data/lib/parse/model/bytes.rb +50 -0
- data/lib/parse/model/core/actions.rb +499 -0
- data/lib/parse/model/core/properties.rb +377 -0
- data/lib/parse/model/core/querying.rb +100 -0
- data/lib/parse/model/core/schema.rb +92 -0
- data/lib/parse/model/date.rb +50 -0
- data/lib/parse/model/file.rb +127 -0
- data/lib/parse/model/geopoint.rb +98 -0
- data/lib/parse/model/model.rb +120 -0
- data/lib/parse/model/object.rb +347 -0
- data/lib/parse/model/pointer.rb +106 -0
- data/lib/parse/model/push.rb +99 -0
- data/lib/parse/query.rb +378 -0
- data/lib/parse/query/constraint.rb +130 -0
- data/lib/parse/query/constraints.rb +176 -0
- data/lib/parse/query/operation.rb +66 -0
- data/lib/parse/query/ordering.rb +49 -0
- data/lib/parse/stack.rb +11 -0
- data/lib/parse/stack/version.rb +5 -0
- data/lib/parse/webhooks.rb +228 -0
- data/lib/parse/webhooks/payload.rb +115 -0
- data/lib/parse/webhooks/registration.rb +139 -0
- data/parse-stack.gemspec +45 -0
- metadata +340 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Parse
|
4
|
+
|
5
|
+
module API
|
6
|
+
#object fetch methods
|
7
|
+
module Schema
|
8
|
+
SCHEMA_PREFIX = "/1/schemas/".freeze
|
9
|
+
def schema(className)
|
10
|
+
request :get, "#{SCHEMA_PREFIX}#{className}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_schema(className, schema)
|
14
|
+
request :post, "#{SCHEMA_PREFIX}#{className}", body: schema
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_schema(className, schema)
|
18
|
+
request :put, "#{SCHEMA_PREFIX}#{className}", body: schema
|
19
|
+
end
|
20
|
+
|
21
|
+
end #Schema
|
22
|
+
|
23
|
+
end #API
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Parse
|
4
|
+
|
5
|
+
module API
|
6
|
+
module Users
|
7
|
+
# Note that Parse::Objects mainly use the objects.rb API since we can
|
8
|
+
# detect class names to proper URI handlers
|
9
|
+
USER_PATH_PREFIX = "/1/users".freeze
|
10
|
+
USER_CLASS = "_User".freeze
|
11
|
+
def fetch_user(id)
|
12
|
+
request :get, "#{USER_PATH_PREFIX}/#{id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_users(query = {})
|
16
|
+
response = request :get, "#{USER_PATH_PREFIX}", query: query
|
17
|
+
response.parse_class = USER_CLASS
|
18
|
+
response
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_me(query = {})
|
22
|
+
response = request :get, "#{USER_PATH_PREFIX}/me", query: query
|
23
|
+
response.parse_class = USER_CLASS
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_user(id, body = {})
|
28
|
+
response = request :put, "#{USER_PATH_PREFIX}/#{id}", body: body
|
29
|
+
response.parse_class = USER_CLASS
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_user(id)
|
34
|
+
request :delete, "#{USER_PATH_PREFIX}/#{id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
end # Users
|
40
|
+
|
41
|
+
end #API
|
42
|
+
|
43
|
+
end #Parse
|
data/lib/parse/client.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require_relative "client/request"
|
4
|
+
require_relative "client/response"
|
5
|
+
require_relative "client/body_builder"
|
6
|
+
require_relative "client/authentication"
|
7
|
+
require_relative "client/caching"
|
8
|
+
require_relative "api/all"
|
9
|
+
|
10
|
+
module Parse
|
11
|
+
# Main class for the client. The client class is based on a Faraday stack.
|
12
|
+
# The Faraday stack is similar to a Rack-style application in which you can define middlewares
|
13
|
+
# that will be called for each request going out and coming back in. We use this in order to setup
|
14
|
+
# some helper middlewares such as encoding to JSON, handling Parse authentication and caching.
|
15
|
+
class Client
|
16
|
+
include Parse::API::Objects
|
17
|
+
include Parse::API::Config
|
18
|
+
include Parse::API::Files
|
19
|
+
include Parse::API::CloudFunctions
|
20
|
+
include Parse::API::Users
|
21
|
+
include Parse::API::Sessions
|
22
|
+
include Parse::API::Hooks
|
23
|
+
include Parse::API::Apps
|
24
|
+
include Parse::API::Batch
|
25
|
+
include Parse::API::Push
|
26
|
+
include Parse::API::Schema
|
27
|
+
|
28
|
+
attr_accessor :session, :cache
|
29
|
+
attr_reader :application_id, :api_key, :master_key, :version, :host
|
30
|
+
# The client can support multiple sessions. The first session created, will be placed
|
31
|
+
# under the default session tag. The :default session will be the default client to be used
|
32
|
+
# by the other classes including Parse::Query and Parse::Objects
|
33
|
+
@@sessions = { default: nil }
|
34
|
+
|
35
|
+
# get a session for a given tag. This will also create a new one for the tag if not specified.
|
36
|
+
def self.session(connection = :default)
|
37
|
+
#warn "Please call Parse::Client.setup() to initialize your parse session" if @@sessions.empty? || @@sessions[:default].nil?
|
38
|
+
@@sessions[connection] ||= self.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.setup(opts = {})
|
42
|
+
# If Proc.new is called from inside a method without any arguments of
|
43
|
+
# its own, it will return a new Proc containing the block given to
|
44
|
+
# its surrounding method.
|
45
|
+
# http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html
|
46
|
+
@@sessions[:default] = self.new(opts, &Proc.new)
|
47
|
+
end
|
48
|
+
|
49
|
+
# This builds a new Parse::Client stack. The options are:
|
50
|
+
# required
|
51
|
+
# :application_id - Parse Application ID. If not set it will be read from the
|
52
|
+
# PARSE_APP_ID environment variable
|
53
|
+
# :api_key - the Parse REST API Key. If not set it will be
|
54
|
+
# read from PARSE_API_KEY environment variable
|
55
|
+
# :master_key - the Parse Master Key (optional). If PARSE_MASTER_KEY env is set
|
56
|
+
# it will be used.
|
57
|
+
# optional
|
58
|
+
# :logger - boolean - whether to print the requests and responses.
|
59
|
+
# :cache - Moneta::Transformer - if set, it should be a Moneta store instance
|
60
|
+
# :expires - Integer - if set, it should be a Moneta store instance
|
61
|
+
# :adapter - the HTTP adapter to use with Faraday, defaults to Faraday.default_adapter
|
62
|
+
# :host - defaults to Parse::Protocol::Host (api.parse.com)
|
63
|
+
def initialize(opts = {})
|
64
|
+
@host = Parse::Protocol::HOST
|
65
|
+
@version = 1
|
66
|
+
@application_id = opts[:application_id] || ENV["PARSE_APP_ID"]
|
67
|
+
@api_key = opts[:api_key] || ENV["PARSE_API_KEY"]
|
68
|
+
@master_key = opts[:master_key] || ENV["PARSE_MASTER_KEY"]
|
69
|
+
opts[:adapter] ||= Faraday.default_adapter
|
70
|
+
opts[:expires] ||= 3
|
71
|
+
if @application_id.nil? || ( @api_key.nil? && @master_key.nil? )
|
72
|
+
raise "Please call Parse.setup(application_id:, api_key:) to setup a session"
|
73
|
+
end
|
74
|
+
@version_prefix = "/1/".freeze
|
75
|
+
#Configure Faraday
|
76
|
+
@session = Faraday.new("https://#{@host}", opts[:faraday]) do |conn|
|
77
|
+
#conn.request :json
|
78
|
+
|
79
|
+
conn.response :logger if opts[:logging]
|
80
|
+
|
81
|
+
# This middleware handles sending the proper authentication headers to Parse
|
82
|
+
# on each request.
|
83
|
+
|
84
|
+
# this is the required authentication middleware. Should be the first thing
|
85
|
+
# so that other middlewares have access to the env that is being set by
|
86
|
+
# this middleware. First added is first to brocess.
|
87
|
+
conn.use Parse::Middleware::Authentication,
|
88
|
+
application_id: @application_id,
|
89
|
+
master_key: @master_key,
|
90
|
+
api_key: @api_key
|
91
|
+
# This middleware turns the result from Parse into a Parse::Response object
|
92
|
+
# and making sure request that are going out, follow the proper MIME format.
|
93
|
+
# We place it after the Authentication middleware in case we need to use then
|
94
|
+
# authentication information when building request and responses.
|
95
|
+
conn.use Parse::Middleware::BodyBuilder
|
96
|
+
if opts[:cache].present? && opts[:expires].to_i > 0
|
97
|
+
unless opts[:cache].is_a?(Moneta::Transformer)
|
98
|
+
raise "Parse::Client option :cache needs to be a type of Moneta::Transformer store."
|
99
|
+
end
|
100
|
+
self.cache = opts[:cache]
|
101
|
+
conn.use Parse::Middleware::Caching, self.cache, {expires: opts[:expires].to_i }
|
102
|
+
end
|
103
|
+
|
104
|
+
yield(conn) if block_given?
|
105
|
+
|
106
|
+
conn.adapter opts[:adapter]
|
107
|
+
|
108
|
+
end
|
109
|
+
@@sessions[:default] ||= self
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
def clear_cache!
|
114
|
+
self.cache.clear if self.cache.present?
|
115
|
+
end
|
116
|
+
|
117
|
+
# This is the base method to make raw requests. The first parameter is a symbol
|
118
|
+
# of the type of request - either :get, :put, :post, :delete. The second parameter
|
119
|
+
# is the path api to use. For example, to make a request for objects, you would pass the "/1/classes/<ClassName>".
|
120
|
+
# After the first two parameters, the rest are named parameters. If the request is of type :get, you can pass
|
121
|
+
# any query string parameters in hash form with query:. If it is any other request, the body of the request should be sent
|
122
|
+
# with the body: parameter. Note that the middleware will handle turning the hash sent into the body: parameter into JSON.
|
123
|
+
# If you need to override or add additional headers to a specific request (ex. when uploading a Parse File), you can do so
|
124
|
+
# with the header: paramter (also a hash).
|
125
|
+
# This method also takes in a Parse::Request object instead of the arguments listed above.
|
126
|
+
def request(method, uri = nil, body: nil, query: nil, headers: nil)
|
127
|
+
headers ||= {}
|
128
|
+
# if the first argument is a Parse::Request object, then construct it
|
129
|
+
if method.is_a?(Request)
|
130
|
+
request = method
|
131
|
+
method = request.method
|
132
|
+
uri ||= request.path
|
133
|
+
query ||= request.query
|
134
|
+
body ||= request.body
|
135
|
+
headers.merge! request.headers
|
136
|
+
end
|
137
|
+
|
138
|
+
# http method
|
139
|
+
method = method.downcase.to_sym
|
140
|
+
# set the User-Agent
|
141
|
+
headers["User-Agent".freeze] = "Parse-Ruby-Mapper v#{Parse::Stack::VERSION}".freeze
|
142
|
+
#if it is a :get request, then use query params, otherwise body.
|
143
|
+
params = (method == :get ? query : body) || {}
|
144
|
+
# if the path does not start with the '/1/' prefix, then add it to be nice.
|
145
|
+
uri.replace(@version_prefix + uri) unless uri.start_with?(@version_prefix)
|
146
|
+
# actually send the request and return the body
|
147
|
+
@session.send(method, uri, params, headers).body
|
148
|
+
rescue Faraday::Error::ClientError => e
|
149
|
+
raise Parse::ConnectionError, e.message
|
150
|
+
end
|
151
|
+
|
152
|
+
# shorthand for request(:get, uri, query: {})
|
153
|
+
def get(uri, query = nil, headers = {})
|
154
|
+
request :get, uri, query: query, headers: headers
|
155
|
+
end
|
156
|
+
|
157
|
+
# shorthand for request(:post, uri, body: {})
|
158
|
+
def post(uri, body = nil, headers = {} )
|
159
|
+
request :post, uri, body: body, headers: headers
|
160
|
+
end
|
161
|
+
|
162
|
+
# shorthand for request(:put, uri, body: {})
|
163
|
+
def put(uri, body = nil, headers = {})
|
164
|
+
request :put, uri, body: body, headers: headers
|
165
|
+
end
|
166
|
+
|
167
|
+
# shorthand for request(:delete, uri, body: {})
|
168
|
+
def delete(uri, body = nil, headers = {})
|
169
|
+
request :delete, uri, body: body, headers: headers
|
170
|
+
end
|
171
|
+
|
172
|
+
def send_request(req) #Parse::Request object
|
173
|
+
raise "Object not of Parse::Request type." unless req.is_a?(Parse::Request)
|
174
|
+
request req.method, req.path, req.body, req.headers
|
175
|
+
end
|
176
|
+
|
177
|
+
# The connectable module adds methods to objects so that they can get a default
|
178
|
+
# Parse::Client object if needed. This is mainly used for Parse::Query and Parse::Object classes.
|
179
|
+
# This is included in the Parse::Model class.
|
180
|
+
# Any subclass can override their `client` methods to provide a different session to use
|
181
|
+
module Connectable
|
182
|
+
|
183
|
+
def self.included(baseClass)
|
184
|
+
baseClass.extend ClassMethods
|
185
|
+
end
|
186
|
+
|
187
|
+
module ClassMethods
|
188
|
+
attr_accessor :client
|
189
|
+
def client
|
190
|
+
@client ||= Parse::Client.session #defaults to :default tag
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def client
|
195
|
+
self.class.client
|
196
|
+
end
|
197
|
+
|
198
|
+
end #Connectable
|
199
|
+
end
|
200
|
+
|
201
|
+
# Helper method that users should call to setup the client stack.
|
202
|
+
# A block can be passed in order to do additional client configuration.
|
203
|
+
def self.setup(opts = {})
|
204
|
+
if block_given?
|
205
|
+
Parse::Client.new(opts, &Proc.new)
|
206
|
+
else
|
207
|
+
Parse::Client.new(opts)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Helper method to call cloud functions and get results
|
212
|
+
def self.trigger_job(name, body, session: :default, raw: false)
|
213
|
+
response = Parse::Client.session(session).trigger_job(name, body)
|
214
|
+
return response if raw
|
215
|
+
response.error? ? nil : response.result["result"]
|
216
|
+
end
|
217
|
+
|
218
|
+
# Helper method to call cloud functions and get results
|
219
|
+
def self.call_function(name, body, session: :default, raw: false)
|
220
|
+
response = Parse::Client.session(session).call_function(name, body)
|
221
|
+
return response if raw
|
222
|
+
response.error? ? nil : response.result["result"]
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require_relative 'protocol'
|
4
|
+
# All Parse requests require authentication with specific header values.
|
5
|
+
# This middleware takes all outgoing requests and adds the proper header values
|
6
|
+
# base on the client configuration.
|
7
|
+
module Parse
|
8
|
+
|
9
|
+
module Middleware
|
10
|
+
|
11
|
+
class Authentication < Faraday::Middleware
|
12
|
+
include Parse::Protocol
|
13
|
+
attr_accessor :application_id
|
14
|
+
attr_accessor :api_key
|
15
|
+
attr_accessor :master_key
|
16
|
+
# The options hash should contain the proper keys to be added to the request.
|
17
|
+
def initialize(app, options = {})
|
18
|
+
super(app)
|
19
|
+
@application_id = options[:application_id]
|
20
|
+
@api_key = options[:api_key]
|
21
|
+
@master_key = options[:master_key]
|
22
|
+
@content_type = options[:content_type] || CONTENT_TYPE_FORMAT
|
23
|
+
end
|
24
|
+
|
25
|
+
# we dup the call for thread-safety
|
26
|
+
def call(env)
|
27
|
+
dup.call!(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call!(env)
|
31
|
+
# We add the main Parse protocol headers
|
32
|
+
headers = {}
|
33
|
+
raise "No Parse Application Id specified for authentication." unless @application_id.present?
|
34
|
+
headers[APP_ID] = @application_id
|
35
|
+
|
36
|
+
# Set a user agent
|
37
|
+
headers["User-Agent".freeze] = "Parse-Stack v0.0.1".freeze
|
38
|
+
headers[API_KEY] = @api_key unless @api_key.blank?
|
39
|
+
headers[MASTER_KEY] = @master_key unless @master_key.blank?
|
40
|
+
# merge the headers with the current provided headers
|
41
|
+
env[:request_headers].merge! headers
|
42
|
+
# set the content type of the request if it was not provided already.
|
43
|
+
env[:request_headers][CONTENT_TYPE] ||= @content_type
|
44
|
+
# only modify header
|
45
|
+
|
46
|
+
@app.call(env).on_complete do |response_env|
|
47
|
+
# check for return code raise an error when authentication was a failure
|
48
|
+
# if response_env[:status] == 401
|
49
|
+
# warn "Unauthorized Parse API Credentials for Application Id: #{@application_id}"
|
50
|
+
# end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end # Authenticator
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require_relative 'response'
|
4
|
+
require_relative 'protocol'
|
5
|
+
# This middleware takes an incoming response (after an outgoing request)
|
6
|
+
# and creates a Parse::Response object.
|
7
|
+
module Parse
|
8
|
+
module Middleware
|
9
|
+
class BodyBuilder < Faraday::Middleware
|
10
|
+
class << self
|
11
|
+
attr_accessor :logging
|
12
|
+
end
|
13
|
+
|
14
|
+
include Parse::Protocol
|
15
|
+
HTTP_OVERRIDE = 'X-Http-Method-Override'.freeze
|
16
|
+
|
17
|
+
# thread-safety
|
18
|
+
def call(env)
|
19
|
+
dup.call!(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call!(env)
|
23
|
+
# the maximum url size is ~2KB, so if we request a Parse API url greater than this
|
24
|
+
# (which is most likely a very complicated query), we need to override the request method
|
25
|
+
# to be POST instead of GET and send the query parameters in the body of the POST request.
|
26
|
+
# The standard maximum POST request (which is a server setting), is usually set to 20MBs
|
27
|
+
if env[:method] == :get && env[:url].to_s.length > 2_000
|
28
|
+
env[:request_headers][HTTP_OVERRIDE] = 'GET'
|
29
|
+
env[:request_headers][CONTENT_TYPE] = 'application/x-www-form-urlencoded'.freeze
|
30
|
+
env[:body] = env[:url].query
|
31
|
+
env[:url].query = nil
|
32
|
+
#override
|
33
|
+
env[:method] = :post
|
34
|
+
# else if not a get, always make sure the request is JSON encoded if the content type matches
|
35
|
+
elsif env[:request_headers][CONTENT_TYPE] == CONTENT_TYPE_FORMAT &&
|
36
|
+
(env[:body].is_a?(Hash) || env[:body].is_a?(Array))
|
37
|
+
env[:body] = env[:body].to_json
|
38
|
+
end
|
39
|
+
|
40
|
+
if self.class.logging
|
41
|
+
puts "[Request #{env.method.upcase}] #{env[:url]}"
|
42
|
+
puts "[Request Body] #{env[:body]}"
|
43
|
+
end
|
44
|
+
@app.call(env).on_complete do |response_env|
|
45
|
+
# on a response, create a new Parse::Response and replace the :body
|
46
|
+
# of the env
|
47
|
+
# TODO: CHECK FOR HTTP STATUS CODES
|
48
|
+
if self.class.logging
|
49
|
+
puts "[[Response ]] --------------------------------------"
|
50
|
+
puts response_env.body
|
51
|
+
puts "[[Response]] --------------------------------------\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
r = Parse::Response.new(response_env.body)
|
56
|
+
rescue Exception => e
|
57
|
+
r = Parse::Response.new
|
58
|
+
r.code = response_env.status
|
59
|
+
r.error = "Invalid Parse Response: #{e}"
|
60
|
+
end
|
61
|
+
|
62
|
+
r.code ||= response_env[:status] if r.error.present?
|
63
|
+
response_env[:body] = r
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end #Middleware
|
69
|
+
end
|