stackmob 0.0.0
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.
- data/.document +5 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +47 -0
- data/LICENSE.txt +175 -0
- data/README.md +210 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/lib/stackmob.rb +67 -0
- data/lib/stackmob/client.rb +90 -0
- data/lib/stackmob/data_store.rb +74 -0
- data/lib/stackmob/deployer.rb +37 -0
- data/lib/stackmob/helpers.rb +63 -0
- data/lib/stackmob/push.rb +63 -0
- data/lib/stackmob/rack/simple_oauth_provider.rb +59 -0
- data/lib/stackmob/rails.rb +15 -0
- data/lib/stackmob/railtie.rb +38 -0
- data/lib/stackmob/sinatra.rb +5 -0
- data/lib/stackmob/tasks.rb +40 -0
- data/stackmob.gemspec +99 -0
- data/test/helper.rb +22 -0
- data/test/integration/test_client.rb +45 -0
- data/test/integration/test_data_store.rb +31 -0
- data/test/integration/test_deployer.rb +23 -0
- data/test/integration/test_push.rb +24 -0
- data/test/integration_helper.rb +46 -0
- data/test/unit/stackmob/rack/test_simple_oauth_provider.rb +52 -0
- data/test/unit/stackmob/test_client.rb +105 -0
- data/test/unit/stackmob/test_data_store.rb +83 -0
- data/test/unit/stackmob/test_deployer.rb +21 -0
- data/test/unit/stackmob/test_push.rb +133 -0
- metadata +258 -0
data/Rakefile
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "stackmob"
|
18
|
+
gem.homepage = "http://github.com/stackmob/stackmob-ruby"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "Support Gem for StackMob Heroku Add-On"
|
21
|
+
gem.description = "Support Gem for StackMob Heroku Add-On"
|
22
|
+
gem.email = "jordan@stackmob.com"
|
23
|
+
gem.authors = ["StackMob"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
namespace :test do
|
31
|
+
|
32
|
+
|
33
|
+
Rake::TestTask.new(:unit) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/unit/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::TestTask.new(:integration) do |test|
|
40
|
+
test.libs << 'lib' << 'test'
|
41
|
+
test.pattern = 'test/integration/**/test_*.rb'
|
42
|
+
test.verbose = true
|
43
|
+
end
|
44
|
+
|
45
|
+
Rcov::RcovTask.new do |test|
|
46
|
+
test.libs << 'test'
|
47
|
+
test.pattern = 'test/**/test_*.rb'
|
48
|
+
test.verbose = true
|
49
|
+
test.rcov_opts << '--exclude "gems/*"'
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Run Unit & Integration Tests"
|
53
|
+
task :all => [:unit, :integration]
|
54
|
+
end
|
55
|
+
|
56
|
+
task :default => "test:all"
|
57
|
+
|
58
|
+
require 'yard'
|
59
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/lib/stackmob.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'oauth'
|
16
|
+
require 'yajl'
|
17
|
+
require 'yaml'
|
18
|
+
|
19
|
+
require 'stackmob/client'
|
20
|
+
require 'stackmob/data_store'
|
21
|
+
require 'stackmob/push'
|
22
|
+
require 'stackmob/rack/simple_oauth_provider'
|
23
|
+
require 'stackmob/helpers'
|
24
|
+
|
25
|
+
module StackMob
|
26
|
+
|
27
|
+
SANDBOX = 0
|
28
|
+
PRODUCTION = 1
|
29
|
+
|
30
|
+
def self.config
|
31
|
+
@config ||= YAML.load_file("config/stackmob.yml")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.secret
|
35
|
+
StackMob.config[sm_env_str]['secret']
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.key
|
39
|
+
StackMob.config[sm_env_str]['key']
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.app_name
|
43
|
+
StackMob.config['sm_app_name']
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.client_name
|
47
|
+
StackMob.config['sm_client_name']
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.dev_url
|
51
|
+
"http://#{StackMob.client_name}.mob2.stackmob.com"
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.env
|
55
|
+
(is_production?) ? PRODUCTION : SANDBOX
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.sm_env_str
|
59
|
+
env == PRODUCTION ? "production" : "development"
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.is_production?
|
63
|
+
ENV["RACK_ENV"] == "production"
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'oauth'
|
16
|
+
require 'yajl'
|
17
|
+
|
18
|
+
module StackMob
|
19
|
+
class Client
|
20
|
+
|
21
|
+
VALID_METHODS = [:get, :post, :put, :delete]
|
22
|
+
|
23
|
+
class InvalidRequestMethod < ArgumentError; end # invalid HTTP Method/Verb is passed to the client
|
24
|
+
class RequestError < RuntimeError; end # response with code other than 200 received
|
25
|
+
class BadResponseBody < RuntimeError; end # Client receives response with invalid JSON
|
26
|
+
|
27
|
+
attr_accessor :app_name, :app_vsn
|
28
|
+
|
29
|
+
def initialize(base_url, app_name, app_vsn, oauth_key, oauth_secret)
|
30
|
+
self.app_name = app_name
|
31
|
+
self.app_vsn = app_vsn
|
32
|
+
|
33
|
+
create_oauth_client(oauth_key, oauth_secret, base_url)
|
34
|
+
end
|
35
|
+
|
36
|
+
def request(method, service, path, params = {})
|
37
|
+
request_path, request_body = generate_path_and_body(method, service, path, params)
|
38
|
+
|
39
|
+
response = @oauth_client.send(method, request_path, request_body)
|
40
|
+
|
41
|
+
rcode = response.code.to_i
|
42
|
+
if rcode >= 200 && rcode <= 299
|
43
|
+
parse_response(response) if method != :delete
|
44
|
+
else
|
45
|
+
raise RequestError.new("\nReq Method: #{method}\nReq. Path: #{request_path}\nReq. Body: #{request_body}\nResp. Code: #{rcode}, Resp Body: #{response.respond_to?(:body) ? response.body : 'unknown'}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_oauth_client(key, secret, url)
|
50
|
+
@oauth_client = OAuth::AccessToken.new(OAuth::Consumer.new(key, secret, :site => url))
|
51
|
+
end
|
52
|
+
private :create_oauth_client
|
53
|
+
|
54
|
+
def generate_path_and_body(method, service, path, params)
|
55
|
+
intermediate_path = full_path(service, path)
|
56
|
+
case method
|
57
|
+
when :get, :delete
|
58
|
+
[intermediate_path + "?" + params_to_qs(params), ""]
|
59
|
+
when :post, :put
|
60
|
+
[intermediate_path, Yajl::Encoder.encode(params)]
|
61
|
+
else
|
62
|
+
raise InvalidRequestMethod
|
63
|
+
end
|
64
|
+
end
|
65
|
+
private :generate_path_and_body
|
66
|
+
|
67
|
+
def params_to_qs(params)
|
68
|
+
params.to_a.map { |pair| pair.join("=") }.join("&")
|
69
|
+
end
|
70
|
+
private :params_to_qs
|
71
|
+
|
72
|
+
def parse_response(r)
|
73
|
+
Yajl::Parser.parse(r.body)
|
74
|
+
rescue Yajl::ParseError
|
75
|
+
raise BadResponseBody.new("#{r.body} is not valid JSON")
|
76
|
+
end
|
77
|
+
private :parse_response
|
78
|
+
|
79
|
+
def full_path(service, requested_path)
|
80
|
+
"/#{service}/#{self.app_vsn}/#{self.app_name}/#{strip_prepending_slash(requested_path)}"
|
81
|
+
end
|
82
|
+
private :full_path
|
83
|
+
|
84
|
+
def strip_prepending_slash(path)
|
85
|
+
path.gsub(/^\//, "")
|
86
|
+
end
|
87
|
+
private :strip_prepending_slash
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
class DataStore
|
17
|
+
|
18
|
+
attr_accessor :client
|
19
|
+
|
20
|
+
API_SVC = :api
|
21
|
+
|
22
|
+
def initialize(cl)
|
23
|
+
self.client = cl
|
24
|
+
end
|
25
|
+
|
26
|
+
def api_schema
|
27
|
+
self.client.request(:get, API_SVC, "/listapi")
|
28
|
+
end
|
29
|
+
|
30
|
+
def create(obj_name, params)
|
31
|
+
create!(obj_name, params)
|
32
|
+
rescue Client::RequestError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def create!(obj_name, params)
|
37
|
+
self.client.request(:post, API_SVC, obj_name_to_path(obj_name), params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(obj_name, params)
|
41
|
+
delete!(obj_name, params); true
|
42
|
+
rescue Client::RequestError
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete!(obj_name, params)
|
47
|
+
self.client.request(:delete, API_SVC, obj_name_to_path(obj_name), params)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get(obj_name, params = {})
|
51
|
+
self.client.request(:get, API_SVC, obj_name_to_path(obj_name), params)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_one(obj_name, params)
|
55
|
+
get(obj_name, params).first
|
56
|
+
end
|
57
|
+
|
58
|
+
def update(obj_name, obj_id, params)
|
59
|
+
update!(obj_name, obj_id, params); true
|
60
|
+
rescue Client::RequestError
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
64
|
+
def update!(obj_name, obj_id, params)
|
65
|
+
self.client.request(:put, API_SVC, obj_name_to_path(obj_name) + "/#{obj_id}", params)
|
66
|
+
end
|
67
|
+
|
68
|
+
def obj_name_to_path(obj_name)
|
69
|
+
"/#{obj_name}"
|
70
|
+
end
|
71
|
+
private :obj_name_to_path
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'stackmob'
|
16
|
+
|
17
|
+
module StackMob
|
18
|
+
class Deployer
|
19
|
+
|
20
|
+
attr_accessor :client
|
21
|
+
|
22
|
+
APP_PATH = "heroku/app"
|
23
|
+
|
24
|
+
def initialize(cl)
|
25
|
+
self.client = cl
|
26
|
+
end
|
27
|
+
|
28
|
+
def register(hostname)
|
29
|
+
self.client.request(:post, :api, APP_PATH, :hostname => hostname)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch
|
33
|
+
self.client.request(:get, :api, APP_PATH)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
module Helpers
|
17
|
+
def sm_datastore
|
18
|
+
@sm_datastore ||= begin
|
19
|
+
client = StackMob::Client.new(sm_api_host, sm_app_name, StackMob.env, StackMob.key, StackMob.secret)
|
20
|
+
StackMob::DataStore.new(client)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def sm_push
|
25
|
+
@sm_push ||= begin
|
26
|
+
client = StackMob::Client.new(sm_push_host, sm_app_name, StackMob.env, StackMob.key, StackMob.secret)
|
27
|
+
StackMob::Push.new(client)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sm_api_host
|
32
|
+
sm_normalize_host(sm_hostname_from_header_or_config('HTTP_X_STACKMOB_API'))
|
33
|
+
end
|
34
|
+
private :sm_api_host
|
35
|
+
|
36
|
+
def sm_push_host
|
37
|
+
sm_normalize_host(sm_hostname_from_header_or_config('HTTP_X_STACKMOB_PUSH'))
|
38
|
+
end
|
39
|
+
private :sm_push_host
|
40
|
+
|
41
|
+
def sm_hostname_from_header_or_config(header_str)
|
42
|
+
hostname = request.env[header_str]
|
43
|
+
(hostname.nil? || hostname == "") ? StackMob.dev_url : hostname
|
44
|
+
end
|
45
|
+
private :sm_hostname_from_header_or_config
|
46
|
+
|
47
|
+
def sm_app_name
|
48
|
+
StackMob.app_name
|
49
|
+
end
|
50
|
+
private :sm_app_name
|
51
|
+
|
52
|
+
def sm_app_version
|
53
|
+
(Rails.env.production?) ? StackMob::PRODUCTION : StackMob::SANDBOX
|
54
|
+
end
|
55
|
+
private :sm_app_version
|
56
|
+
|
57
|
+
def sm_normalize_host(host_str)
|
58
|
+
"http://#{StackMob.client_name}.#{host_str}"
|
59
|
+
end
|
60
|
+
private :sm_normalize_host
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2011 StackMob
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module StackMob
|
16
|
+
class Push
|
17
|
+
|
18
|
+
attr_accessor :client
|
19
|
+
|
20
|
+
PUSH_SVC = :push
|
21
|
+
DEVICE_TOKEN_PATH = "/device_tokens"
|
22
|
+
BROADCAST_PATH = "/push_broadcast"
|
23
|
+
PUSH_PATH = "/push"
|
24
|
+
|
25
|
+
DEFAULT_BADGE = 0
|
26
|
+
DEFAULT_SOUND = ""
|
27
|
+
|
28
|
+
def initialize(cl)
|
29
|
+
self.client = cl
|
30
|
+
end
|
31
|
+
|
32
|
+
def register(user_id, device_token)
|
33
|
+
self.client.request(:post, PUSH_SVC, DEVICE_TOKEN_PATH, :userId => user_id, :token => device_token) # using non-convential symbols to conform to StackMob Push API
|
34
|
+
end
|
35
|
+
|
36
|
+
def broadcast(opts)
|
37
|
+
aps_data = generate_aps_data(opts)
|
38
|
+
payload = {:recipients => [], :aps => aps_data, :areRecipientsDeviceTokens => true, :exclude_tokens => []}
|
39
|
+
|
40
|
+
self.client.request(:post, PUSH_SVC, BROADCAST_PATH, payload)
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_message(to, opts)
|
44
|
+
aps_data = generate_aps_data(opts)
|
45
|
+
recipients_are_device_tokens = (opts[:recipients_are_users]) ? false : true
|
46
|
+
payload = {:recipients => Array(to), :aps => aps_data, :areRecipientsDeviceTokens => recipients_are_device_tokens, :exclude_tokens => []}
|
47
|
+
|
48
|
+
self.client.request(:post, PUSH_SVC, PUSH_PATH, payload)
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate_aps_data(opts)
|
52
|
+
alert = opts[:alert] || (raise ArgumentError.new("Push requires alert message"))
|
53
|
+
badge = opts[:badge] || DEFAULT_BADGE
|
54
|
+
sound = opts[:sound] || DEFAULT_SOUND
|
55
|
+
|
56
|
+
{:badge => badge, :sound => sound, :alert => alert}
|
57
|
+
end
|
58
|
+
private :generate_aps_data
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|