wschenk-workstreamer_api 0.1.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.
Files changed (71) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +23 -0
  5. data/Rakefile +52 -0
  6. data/VERSION +1 -0
  7. data/lib/workstreamer_api.rb +375 -0
  8. data/rails_example/Rakefile +10 -0
  9. data/rails_example/app/controllers/application.rb +15 -0
  10. data/rails_example/app/controllers/user_controller.rb +12 -0
  11. data/rails_example/app/controllers/workstreamer_controller.rb +154 -0
  12. data/rails_example/app/helpers/application_helper.rb +3 -0
  13. data/rails_example/app/helpers/user_helper.rb +2 -0
  14. data/rails_example/app/helpers/workstreamer_helper.rb +2 -0
  15. data/rails_example/app/models/user.rb +2 -0
  16. data/rails_example/app/views/layouts/application.rhtml +158 -0
  17. data/rails_example/app/views/workstreamer/index.html.erb +1 -0
  18. data/rails_example/app/views/workstreamer/info.html.erb +25 -0
  19. data/rails_example/app/views/workstreamer/stream.html.erb +24 -0
  20. data/rails_example/config/app_config.yml +3 -0
  21. data/rails_example/config/boot.rb +109 -0
  22. data/rails_example/config/database.yml +22 -0
  23. data/rails_example/config/environment.rb +77 -0
  24. data/rails_example/config/environments/development.rb +17 -0
  25. data/rails_example/config/environments/production.rb +24 -0
  26. data/rails_example/config/environments/test.rb +22 -0
  27. data/rails_example/config/initializers/inflections.rb +10 -0
  28. data/rails_example/config/initializers/mime_types.rb +5 -0
  29. data/rails_example/config/initializers/new_rails_defaults.rb +17 -0
  30. data/rails_example/config/locales/en.yml +5 -0
  31. data/rails_example/config/routes.rb +43 -0
  32. data/rails_example/db/migrate/20090611183940_create_users.rb +15 -0
  33. data/rails_example/db/schema.rb +23 -0
  34. data/rails_example/doc/README_FOR_APP +5 -0
  35. data/rails_example/public/404.html +30 -0
  36. data/rails_example/public/422.html +30 -0
  37. data/rails_example/public/500.html +33 -0
  38. data/rails_example/public/dispatch.cgi +10 -0
  39. data/rails_example/public/dispatch.fcgi +24 -0
  40. data/rails_example/public/dispatch.rb +10 -0
  41. data/rails_example/public/favicon.ico +0 -0
  42. data/rails_example/public/images/rails.png +0 -0
  43. data/rails_example/public/javascripts/application.js +2 -0
  44. data/rails_example/public/javascripts/controls.js +963 -0
  45. data/rails_example/public/javascripts/dragdrop.js +973 -0
  46. data/rails_example/public/javascripts/effects.js +1128 -0
  47. data/rails_example/public/javascripts/prototype.js +4320 -0
  48. data/rails_example/public/robots.txt +5 -0
  49. data/rails_example/script/about +4 -0
  50. data/rails_example/script/console +3 -0
  51. data/rails_example/script/dbconsole +3 -0
  52. data/rails_example/script/destroy +3 -0
  53. data/rails_example/script/generate +3 -0
  54. data/rails_example/script/performance/benchmarker +3 -0
  55. data/rails_example/script/performance/profiler +3 -0
  56. data/rails_example/script/performance/request +3 -0
  57. data/rails_example/script/plugin +3 -0
  58. data/rails_example/script/process/inspector +3 -0
  59. data/rails_example/script/process/reaper +3 -0
  60. data/rails_example/script/process/spawner +3 -0
  61. data/rails_example/script/runner +3 -0
  62. data/rails_example/script/server +3 -0
  63. data/rails_example/test/fixtures/users.yml +7 -0
  64. data/rails_example/test/functional/user_controller_test.rb +8 -0
  65. data/rails_example/test/functional/workstreamer_controller_test.rb +8 -0
  66. data/rails_example/test/performance/browsing_test.rb +9 -0
  67. data/rails_example/test/test_helper.rb +38 -0
  68. data/rails_example/test/unit/user_test.rb +8 -0
  69. data/spec/spec_helper.rb +9 -0
  70. data/spec/workstreamer_api_spec.rb +7 -0
  71. metadata +143 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ rails_example/tmp
7
+ rails_example/log/*
8
+ rails_example/db/*.sqlite3
9
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Will Schenk
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,23 @@
1
+ = workstreamer_api
2
+
3
+ This gem provides a ruby wrapper to interface with workstreamer.com. It
4
+ depends on the "nokogiri" and "oauth" gems. Also included is an example
5
+ rails application which has an example of how to use the gem.
6
+
7
+ Documentation on how to use the API can be found http://workstreamer.pbworks.com/
8
+
9
+ To use the rails application, you need to first get a ClientApplication key from
10
+ workstreamer as documented here: http://workstreamer.pbworks.com/Authentication
11
+
12
+ Then edit rails_example/config/app_config.yml
13
+
14
+ == Copyright
15
+
16
+ Copyright (c) 2009 Workstreamer, LLC. See LICENSE for details.
17
+ = workstreamer_api
18
+
19
+ Description goes here.
20
+
21
+ == Copyright
22
+
23
+ Copyright (c) 2009 Will Schenk. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "workstreamer_api"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = "wschenk@gmail.com"
10
+ gem.homepage = "http://github.com/wschenk/workstreamer_api"
11
+ gem.authors = ["Will Schenk"]
12
+ gem.rubyforge_project = "workstreamer_api"
13
+ gem.add_dependency 'oauth'
14
+ gem.add_dependency 'nokogiri'
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ Jeweler::RubyforgeTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ require 'spec/rake/spectask'
24
+ Spec::Rake::SpecTask.new(:spec) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.pattern = 'spec/**/*_spec.rb'
32
+ spec.rcov = true
33
+ end
34
+
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ if File.exist?('VERSION.yml')
41
+ config = YAML.load(File.read('VERSION.yml'))
42
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
43
+ else
44
+ version = ""
45
+ end
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "workstreamer_api #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
52
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,375 @@
1
+ require 'rubygems'
2
+ require 'oauth'
3
+ require 'pp'
4
+ require 'json'
5
+ require 'nokogiri'
6
+
7
+ ##
8
+ # The WsApi module wraps all functionality needed to interface with the
9
+ # workstreamer system.
10
+ module WorkstreamerApi
11
+ ##
12
+ # Thrown if any call returns a 401; this means that you don't have a valid
13
+ # AccessToken. It either never existed, or the token was invalidated by the
14
+ # user.
15
+ class Unauthorized < Exception ; end
16
+ class Forbidden < Exception ; end
17
+
18
+ ##
19
+ # This is the main workstreamer class. The key and secret need to have
20
+ # been generated on the workstreamer site. If the user doesn't have an
21
+ # AccessToken, this class can be used to generate a RequestToken.
22
+ class Api
23
+ ##
24
+ # Create a new workstreamer api instance.
25
+ #
26
+ # key - the ClientApplication key generated for your application by
27
+ # workstreamer.com at http://alpha.workstreamer.com/oauth_clients
28
+ # secret - that matches the key
29
+ # site - normally "alpha.workstreamer.com" TODO This needs to be domain aware
30
+ #
31
+ # This is all that is needed to create a RequestToken for a user who
32
+ # hasn't given permission. (See request_token for more info.) If
33
+ # the user has already granted access, be sure to pass in
34
+ #
35
+ # user_token - the AccessToken back from the oath provider
36
+ # user_secret - the secret.
37
+ #
38
+ # See example/app/controllers/workstreamer_controller.callback for an example.
39
+ #
40
+ def initialize( key, secret, site = "http://localhost:3000", user_token = nil, user_secret = nil )
41
+ @key = key
42
+ @secret = secret
43
+ @site = site
44
+ @user_token = user_token
45
+ @user_secret = user_secret
46
+ end
47
+
48
+ # Utility methods
49
+
50
+ ##
51
+ # Returns a (cached) OAuth::Consumer
52
+ #
53
+ def consumer
54
+ @consumer ||= OAuth::Consumer.new @key, @secret, { :site => @site }
55
+ end
56
+
57
+ ##
58
+ # This generates a request token for your application. Once a RequestToken
59
+ # is generated you then redirect the browser to the authorize_url. e.g.
60
+ #
61
+ # request_token = workstreamer.request_token
62
+ #
63
+ # session[:request_token] = request_token.token
64
+ # session[:request_token_secret] = request_token.secret
65
+ #
66
+ # redirect_to request_token.authorize_url
67
+ #
68
+ def request_token
69
+ @request_token ||= consumer.get_request_token
70
+ end
71
+
72
+ ##
73
+ # This returns a (cached) OAuth::AccessToken which can, in turn, be used
74
+ # to make remote HTTP calls to workstreamer. All of the API wrapper calls use
75
+ # this method for the actual network transportation.
76
+ #
77
+ def access_token
78
+ @access_token ||= OAuth::AccessToken.new( consumer, @user_token, @user_secret )
79
+ end
80
+
81
+ ##
82
+ # OAuth-orized HTTP GET to workstreamer. Passed in a URL it will
83
+ # return a parsed object. If JSON is requested, it will return a Hash.
84
+ # Otherwise, it will return a nogokiri XML object. (convert_node_to_hash will
85
+ # transform a nogokiri XML object to a Hash but won't preserve the XML attributes.)
86
+ #
87
+ def get( url, json = true )
88
+ ret = access_token.get( url )
89
+ parse( ret, json )
90
+ end
91
+
92
+ ##
93
+ # OAuth-orized HTTP POST to workstreamer. Passed in a URL and a hash or
94
+ # parameters it will return a parsed object. If JSON is requested, it
95
+ # will return a Hash. Otherwise, it will return a nogokiri XML object.
96
+ # (convert_node_to_hash will transform a nogokiri XML object to a Hash
97
+ # but won't preserve the XML attributes.)
98
+ #
99
+ def post( url, parameters, json = true )
100
+ ret = access_token.post url, parameters
101
+ parse( ret, json )
102
+ end
103
+
104
+ ##
105
+ # Intepret a server response. Raise WsApi::Unauthorized or WsApi::HTTPSuccess
106
+ # if there was an error, otherwise process the body through JSON.parse or
107
+ # Nokogiri::XML.
108
+ #
109
+ # TODO Should probably do this on the returned content-type rather than parameter
110
+ def parse( ret, json = true )
111
+ case ret
112
+ when Net::HTTPSuccess
113
+ # logger.debug ret.body
114
+ if json
115
+ return JSON.parse(ret.body)
116
+ else
117
+ return Nokogiri::XML( ret.body )
118
+ end
119
+ when Net::HTTPUnauthorized
120
+ raise Unauthorized
121
+ when Net::HTTPForbidden
122
+ raise Forbidden
123
+ else
124
+ puts ret
125
+ puts ret.body
126
+ raise Unknown
127
+ end
128
+ end
129
+
130
+ ##
131
+ # Used to build the parameter options; will only create an entry if
132
+ # the options is set, and will collapse arrays to comma seperated.
133
+ #
134
+ # params - hash to set (destination)
135
+ # key - the key to move over
136
+ # options - hash that we're getting the data from (origin)
137
+ #
138
+ def set_options( params, key, options )
139
+ if options[key].is_a? Array
140
+ params[key] = options[key].join( "," )
141
+ elsif options[key]
142
+ params[key] = options[key]
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Simple method to turn a Nokogiri XML object into a ruby hash.
148
+ #
149
+ def convert_node_to_hash( xml )
150
+ ret = {}
151
+
152
+ xml.children.each do |child|
153
+ if child.is_a? Nokogiri::XML::Element
154
+ subchildren = child.children
155
+ if subchildren.size == 1
156
+ ret[child.name] = child.inner_html
157
+ else
158
+ ret[child.name] = convert_node_to_hash( child )
159
+ end
160
+ end
161
+ end
162
+
163
+ ret
164
+ end
165
+
166
+ # Api methods
167
+
168
+ ##
169
+ # Looks up information on a specific user. If login is null, looks up the
170
+ # current/authorized user.
171
+ #
172
+ # login - workstreamer login or user id
173
+ #
174
+ def user_info( login = nil )
175
+ url = "/user/info?format=json"
176
+ url += "&login=#{login}" if login
177
+ get url
178
+ end
179
+
180
+ ##
181
+ # Returns the user's current stream.
182
+ #
183
+ # TODO Stream filters
184
+ # TODO Unread counts
185
+ #
186
+ def stream
187
+ xml = get( "/post_apis/index?format=xml", false )
188
+
189
+ ret = []
190
+
191
+ (xml/'master-content').each do |mc|
192
+ ret << convert_node_to_hash( mc )
193
+ end
194
+
195
+ ret
196
+ end
197
+
198
+ ##
199
+ # Generate create_content method, which all other content creating methods
200
+ # -- except for FeedItems -- delegate to. Depending upon the parameters,
201
+ # content of a different type is created.
202
+ #
203
+ # See http://workstreamer.pbworks.com/Content-API for details on the
204
+ # parameters to pass in.
205
+ #
206
+ def create_content( parameters )
207
+ xml = post( "/post_apis/create", parameters, false )
208
+
209
+ return false if xml.nil?
210
+
211
+ node = xml.at( "result" )
212
+ return false if node.nil?
213
+ node.content == "SUCCESS"
214
+ end
215
+
216
+ ##
217
+ # Create a status update. The content is parsed on the worksteamer server,
218
+ # so if you use @user or #tag the status update with be directed to a user
219
+ # and/or tagged.
220
+ #
221
+ # message - The status update.
222
+ #
223
+ # TODO: Add project
224
+ #
225
+ def post_status_update( message )
226
+ create_content( {:content => message } )
227
+ end
228
+
229
+ ##
230
+ # Create a direct message from the current user to user_name.
231
+ #
232
+ # user_name - User to send the message to
233
+ # message - The message
234
+ #
235
+ def post_direct_message( user_name, message )
236
+ create_content( {:content => "d @#{user_name} #{message}"} )
237
+ end
238
+
239
+ ##
240
+ # Lookup multiple users at the same time. Returns an array of the
241
+ # same type as user_info returns.
242
+ #
243
+ # users - list of users
244
+ #
245
+ def lookup_users( users )
246
+ users = users.gsub( /([\s,]+)/, " " ).split( / / )
247
+
248
+ ret = {}
249
+ users.each do |login|
250
+ ret[login] = user_info( login )
251
+ end
252
+
253
+ ret
254
+ end
255
+
256
+ ##
257
+ # Lookup multuple users but return an array of user_ids
258
+ #
259
+ def lookup_user_ids( users )
260
+ lookup_users( users ).collect { |x| x[1]['id'] }
261
+ end
262
+
263
+ ##
264
+ # Get project title and membership.
265
+ #
266
+ # project - Project name or project id
267
+ #
268
+ def lookup_project( project )
269
+ get "/projects/view?format=json&name=#{URI::escape project}"
270
+ end
271
+
272
+ ##
273
+ # Post a message with optional comma seperated tags.
274
+ #
275
+ # title - the title of the message
276
+ # body - the body of the message
277
+ #
278
+ # Options include:
279
+ #
280
+ # :tags => "tag1,tag2,tag3"
281
+ #
282
+ # :recipient_ids => "user_id1,user_id2,user_id3" or [user1_id, user2_id, user3_id]
283
+ # or
284
+ # :recipients => "tom,dick,harry" or ["tom", "dick", "harry"]
285
+ #
286
+ # :project => "project_name"
287
+ # or
288
+ # :project_id => "project_id"
289
+ #
290
+ def post_message( title, body, options )
291
+ params = {:title => title, :content => body }
292
+ set_options( params, :tags, options )
293
+ options[:recipient_ids] = options[:recipient_ids].join( "," ) if options[:recipient_ids]
294
+ options[:recipient_ids] = lookup_user_ids( options[:recipients] ) if options[:recipients]
295
+ options[:recipients] = options[:recipient_ids]
296
+ set_options( params, :recipients, options )
297
+
298
+ options[:project_id] = lookup_project( options[:project] )['id'] if options[:project]
299
+
300
+ set_options( params, :project_id, options )
301
+
302
+ pp params
303
+
304
+ create_content( params )
305
+ end
306
+
307
+ ##
308
+ # Creates an event
309
+ #
310
+ # title - Event title
311
+ # start_date - Defaults to today
312
+ # end_date - when the event ends
313
+ #
314
+ # TODO Add project and permissions.
315
+ def create_event( title, start_date = Date.today, end_date = nil )
316
+ params = {:title => title, :start_date => start_date }
317
+ params[:end_date] = end_date if end_date
318
+ create_content( params )
319
+ end
320
+
321
+ ##
322
+ # Creates a task
323
+ #
324
+ # title - the Task title
325
+ # assigned_to - The user name or user id of who the task is assigned to
326
+ # content - the body of the task
327
+ # project_id - the optional project
328
+ #
329
+ def create_task( title, assigned_to, content, project_id = nil )
330
+ unless assigned_to =~ /^\d+$/
331
+ assigned_to = lookup_user_ids( assigned_to ).first
332
+ end
333
+
334
+ create_content({:title => title, :assigned_to => assigned_to, :content => content, :project_id => project_id } )
335
+ end
336
+
337
+ ##
338
+ # Create a comment on an existing content.
339
+ #
340
+ # comment_text - the comment text
341
+ # parent_id - the id of the MasterContent that you are commenting on. This
342
+ # is NOT the id of the content itself, but the enclosing
343
+ # MasterContent
344
+ #
345
+ def create_comment( comment_text, parent_id )
346
+ create_content({:comment => comment_text, :reply_to => parent_id})
347
+ end
348
+
349
+ ##
350
+ # Return everything we know about a content.
351
+ #
352
+ # id - The content id.
353
+ #
354
+ def get_content( id )
355
+ url = "/post_apis/show?id=#{id}"
356
+
357
+ xml = get url, false
358
+
359
+ convert_node_to_hash xml
360
+ end
361
+
362
+ ##
363
+ # Create a feed item. Params are
364
+ #
365
+ # :remote_id - The remote id for this feed item (uninterpreted)
366
+ # :created_at - The created_at/published date, defaults to now
367
+ # :title - Title of the item
368
+ # :summary - The body of the item shown when expanded
369
+ # :url - The url that represents the "original" item
370
+ #
371
+ def post_feed_item( params )
372
+ post "/feed/post_item", params, false
373
+ end
374
+ end
375
+ end