mxit-rails 0.2.5 → 0.2.6
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/app/helpers/mxit_rails_helper.rb +4 -4
- data/app/views/layouts/mxit.html.erb +7 -1
- data/lib/mxit-rails.rb +8 -0
- data/lib/mxit_rails/descriptor.rb +4 -1
- data/lib/mxit_rails/mxit_api/api_client.rb +260 -0
- data/lib/mxit_rails/mxit_api/auth_token.rb +35 -0
- data/lib/mxit_rails/mxit_api/mxit_api_exception.rb +8 -0
- data/lib/mxit_rails/mxit_api/request_exception.rb +9 -0
- data/lib/mxit_rails/page.rb +31 -5
- data/lib/mxit_rails/validations.rb +44 -0
- data/lib/mxit_rails/version.rb +1 -1
- data/test/dummy/app/controllers/form_controller.rb +12 -0
- data/test/dummy/app/controllers/welcome_controller.rb +1 -1
- data/test/dummy/app/views/form/index/age.html.erb +1 -5
- data/test/dummy/app/views/form/index/done.html.erb +6 -6
- data/test/dummy/app/views/form/index/gender.html.erb +1 -5
- data/test/dummy/app/views/form/index/name.html.erb +1 -5
- data/test/dummy/app/views/form/index/start.html.erb +1 -5
- data/test/dummy/app/views/form/index/surname.html.erb +1 -4
- data/test/dummy/app/views/index/index.html.erb +8 -7
- data/test/dummy/app/views/index/success.html.erb +1 -1
- data/test/dummy/app/views/welcome/easter_egg.html.erb +1 -1
- data/test/dummy/app/views/welcome/index.html.erb +3 -5
- data/test/dummy/log/development.log +591 -0
- metadata +6 -2
@@ -8,7 +8,7 @@ module MxitRailsHelper
|
|
8
8
|
str = ''
|
9
9
|
if @_mxit.has_table
|
10
10
|
# Close the previous row if there is one
|
11
|
-
str += "
|
11
|
+
str += "<br /></td></tr>"
|
12
12
|
else
|
13
13
|
# Start a new table
|
14
14
|
str += '<table title="mxit:table:full" style="width:100%" name="main_table" cellspacing="0" cellpadding="0">'
|
@@ -17,7 +17,7 @@ module MxitRailsHelper
|
|
17
17
|
@_mxit.has_table = true
|
18
18
|
|
19
19
|
# Start the new row
|
20
|
-
style = mxit_style *styles
|
20
|
+
style = styles.empty? ? mxit_style(:body) : mxit_style(*styles)
|
21
21
|
str += "<tr><td style=\"#{ style }\">"
|
22
22
|
str.html_safe
|
23
23
|
end
|
@@ -43,11 +43,11 @@ module MxitRailsHelper
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def mxit_nav_link target, label
|
46
|
-
"
|
46
|
+
"#{ mxit_link target, label }<br /><br />".html_safe
|
47
47
|
end
|
48
48
|
|
49
49
|
def mxit_proceed content
|
50
|
-
"<
|
50
|
+
"<br /><b style=\"#{ mxit_style :link }\"> » #{content}</b><br />".html_safe
|
51
51
|
end
|
52
52
|
|
53
53
|
end
|
@@ -27,10 +27,16 @@
|
|
27
27
|
|
28
28
|
<% if @_mxit.has_table %>
|
29
29
|
<!-- Close the table if there is one -->
|
30
|
-
|
30
|
+
<br /></td></tr>
|
31
31
|
</table>
|
32
32
|
<% end %>
|
33
33
|
|
34
|
+
<% if mxit_validation_message %>
|
35
|
+
<b><%= mxit_validation_message %></b><br />
|
36
|
+
<% elsif flash[:notice] %>
|
37
|
+
<b><%= flash[:notice] %></b><br />
|
38
|
+
<% end %>
|
39
|
+
|
34
40
|
<% if @_mxit.input %>
|
35
41
|
<%= mxit_proceed @_mxit.input_label %>
|
36
42
|
<form method="POST" action="<%= request.path %>">
|
data/lib/mxit-rails.rb
CHANGED
@@ -10,3 +10,11 @@ require "mxit_rails/page"
|
|
10
10
|
|
11
11
|
module MxitRails
|
12
12
|
end
|
13
|
+
|
14
|
+
require "mxit_rails/mxit_api/api_client"
|
15
|
+
require "mxit_rails/mxit_api/auth_token"
|
16
|
+
require "mxit_rails/mxit_api/mxit_api_exception"
|
17
|
+
require "mxit_rails/mxit_api/request_exception"
|
18
|
+
|
19
|
+
module MxitRails::MXitApi
|
20
|
+
end
|
@@ -18,7 +18,7 @@ module MxitRails
|
|
18
18
|
attr_accessor :action
|
19
19
|
attr_accessor :type
|
20
20
|
|
21
|
-
|
21
|
+
attr_accessor :proceed
|
22
22
|
|
23
23
|
attr_accessor :input
|
24
24
|
attr_accessor :input_label
|
@@ -29,6 +29,9 @@ module MxitRails
|
|
29
29
|
|
30
30
|
attr_accessor :has_table
|
31
31
|
|
32
|
+
attr_accessor :validations_failed
|
33
|
+
attr_accessor :validated
|
34
|
+
|
32
35
|
def initialize name, action, parent=nil
|
33
36
|
@parent_descriptor = parent
|
34
37
|
@name = name.to_sym
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module MxitRails::MxitApi
|
5
|
+
class Client
|
6
|
+
MXIT_AUTH_BASE_URI = 'https://auth.mxit.com'
|
7
|
+
MXIT_AUTH_TOKEN_URI = MXIT_AUTH_BASE_URI + '/token'
|
8
|
+
MXIT_AUTH_CODE_URI = MXIT_AUTH_BASE_URI + '/authorize'
|
9
|
+
|
10
|
+
MXIT_API_URI = 'http://api.mxit.com'
|
11
|
+
|
12
|
+
attr_accessor :app_name, :client_id, :client_secret
|
13
|
+
attr_accessor :auth_token
|
14
|
+
|
15
|
+
def initialize(app_name, client_id, client_secret)
|
16
|
+
@app_name = app_name
|
17
|
+
@client_id = client_id
|
18
|
+
@client_secret = client_secret
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_app_auth(scopes)
|
22
|
+
if scopes.empty?
|
23
|
+
raise MxitRails::MxitApi::Exception.new("No scopes were provided.")
|
24
|
+
end
|
25
|
+
|
26
|
+
response = http_client(MXIT_AUTH_TOKEN_URI) do |http, path|
|
27
|
+
|
28
|
+
request = new_post_request(path, {
|
29
|
+
"grant_type" => "client_credentials",
|
30
|
+
"scope" => scopes.join(" ")
|
31
|
+
})
|
32
|
+
|
33
|
+
http.request(request)
|
34
|
+
end
|
35
|
+
|
36
|
+
case response
|
37
|
+
when Net::HTTPSuccess then
|
38
|
+
@auth_token = AuthToken.new(JSON.parse(response.body))
|
39
|
+
|
40
|
+
else
|
41
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# The user's response to the authorisation code request will be redirected to `redirect_uri`. If
|
46
|
+
# the request was successful there will be a `code` request parameter; otherwise `error`.
|
47
|
+
#
|
48
|
+
# redirect_uri - absolute URI to which the user will be redirected after authorisation
|
49
|
+
# state - passed back to `redirect_uri` as a request parameter
|
50
|
+
# scopes - list of scopes to which access is required
|
51
|
+
def user_code_request_uri(redirect_uri, state, scopes)
|
52
|
+
if scopes.empty?
|
53
|
+
raise MxitRails::MxitApi::Exception.new("No scopes were provided.")
|
54
|
+
end
|
55
|
+
|
56
|
+
# build parameters
|
57
|
+
parameters = {
|
58
|
+
:response_type => "code",
|
59
|
+
:client_id => @client_id,
|
60
|
+
:redirect_uri => redirect_uri,
|
61
|
+
:state => state,
|
62
|
+
:scope => scopes.join(' ')
|
63
|
+
}
|
64
|
+
|
65
|
+
path = MXIT_AUTH_CODE_URI + "?#{URI.encode_www_form(parameters)}"
|
66
|
+
end
|
67
|
+
|
68
|
+
# NOTE: `user_code_request_uri` must be used before `request_user_auth` because it provides the
|
69
|
+
# `code` argument. `redirect_uri` must match the one used in the `user_code_request_uri` call
|
70
|
+
def request_user_auth(code, redirect_uri)
|
71
|
+
response = http_client(MXIT_AUTH_TOKEN_URI) do |http, path|
|
72
|
+
|
73
|
+
request = new_post_request(path, {
|
74
|
+
"grant_type" => "authorization_code",
|
75
|
+
"code" => code,
|
76
|
+
"redirect_uri" => redirect_uri
|
77
|
+
})
|
78
|
+
|
79
|
+
http.request(request)
|
80
|
+
end
|
81
|
+
|
82
|
+
case response
|
83
|
+
when Net::HTTPSuccess then
|
84
|
+
@auth_token = AuthToken.new(JSON.parse(response.body))
|
85
|
+
|
86
|
+
else
|
87
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def revoke_token(auth_token)
|
92
|
+
response = http_client(MXIT_AUTH_BASE_URI + "/revoke") do |http, path|
|
93
|
+
|
94
|
+
request = new_post_request(path, {
|
95
|
+
"token" => auth_token.access_token
|
96
|
+
})
|
97
|
+
|
98
|
+
http.request(request)
|
99
|
+
end
|
100
|
+
|
101
|
+
if response.code != '200'
|
102
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def refresh_token(auth_token)
|
107
|
+
if auth_token.refresh_token.nil?
|
108
|
+
raise MxitRails::MxitApi::Exception.new("The provided auth token doesn't have a refresh " +
|
109
|
+
"token.")
|
110
|
+
end
|
111
|
+
|
112
|
+
response = http_client(MXIT_AUTH_TOKEN_URI) do |http, path|
|
113
|
+
|
114
|
+
request = new_post_request(path, {
|
115
|
+
"grant_type" => "refresh_token",
|
116
|
+
"refresh_token" => auth_token.refresh_token
|
117
|
+
})
|
118
|
+
|
119
|
+
http.request(request)
|
120
|
+
end
|
121
|
+
|
122
|
+
case response
|
123
|
+
when Net::HTTPSuccess then
|
124
|
+
auth_token = AuthToken.new(JSON.parse(response.body))
|
125
|
+
|
126
|
+
else
|
127
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
### API methods requiring authorisation.
|
132
|
+
|
133
|
+
# When sending as the app the `message/send` scope is required otherwise `message/user`
|
134
|
+
def send_message(from, to, body, contains_markup, auth_token=nil)
|
135
|
+
auth_token = auth_token || @auth_token
|
136
|
+
|
137
|
+
if from == @app_name
|
138
|
+
check_auth_token(auth_token, ["message/send"])
|
139
|
+
else
|
140
|
+
check_auth_token(auth_token, ["message/user"])
|
141
|
+
end
|
142
|
+
|
143
|
+
response = http_client(MXIT_API_URI + "/message/send/") do |http, path|
|
144
|
+
|
145
|
+
request = Net::HTTP::Post.new(path)
|
146
|
+
set_api_headers(request, auth_token.access_token)
|
147
|
+
|
148
|
+
request.body = {
|
149
|
+
"Body" => body,
|
150
|
+
"ContainsMarkup" => contains_markup,
|
151
|
+
"From" => from,
|
152
|
+
"To" => to
|
153
|
+
# "Spool" => default(true)
|
154
|
+
# "SpoolTimeOut" => default(60*60*24*7)
|
155
|
+
}.to_json
|
156
|
+
|
157
|
+
http.request(request)
|
158
|
+
end
|
159
|
+
|
160
|
+
if response.code != '200'
|
161
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# The following filter parameters are available (only one can be specified at a time):
|
166
|
+
# @All - Return all roster entries
|
167
|
+
# @Friends - Return only friends
|
168
|
+
# @Apps - Return only applications
|
169
|
+
# @Invites - Return all entries that is in an invite state
|
170
|
+
# @Connections - Return all entries that has been accepted
|
171
|
+
# @Rejected - Return all entries that has been rejected
|
172
|
+
# @Pending - Return all entries that is waiting to be accepted by the other party
|
173
|
+
# @Deleted - Return all entries that was deleted
|
174
|
+
# @Blocked - Return all entries that was blocked
|
175
|
+
def get_contact_list(filter, options={ :skip => nil, :count => nil, :auth_token => nil })
|
176
|
+
auth_token = options[:auth_token] || @auth_token
|
177
|
+
check_auth_token(auth_token, ["graph/read"])
|
178
|
+
|
179
|
+
response = http_client(MXIT_API_URI + "/user/socialgraph/contactlist") do |http, path|
|
180
|
+
|
181
|
+
parameters = { :filter => filter }
|
182
|
+
# skip and count are optional
|
183
|
+
parameters[:skip] = skip if options[:skip]
|
184
|
+
parameters[:count] = count if options[:count]
|
185
|
+
|
186
|
+
request = Net::HTTP::Get.new(path + "?#{URI.encode_www_form(parameters)}")
|
187
|
+
set_api_headers(request, auth_token.access_token)
|
188
|
+
|
189
|
+
http.request(request)
|
190
|
+
end
|
191
|
+
|
192
|
+
case response
|
193
|
+
when Net::HTTPSuccess then
|
194
|
+
data = JSON.parse(response.body)
|
195
|
+
|
196
|
+
else
|
197
|
+
raise MxitRails::MxitApi::RequestException.new(response.message, response.code)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def batch_notify_users(mxit_ids, message, contains_markup)
|
202
|
+
Rails.logger.info('Requesting MXit API auth...')
|
203
|
+
request_app_auth(["message/send"])
|
204
|
+
Rails.logger.info('Finished MXit API auth.')
|
205
|
+
|
206
|
+
batch_size = 50
|
207
|
+
Rails.logger.info('Starting to notify users in batches of ' + batch_size.to_s + '...')
|
208
|
+
i = 0
|
209
|
+
while i < mxit_ids.count
|
210
|
+
current_batch = mxit_ids[i, batch_size]
|
211
|
+
i += batch_size
|
212
|
+
|
213
|
+
to = current_batch.join(',')
|
214
|
+
send_message(@app_name, to, message, contains_markup)
|
215
|
+
|
216
|
+
Rails.logger.info("Total users notified: " + current_batch.count.to_s)
|
217
|
+
end
|
218
|
+
Rails.logger.info('Finished notifying!')
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def http_client(url)
|
224
|
+
uri = URI(url)
|
225
|
+
|
226
|
+
use_ssl = uri.scheme == 'https'
|
227
|
+
response = Net::HTTP.start(uri.host, uri.port, :use_ssl => use_ssl) do |http|
|
228
|
+
yield(http, uri.path)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def new_post_request(path, form_data)
|
233
|
+
request = Net::HTTP::Post.new(path)
|
234
|
+
request.basic_auth(@client_id, @client_secret)
|
235
|
+
request.set_form_data(form_data)
|
236
|
+
return request
|
237
|
+
end
|
238
|
+
|
239
|
+
def set_request_auth(request, access_token)
|
240
|
+
request["Authorization"] = "Bearer " + access_token
|
241
|
+
end
|
242
|
+
|
243
|
+
def set_api_headers(request, access_token, format="application/json")
|
244
|
+
set_request_auth(request, access_token)
|
245
|
+
request["Accept"] = format
|
246
|
+
request.content_type = format
|
247
|
+
end
|
248
|
+
|
249
|
+
def check_auth_token(auth_token, scopes)
|
250
|
+
if auth_token.nil?
|
251
|
+
raise MxitRails::MxitApi::Exception.new("No auth token has been set/provided.")
|
252
|
+
elsif not auth_token.has_scopes? scopes
|
253
|
+
raise MxitRails::MxitApi::Exception.new("The auth token doesn't have the required " +
|
254
|
+
"scope(s).")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module MxitRails::MxitApi
|
2
|
+
class AuthToken
|
3
|
+
attr_reader :access_token, :type, :expires_in, :refresh_token, :expires_at,
|
4
|
+
:refresh_token_expires_at
|
5
|
+
|
6
|
+
def initialize(token_response)
|
7
|
+
@access_token = token_response['access_token']
|
8
|
+
@type = token_response['type']
|
9
|
+
@expires_in = token_response['expires_in']
|
10
|
+
@refresh_token = token_response['refresh_token']
|
11
|
+
@scope = token_response['scope'].split
|
12
|
+
|
13
|
+
@expires_at = Time.now + expires_in.seconds
|
14
|
+
# If there isn't a refresh token `has_refresh_token_expired?` must always return true.
|
15
|
+
@refresh_token_expires_at = @refresh_token ? Time.now + 24.hours : Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def scope
|
19
|
+
@scope.join(' ')
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_expired?
|
23
|
+
# For extreme latency check within 3 seconds.
|
24
|
+
@expires_at - Time.now <= 3.0
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_refresh_token_expired?
|
28
|
+
@refresh_token_expires_at - Time.now <= 3.0
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_scopes?(scopes)
|
32
|
+
(scopes - @scope).empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/mxit_rails/page.rb
CHANGED
@@ -70,6 +70,7 @@ module MxitRails
|
|
70
70
|
|
71
71
|
@_mxit = descriptor
|
72
72
|
@_mxit_validated = true
|
73
|
+
@_mxit_validation_types = []
|
73
74
|
@_mxit_validation_messages = []
|
74
75
|
|
75
76
|
clean_session
|
@@ -118,6 +119,7 @@ module MxitRails
|
|
118
119
|
|
119
120
|
valid = true
|
120
121
|
input = descriptor.input.to_sym
|
122
|
+
validation_type = arguments.first
|
121
123
|
|
122
124
|
if block.nil?
|
123
125
|
parameter = arguments[1..-2][0] # Will return nil if there isn't an argument
|
@@ -127,30 +129,36 @@ module MxitRails
|
|
127
129
|
end
|
128
130
|
|
129
131
|
else
|
132
|
+
validation_type = :custom
|
130
133
|
valid = yield(params[input])
|
131
134
|
end
|
132
135
|
|
133
136
|
if !valid
|
134
137
|
@_mxit_validated = false
|
138
|
+
@_mxit_validation_types << validation_type
|
135
139
|
@_mxit_validation_messages << arguments.last
|
136
140
|
end
|
137
141
|
|
138
142
|
end
|
139
143
|
|
144
|
+
def validations_failed &block
|
145
|
+
descriptor.validations_failed = block
|
146
|
+
end
|
147
|
+
def validated &block
|
148
|
+
descriptor.validated = block
|
149
|
+
end
|
150
|
+
|
140
151
|
def submit &block
|
141
152
|
set_descriptor :default
|
142
153
|
|
143
154
|
if descriptor.form? && next_step?
|
144
155
|
yield
|
145
156
|
|
146
|
-
elsif params.include?(:_mxit_rails_submit)
|
147
|
-
|
148
|
-
yield
|
149
|
-
end
|
157
|
+
elsif (params.include?(:_mxit_rails_submit) && @_mxit_validated) || (self.current_step == :_submit)
|
158
|
+
yield
|
150
159
|
end
|
151
160
|
end
|
152
161
|
|
153
|
-
|
154
162
|
def current_step
|
155
163
|
return nil if session[:_mxit_rails_step].nil?
|
156
164
|
session[:_mxit_rails_step].to_sym
|
@@ -176,6 +184,12 @@ module MxitRails
|
|
176
184
|
|
177
185
|
if params.include?(:_mxit_rails_submit)
|
178
186
|
if @_mxit_validated
|
187
|
+
# Validated hook
|
188
|
+
unless descriptor.validated.nil?
|
189
|
+
descriptor.validated.call
|
190
|
+
end
|
191
|
+
|
192
|
+
# Store input in session
|
179
193
|
if descriptor.input
|
180
194
|
input = descriptor.input.to_sym
|
181
195
|
mxit_form_session[input] = params[input]
|
@@ -185,9 +199,15 @@ module MxitRails
|
|
185
199
|
mxit_form_session[select] = params[select]
|
186
200
|
end
|
187
201
|
|
202
|
+
# Clear submission flag from params and go to next step
|
188
203
|
params.delete :_mxit_rails_submit
|
189
204
|
@next_step = true
|
190
205
|
return
|
206
|
+
else
|
207
|
+
# Validations_failed hook
|
208
|
+
unless descriptor.validations_failed.nil?
|
209
|
+
descriptor.validations_failed.call(@_mxit_validation_types, @_mxit_validation_messages)
|
210
|
+
end
|
191
211
|
end
|
192
212
|
end
|
193
213
|
|
@@ -203,6 +223,12 @@ module MxitRails
|
|
203
223
|
redirect_to request.path
|
204
224
|
end
|
205
225
|
|
226
|
+
def submit!
|
227
|
+
self.current_step = :_submit
|
228
|
+
# A redirect might not be absolutely required, but it makes things much simpler
|
229
|
+
redirect_to request.path
|
230
|
+
end
|
231
|
+
|
206
232
|
def form &block
|
207
233
|
descriptor.type = :form
|
208
234
|
session[:_mxit_rails_params] ||= {}
|