mxit-rails 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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] ||= {}
|