CollegiateLink 0.0.2

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.
@@ -0,0 +1,46 @@
1
+ require 'digest'
2
+ require 'net/https'
3
+ require 'net/ssh'
4
+ require 'guid'
5
+ require 'nokogiri'
6
+ require 'happymapper'
7
+
8
+ require 'collegiatelink/response'
9
+ require 'collegiatelink/request'
10
+ require 'collegiatelink/client'
11
+ require 'collegiatelink/organization'
12
+
13
+ ##
14
+ # This gem provides a CollegiateLink API client in Ruby. It's still under
15
+ # development, so let me know if you plan to use it. (tomdooner@gmail.com)
16
+ #
17
+ module CollegiateLink
18
+ # URL base for all requests. The API key will be substituted into this when
19
+ # the request is made.
20
+ URL_BASE = 'https://%s.collegiatelink.net/ws/'
21
+
22
+ # Currently-supported CollegiateLink action request types
23
+ ACTIONS = [
24
+ 'organization/list',
25
+ 'organization/roster',
26
+ #'user/memberships',
27
+ #'user/position',
28
+ 'event/list',
29
+ ]
30
+
31
+ # Raised when performing a CollegiateLink::Request for an action that is not
32
+ # supported by the gem (as defined by CollegiateLink::ACTIONS).
33
+ class UnknownAction < Exception; end
34
+
35
+ # Raised when something unexpected goes wrong.
36
+ class UnknownException < Exception; end
37
+
38
+ # Raised when the CollegiateLink::Request HTTP request fails.
39
+ class NetworkException < Exception; end
40
+
41
+ # Raised when CollegiateLink returns that the combination of IP and SSL fails.
42
+ class AuthenticationException < Exception; end
43
+
44
+ # Raised for the CollegiateLink VALIDATION_ERROR case.
45
+ class ValidationException < Exception; end
46
+ end
@@ -0,0 +1,79 @@
1
+ module CollegiateLink
2
+
3
+ # The Client is what makes the requests to CollegiateLink.
4
+ class Client
5
+ ##
6
+ # Creates a CollegiateLink client.
7
+ #
8
+ # ==== Parameters:
9
+ # * +apikey+ - The CollegiateLink "apikey" for your institution. For example, at Case Western Reserve University we go to http://casewestern.collegiatelink.net, so our "apikey" is "casewestern"
10
+ # * +ip+ - The IP address that the +sharedkey+ is approved for.
11
+ # * +sharedkey+ - The shared key granted by CollegiateLink to your IP and institution.
12
+ #
13
+ def initialize(apikey, ip, sharedkey)
14
+ @params = {
15
+ apikey: apikey,
16
+ ip: ip
17
+ }
18
+ @opts = {
19
+ sharedkey: sharedkey
20
+ }
21
+ end
22
+
23
+ ##
24
+ # Return a complete list of CollegiateLink::Organization instances for your
25
+ # institution.
26
+ #
27
+ # ==== Required Parameters:
28
+ # None
29
+ #
30
+ # ==== Optional Parameters:
31
+ # See CollegiateLink::Request#initialize for a list of optional parameters
32
+ #
33
+ def organizations(params = {})
34
+ orgs = request('organization/list', params)
35
+ orgs.map{ |o| CollegiateLink::Organization.parse(o.to_s) }
36
+ end
37
+
38
+ ##
39
+ # Return a complete list of CollegiateLink::Event instances for your
40
+ # institution.
41
+ #
42
+ # ==== Required Parameters:
43
+ # * <tt>:startdate</tt> - Time instance or the Unix integral timestamp for the beginning of results.
44
+ # * <tt>:enddate</tt> - Time instance or the Unix integral timestamp for the end of results.
45
+ #
46
+ # ==== Optional Parameters:
47
+ # See CollegiateLink::Request#initialize for a list of additional parameters
48
+ # that requests can use.
49
+ def events(params = {})
50
+ # Default to showing events in the past 7 days
51
+ seven_days = 7 * 24 * 60 * 60
52
+ params[:startdate] ||= (Time.now.to_i - seven_days)
53
+ params[:enddate] ||= (Time.now.to_i)
54
+
55
+ # Convert to milliseconds...
56
+ params[:startdate] = params[:startdate].to_i * 1000
57
+ params[:enddate] = params[:enddate].to_i * 1000
58
+
59
+ events = request('event/list', params)
60
+ events.map{ |o| CollegiateLink::Event.parse(o.to_s) }
61
+ end
62
+
63
+ private
64
+
65
+ def request(action, params = {})
66
+ cl_request = CollegiateLink::Request.new(action, params.merge(@params), @opts)
67
+ cl_resp = cl_request.perform
68
+
69
+ all_items = cl_resp.items
70
+
71
+ while cl_resp.has_next_page?
72
+ cl_resp = cl_request.request_for_next_page.perform
73
+ all_items += cl_resp.items
74
+ end
75
+
76
+ all_items
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,98 @@
1
+ module CollegiateLink
2
+ ##
3
+ # Address object returned by CollegiateLink as part of an Organization or
4
+ # Event record.
5
+ #
6
+ # ==== Properties:
7
+ # * <tt>:city </tt> - String
8
+ # * <tt>:country </tt> - String
9
+ # * <tt>:postalcode</tt> - String
10
+ # * <tt>:state </tt> - String
11
+ # * <tt>:street1 </tt> - String
12
+ # * <tt>:street2 </tt> - String
13
+ # * <tt>:type </tt> - Integer
14
+ class Address
15
+ include HappyMapper
16
+
17
+ element :city, String
18
+ element :country, String
19
+ element :postalcode, String
20
+ element :state, String
21
+ element :street1, String
22
+ element :street2, String
23
+ element :type, Integer
24
+ end
25
+
26
+ ##
27
+ # Category object returned by CollegiateLink as part of an Organization record
28
+ # See: # http://support.collegiatelink.net/entries/332558-web-services-developer-documentation#orgList
29
+ #
30
+ # ==== Properties:
31
+ # * <tt>:id </tt> - Integer
32
+ # * <tt>:ishidden</tt> - String
33
+ # * <tt>:name </tt> - String
34
+ class Category
35
+ include HappyMapper
36
+
37
+ element :id, Integer
38
+ element :ishidden, String
39
+ element :name, String
40
+ end
41
+
42
+ ##
43
+ # An Organization record returned by CollegiateLink
44
+ # See: http://support.collegiatelink.net/entries/332558-web-services-developer-documentation#orgList
45
+ #
46
+ # ==== Properties:
47
+ # * <tt>:id </tt> - Integer
48
+ # * <tt>:ishidden</tt> - String
49
+ # * <tt>:name </tt> - String
50
+ # * <tt>:addresses </tt> - Array of Address objects
51
+ # * <tt>:categories </tt> - Array of Category objects
52
+ class Organization
53
+ include HappyMapper
54
+
55
+ element :id, Integer
56
+ element :parentId, Integer
57
+ element :name, String
58
+ element :description, String
59
+ element :shortName, String
60
+ element :siteUrl, String
61
+ element :status, String
62
+ element :type, String
63
+ has_many :addresses, Address
64
+ has_many :categories, Category
65
+ end
66
+
67
+ ##
68
+ # An Event record returned by CollegiateLink
69
+ # See: http://support.collegiatelink.net/entries/332558-web-services-developer-documentation#eventList
70
+ #
71
+ # ==== Properties:
72
+ # * <tt>:id </tt> - Integer
73
+ # * <tt>:name </tt> - String
74
+ # * <tt>:description</tt> - String
75
+ # * <tt>:startDate </tt> - Integer
76
+ # * <tt>:endDate </tt> - Integer
77
+ # * <tt>:location </tt> - String
78
+ # * <tt>:status </tt> - String
79
+ # * <tt>:urlLarge </tt> - String
80
+ # * <tt>:urlSmall </tt> - String
81
+ # * <tt>:organization</tt> - The hosting Organization
82
+ #
83
+ class Event
84
+ include HappyMapper
85
+
86
+ element :id, Integer
87
+ element :name, String
88
+ element :description, String
89
+ element :startDate, Integer
90
+ element :endDate, Integer
91
+ element :location, String
92
+ element :status, String
93
+ element :urlLarge, String
94
+ element :urlSmall, String
95
+
96
+ has_one :organization, Organization
97
+ end
98
+ end
@@ -0,0 +1,122 @@
1
+ module CollegiateLink
2
+ ##
3
+ # Represents a single HTTP request to the CollegiateLink API.
4
+ #
5
+ class Request
6
+ ##
7
+ # Create a new Request instance. See +CollegiateLink::ACTIONS+ for a list of
8
+ # eligible actions.
9
+ #
10
+ # ==== Parameters:
11
+ # All parameters are provided in the URL of the request to CollegiateLink.
12
+ #
13
+ # * <tt>:page</tt> - The desired page number of results. (default = 1)
14
+ # * <tt>:pagesize</tt> - The desired number of records to return (default = 100)
15
+ # * <tt>:modelformatting</tt> - Either "normal" or "humanreadable". (default = "normal")
16
+ #
17
+ # ==== Options:
18
+ # Options are ways to pass in additional parameters about the request. The
19
+ # following options are supported.
20
+ #
21
+ # * <tt>:sharedkey</tt> - (Required) The shared key which is used, but not included in the URL of requests
22
+ # * <tt>:debug</tt> - Print debug information as the request happens.
23
+ #
24
+ def initialize(action, params = {}, opts = {})
25
+ raise UnknownAction unless ACTIONS.include?(action)
26
+ raise AuthenticationException unless opts.include?(:sharedkey)
27
+
28
+ @action = action
29
+ @params = params
30
+ @opts = opts
31
+
32
+ # URL Query String Parameters
33
+ @params[:page] ||= 1
34
+ @params[:pagesize] ||= 100
35
+ @params[:modelformatting] ||= 'normal'
36
+ end
37
+
38
+ ##
39
+ # Execute the request by sending the HTTP request.
40
+ #
41
+ def perform
42
+ # First, add the time-sensitive bits...
43
+ request_params = @params.merge({
44
+ time: Time.now.to_i * 1000,
45
+ random: Guid.new
46
+ })
47
+ request_params[:hash] = hash(request_params.merge(sharedkey: @opts[:sharedkey]))
48
+
49
+ # Then, compute the URL...
50
+ url = URI([
51
+ URL_BASE % @params[:apikey]
52
+ @action
53
+ '?'
54
+ URI.encode_www_form(request_params)
55
+ ].join)
56
+
57
+ puts "requesting: \n#{url}" if @opts[:debug]
58
+
59
+ # Make the Request!
60
+ res = Net::HTTP.new(url.host, url.port)
61
+ res.use_ssl = true
62
+ res.verify_mode = OpenSSL::SSL::VERIFY_NONE
63
+
64
+ resp = res.start { |h|
65
+ h.request_get(url.to_s);
66
+ }
67
+
68
+ case resp
69
+ when Net::HTTPSuccess
70
+ return parse_response(resp.body)
71
+ when Net::HTTPError
72
+ raise NetworkException
73
+ else
74
+ raise UnknownException
75
+ end
76
+ end
77
+
78
+ ##
79
+ # Increment the page number and return a request instance that would receive
80
+ # the next bunch of pages.
81
+ #
82
+ def request_for_next_page
83
+ Request.new(@action, @params.merge(page: @params[:page] + 1), @opts)
84
+ end
85
+
86
+ private
87
+
88
+ def parse_response(body)
89
+ d = Nokogiri::XML::Document.parse(body)
90
+
91
+ # Parse exceptions
92
+ if d.xpath("//results/error").length > 0
93
+ type = d.xpath('//results/error/type').inner_html
94
+ message = d.xpath('//results/error/message').inner_html
95
+ case type
96
+ when 'AUTHENTICATION_ERROR'
97
+ raise AuthenticationException, message
98
+ when 'VALIDATION_ERROR'
99
+ raise ValidationException, message
100
+ when 'UNKNOWN_SERVICE'
101
+ raise UnknownAction, message
102
+ when 'GENERAL_ERROR'
103
+ raise UnknownException, message
104
+ else
105
+ raise UnknownException, message
106
+ end
107
+ end
108
+
109
+ Response.new(d)
110
+ end
111
+
112
+ def hash(params = {})
113
+ Digest::MD5.hexdigest [
114
+ params[:apikey],
115
+ params[:ip],
116
+ params[:time].to_i,
117
+ params[:random],
118
+ params[:sharedkey],
119
+ ].join
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ module CollegiateLink
2
+ ##
3
+ # Container for the XML gunk returned from CollegiateLink
4
+ #
5
+ class Response
6
+ # Create the response.
7
+ #
8
+ # ==== Parameters:
9
+ # * +document+ - The Nokogiri document of the returned XML
10
+ def initialize(document)
11
+ raise UnknownException unless document.xpath('//results/page')
12
+
13
+ @document = document
14
+ @page_size = document.xpath('//results/page/pageSize').inner_html.to_i
15
+ @page_number = document.xpath('//results/page/pageNumber').inner_html.to_i
16
+ @total_items = document.xpath('//results/page/totalItems').inner_html.to_i
17
+ @total_pages = document.xpath('//results/page/totalPages').inner_html.to_i
18
+ end
19
+
20
+ # Extract the items from the response. Returns a Nokogiri NodeSet.
21
+ def items
22
+ @document.xpath('//results/page/items/*')
23
+ end
24
+
25
+ # Utility method to determine if the request has another page to retrieve.
26
+ def has_next_page?
27
+ @page_number < @total_pages
28
+ end
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: CollegiateLink
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tom Dooner
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: guid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: happymapper
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: net-ssh
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description:
95
+ email: ted27@case.edu
96
+ executables: []
97
+ extensions: []
98
+ extra_rdoc_files: []
99
+ files:
100
+ - lib/collegiatelink.rb
101
+ - lib/collegiatelink/request.rb
102
+ - lib/collegiatelink/response.rb
103
+ - lib/collegiatelink/client.rb
104
+ - lib/collegiatelink/organization.rb
105
+ homepage:
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: CollegiateLink Client Gem
129
+ test_files: []
130
+ has_rdoc: