mangoapps-ex-sdk-ruby 0.15.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +506 -0
  3. data/LICENSE +201 -0
  4. data/README.md +2040 -0
  5. data/lib/mangoapps/client.rb +219 -0
  6. data/lib/mangoapps/config.rb +98 -0
  7. data/lib/mangoapps/errors.rb +45 -0
  8. data/lib/mangoapps/modules/attachments/get_folder_files.rb +14 -0
  9. data/lib/mangoapps/modules/attachments/get_folders.rb +14 -0
  10. data/lib/mangoapps/modules/attachments.rb +14 -0
  11. data/lib/mangoapps/modules/feeds/feeds.rb +14 -0
  12. data/lib/mangoapps/modules/feeds.rb +12 -0
  13. data/lib/mangoapps/modules/learn/course_catalog.rb +16 -0
  14. data/lib/mangoapps/modules/learn/course_categories.rb +16 -0
  15. data/lib/mangoapps/modules/learn/course_details.rb +13 -0
  16. data/lib/mangoapps/modules/learn/my_learning.rb +13 -0
  17. data/lib/mangoapps/modules/learn.rb +18 -0
  18. data/lib/mangoapps/modules/libraries/get_libraries.rb +14 -0
  19. data/lib/mangoapps/modules/libraries/get_library_categories.rb +14 -0
  20. data/lib/mangoapps/modules/libraries/get_library_items.rb +14 -0
  21. data/lib/mangoapps/modules/libraries.rb +16 -0
  22. data/lib/mangoapps/modules/notifications/my_priority_items.rb +13 -0
  23. data/lib/mangoapps/modules/notifications/notifications.rb +14 -0
  24. data/lib/mangoapps/modules/notifications.rb +14 -0
  25. data/lib/mangoapps/modules/posts/get_all_posts.rb +14 -0
  26. data/lib/mangoapps/modules/posts/get_post_by_id.rb +13 -0
  27. data/lib/mangoapps/modules/posts.rb +14 -0
  28. data/lib/mangoapps/modules/recognitions/award_categories.rb +14 -0
  29. data/lib/mangoapps/modules/recognitions/core_value_tags.rb +13 -0
  30. data/lib/mangoapps/modules/recognitions/get_award_feeds.rb +14 -0
  31. data/lib/mangoapps/modules/recognitions/get_awards_list.rb +14 -0
  32. data/lib/mangoapps/modules/recognitions/get_profile_awards.rb +13 -0
  33. data/lib/mangoapps/modules/recognitions/get_team_awards.rb +13 -0
  34. data/lib/mangoapps/modules/recognitions/gift_cards.rb +13 -0
  35. data/lib/mangoapps/modules/recognitions/leaderboard_info.rb +13 -0
  36. data/lib/mangoapps/modules/recognitions.rb +26 -0
  37. data/lib/mangoapps/modules/tasks/get_task_details.rb +13 -0
  38. data/lib/mangoapps/modules/tasks/get_tasks.rb +14 -0
  39. data/lib/mangoapps/modules/tasks.rb +14 -0
  40. data/lib/mangoapps/modules/trackers/get_trackers.rb +14 -0
  41. data/lib/mangoapps/modules/trackers.rb +12 -0
  42. data/lib/mangoapps/modules/users.rb +11 -0
  43. data/lib/mangoapps/modules/wikis/get_wiki_details.rb +13 -0
  44. data/lib/mangoapps/modules/wikis/get_wikis.rb +14 -0
  45. data/lib/mangoapps/modules/wikis.rb +14 -0
  46. data/lib/mangoapps/oauth.rb +187 -0
  47. data/lib/mangoapps/response.rb +92 -0
  48. data/lib/mangoapps/version.rb +5 -0
  49. data/lib/mangoapps.rb +34 -0
  50. metadata +181 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module AwardCategories
7
+ def award_categories(page: 1, limit: 20, params: {})
8
+ params = params.merge(page: page, limit: limit)
9
+ get("v2/recognitions/get_award_categories.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module CoreValueTags
7
+ def core_value_tags(params = {})
8
+ get("v2/recognitions/get_core_value_tags.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module GetAwardFeeds
7
+ def get_award_feeds(page: 1, limit: 20, params: {})
8
+ params = params.merge(page: page, limit: limit)
9
+ get("v2/recognitions/get_award_feeds.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module GetAwardsList
7
+ def get_awards_list(category_id, page: 1, limit: 20, params: {})
8
+ params = params.merge(category_id: category_id, page: page, limit: limit)
9
+ get("v2/recognitions/get_awards_list.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module GetProfileAwards
7
+ def get_profile_awards(params = {})
8
+ get("v2/recognitions/get_profile_awards.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module GetTeamAwards
7
+ def get_team_awards(params = {})
8
+ get("v2/recognitions/get_team_awards.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module GiftCards
7
+ def gift_cards(params = {})
8
+ get("v2/recognitions/gift_cards.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Recognitions
6
+ module LeaderboardInfo
7
+ def leaderboard_info(params = {})
8
+ get("v2/recognitions/get_leaderboard_info.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "recognitions/award_categories"
4
+ require_relative "recognitions/core_value_tags"
5
+ require_relative "recognitions/leaderboard_info"
6
+ require_relative "recognitions/gift_cards"
7
+ require_relative "recognitions/get_awards_list"
8
+ require_relative "recognitions/get_profile_awards"
9
+ require_relative "recognitions/get_team_awards"
10
+ require_relative "recognitions/get_award_feeds"
11
+
12
+ module MangoApps
13
+ class Client
14
+ module Recognitions
15
+ # Include all recognitions sub-modules
16
+ include MangoApps::Client::Recognitions::AwardCategories
17
+ include MangoApps::Client::Recognitions::CoreValueTags
18
+ include MangoApps::Client::Recognitions::LeaderboardInfo
19
+ include MangoApps::Client::Recognitions::GiftCards
20
+ include MangoApps::Client::Recognitions::GetAwardsList
21
+ include MangoApps::Client::Recognitions::GetProfileAwards
22
+ include MangoApps::Client::Recognitions::GetTeamAwards
23
+ include MangoApps::Client::Recognitions::GetAwardFeeds
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Tasks
6
+ module GetTaskDetails
7
+ def get_task_details(task_id, params = {})
8
+ get("tasks/#{task_id}.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Tasks
6
+ module GetTasks
7
+ def get_tasks(filter: "Pending_Tasks", page: 1, limit: 5, params: {})
8
+ params = params.merge(filter: filter, page: page, limit: limit)
9
+ get("tasks/new_index.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tasks/get_tasks"
4
+ require_relative "tasks/get_task_details"
5
+
6
+ module MangoApps
7
+ class Client
8
+ module Tasks
9
+ # Include all tasks sub-modules
10
+ include MangoApps::Client::Tasks::GetTasks
11
+ include MangoApps::Client::Tasks::GetTaskDetails
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Trackers
6
+ module GetTrackers
7
+ def get_trackers(page: 1, limit: 20, params: {})
8
+ params = params.merge(page: page, limit: limit)
9
+ get("v2/trackers.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "trackers/get_trackers"
4
+
5
+ module MangoApps
6
+ class Client
7
+ module Trackers
8
+ # Include all trackers sub-modules
9
+ include MangoApps::Client::Trackers::GetTrackers
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Users
6
+ def me(params = {})
7
+ get("v2/me.json", params: params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Wikis
6
+ module GetWikiDetails
7
+ def get_wiki_details(wiki_id, params = {})
8
+ get("v2/wikis/#{wiki_id}.json", params: params)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ class Client
5
+ module Wikis
6
+ module GetWikis
7
+ def get_wikis(mode: "my", page: 1, limit: 20, params: {})
8
+ params = params.merge(mode: mode, page: page, limit: limit)
9
+ get("v2/wikis.json", params: params)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wikis/get_wikis"
4
+ require_relative "wikis/get_wiki_details"
5
+
6
+ module MangoApps
7
+ class Client
8
+ module Wikis
9
+ # Include all wikis sub-modules
10
+ include MangoApps::Client::Wikis::GetWikis
11
+ include MangoApps::Client::Wikis::GetWikiDetails
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "oauth2"
4
+ require "json"
5
+ require "net/http"
6
+ require "uri"
7
+
8
+ module MangoApps
9
+ class OAuth
10
+ Discovery = Struct.new(:issuer, :authorization_endpoint, :token_endpoint, :userinfo_endpoint,
11
+ :end_session_endpoint, :jwks_uri)
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ end
16
+
17
+ # Discover OIDC endpoints from well-known config
18
+ def discover!
19
+ # Ensure we always use HTTPS for discovery
20
+ base_url = @config.base_url
21
+ base_url = base_url.gsub(/^http:/, 'https:') unless base_url.start_with?('https:')
22
+ url = URI.parse("#{base_url}/.well-known/openid-configuration")
23
+
24
+ begin
25
+ # Use proper HTTPS handling for discovery
26
+ http = Net::HTTP.new(url.host, url.port)
27
+ http.use_ssl = (url.scheme == 'https')
28
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl?
29
+
30
+ request = Net::HTTP::Get.new(url)
31
+ res = http.request(request)
32
+ raise MangoApps::DiscoveryError, "OIDC discovery failed: #{res.code}" unless res.is_a?(Net::HTTPSuccess)
33
+
34
+ data = JSON.parse(res.body)
35
+ validate_discovery_data(data)
36
+
37
+ @discovery = Discovery.new(
38
+ data["issuer"],
39
+ data["authorization_endpoint"],
40
+ data["token_endpoint"],
41
+ data["userinfo_endpoint"],
42
+ data["end_session_endpoint"],
43
+ data["jwks_uri"]
44
+ )
45
+ rescue JSON::ParserError => e
46
+ raise MangoApps::DiscoveryError, "Invalid JSON response from discovery endpoint: #{e.message}"
47
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error => e
48
+ raise MangoApps::DiscoveryError, "Failed to connect to discovery endpoint: #{e.message}"
49
+ rescue OpenSSL::SSL::SSLError => e
50
+ raise MangoApps::DiscoveryError, "SSL connection failed for discovery endpoint: #{e.message}"
51
+ end
52
+ end
53
+
54
+ def discovery
55
+ @discovery || discover!
56
+ end
57
+
58
+ # OAuth2::Client using discovered endpoints
59
+ def client
60
+ @client ||= ::OAuth2::Client.new(
61
+ @config.client_id,
62
+ @config.client_secret,
63
+ site: @config.base_url,
64
+ authorize_url: discovery.authorization_endpoint,
65
+ token_url: discovery.token_endpoint
66
+ )
67
+ end
68
+
69
+ # Build an auth URL for browser-based login (PKCE optional)
70
+ def authorization_url(state:, code_challenge: nil, code_challenge_method: "S256", extra_params: {})
71
+ params = {
72
+ redirect_uri: @config.redirect_uri,
73
+ scope: @config.scope,
74
+ state: state,
75
+ response_type: "code",
76
+ }.merge(extra_params)
77
+
78
+ if code_challenge
79
+ params[:code_challenge] = code_challenge
80
+ params[:code_challenge_method] = code_challenge_method
81
+ end
82
+
83
+ client.auth_code.authorize_url(params)
84
+ end
85
+
86
+ # Exchange code for tokens (PKCE verifier optional)
87
+ def get_token(authorization_code:, code_verifier: nil)
88
+ token = client.auth_code.get_token(
89
+ authorization_code,
90
+ redirect_uri: @config.redirect_uri,
91
+ headers: { "Content-Type" => "application/x-www-form-urlencoded" },
92
+ code_verifier: code_verifier
93
+ )
94
+ persist(token)
95
+ token
96
+ rescue OAuth2::Error => e
97
+ raise MangoApps::TokenExchangeError, "Token exchange failed: #{e.message}"
98
+ end
99
+
100
+ def refresh!(token)
101
+ raise MangoApps::TokenExpiredError, "No refresh_token available" unless token&.refresh_token
102
+
103
+ begin
104
+ new_token = token.refresh!
105
+ persist(new_token)
106
+ new_token
107
+ rescue OAuth2::Error => e
108
+ raise MangoApps::TokenExpiredError, "Token refresh failed: #{e.message}"
109
+ end
110
+ end
111
+
112
+ def load_token
113
+ @config.token_store&.load
114
+ end
115
+
116
+ # Get user info from OAuth userinfo endpoint
117
+ def get_userinfo(access_token)
118
+ raise MangoApps::TokenExpiredError, "No access token provided" unless access_token
119
+
120
+ userinfo_url = discovery.userinfo_endpoint
121
+ # Ensure userinfo endpoint uses HTTPS
122
+ userinfo_url = userinfo_url.gsub(/^http:/, 'https:') unless userinfo_url.start_with?('https:')
123
+ url = URI.parse(userinfo_url)
124
+
125
+ begin
126
+ # Ensure we use the correct port for HTTPS
127
+ port = url.port || (url.scheme == 'https' ? 443 : 80)
128
+ http = Net::HTTP.new(url.host, port)
129
+ http.use_ssl = (url.scheme == 'https')
130
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl?
131
+
132
+ request = Net::HTTP::Get.new(url)
133
+ request['Authorization'] = "Bearer #{access_token}"
134
+ request['Accept'] = 'application/json'
135
+
136
+ response = http.request(request)
137
+
138
+ # Handle redirects (301, 302)
139
+ if response.is_a?(Net::HTTPRedirection)
140
+ redirect_url = response['location']
141
+ if redirect_url
142
+ # Follow the redirect with HTTPS
143
+ redirect_uri = URI.parse(redirect_url)
144
+ redirect_uri.scheme = 'https' if redirect_uri.scheme == 'http'
145
+
146
+ http = Net::HTTP.new(redirect_uri.host, redirect_uri.port)
147
+ http.use_ssl = (redirect_uri.scheme == 'https')
148
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl?
149
+
150
+ request = Net::HTTP::Get.new(redirect_uri)
151
+ request['Authorization'] = "Bearer #{access_token}"
152
+ request['Accept'] = 'application/json'
153
+
154
+ response = http.request(request)
155
+ end
156
+ end
157
+
158
+ unless response.is_a?(Net::HTTPSuccess)
159
+ raise MangoApps::APIError, "Userinfo request failed: #{response.code} #{response.message}"
160
+ end
161
+
162
+ JSON.parse(response.body)
163
+ rescue JSON::ParserError => e
164
+ raise MangoApps::APIError, "Invalid JSON response from userinfo endpoint: #{e.message}"
165
+ rescue Net::ReadTimeout, Net::OpenTimeout, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Timeout::Error => e
166
+ raise MangoApps::APIError, "Failed to connect to userinfo endpoint: #{e.message}"
167
+ rescue OpenSSL::SSL::SSLError => e
168
+ raise MangoApps::APIError, "SSL connection failed for userinfo endpoint: #{e.message}"
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ def validate_discovery_data(data)
175
+ required_fields = %w[issuer authorization_endpoint token_endpoint]
176
+ missing_fields = required_fields - data.keys
177
+
178
+ if missing_fields.any?
179
+ raise MangoApps::DiscoveryError, "Missing required fields in discovery response: #{missing_fields.join(', ')}"
180
+ end
181
+ end
182
+
183
+ def persist(token)
184
+ @config.token_store&.save(token.to_hash)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ # Response wrapper that provides clean dot notation access to MangoApps API responses
5
+ # Abstracts away the ms_response wrapper and provides intuitive access patterns
6
+ class Response
7
+ include Enumerable
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ @ms_response = data["ms_response"] || data
12
+ end
13
+
14
+ # Delegate to ms_response for cleaner access
15
+ def method_missing(method_name, *args, &block)
16
+ if @ms_response.respond_to?(method_name)
17
+ @ms_response.public_send(method_name, *args, &block)
18
+ elsif @ms_response.is_a?(Hash) && @ms_response.key?(method_name.to_s)
19
+ value = @ms_response[method_name.to_s]
20
+ wrap_value(value)
21
+ elsif @ms_response.is_a?(Hash) && @ms_response.key?(method_name.to_sym)
22
+ value = @ms_response[method_name.to_sym]
23
+ wrap_value(value)
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def respond_to_missing?(method_name, include_private = false)
30
+ @ms_response.respond_to?(method_name, include_private) ||
31
+ (@ms_response.is_a?(Hash) &&
32
+ (@ms_response.key?(method_name.to_s) || @ms_response.key?(method_name.to_sym))) ||
33
+ super
34
+ end
35
+
36
+ # Array access for hash-like behavior
37
+ def [](key)
38
+ value = @ms_response[key]
39
+ wrap_value(value)
40
+ end
41
+
42
+ # Hash-like key access
43
+ def key?(key)
44
+ @ms_response.key?(key)
45
+ end
46
+
47
+ def keys
48
+ @ms_response.keys
49
+ end
50
+
51
+ def values
52
+ @ms_response.values.map { |v| wrap_value(v) }
53
+ end
54
+
55
+ # Enumerable support
56
+ def each(&block)
57
+ @ms_response.each(&block)
58
+ end
59
+
60
+ # Convert back to hash if needed
61
+ def to_h
62
+ @ms_response
63
+ end
64
+
65
+ def to_hash
66
+ @ms_response
67
+ end
68
+
69
+ # Pretty inspection
70
+ def inspect
71
+ "#<MangoApps::Response:#{object_id} @data=#{@ms_response.inspect}>"
72
+ end
73
+
74
+ # Access to raw data if needed
75
+ def raw_data
76
+ @data
77
+ end
78
+
79
+ private
80
+
81
+ def wrap_value(value)
82
+ case value
83
+ when Hash
84
+ Response.new(value)
85
+ when Array
86
+ value.map { |item| wrap_value(item) }
87
+ else
88
+ value
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MangoApps
4
+ VERSION = "0.15.2"
5
+ end
data/lib/mangoapps.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mangoapps/version"
4
+ require_relative "mangoapps/errors"
5
+ require_relative "mangoapps/config"
6
+ require_relative "mangoapps/oauth"
7
+ require_relative "mangoapps/response"
8
+ require_relative "mangoapps/client"
9
+ require_relative "mangoapps/modules/learn"
10
+ require_relative "mangoapps/modules/users"
11
+ require_relative "mangoapps/modules/recognitions"
12
+ require_relative "mangoapps/modules/notifications"
13
+ require_relative "mangoapps/modules/feeds"
14
+ require_relative "mangoapps/modules/posts"
15
+ require_relative "mangoapps/modules/libraries"
16
+ require_relative "mangoapps/modules/trackers"
17
+ require_relative "mangoapps/modules/attachments"
18
+ require_relative "mangoapps/modules/tasks"
19
+ require_relative "mangoapps/modules/wikis"
20
+
21
+ module MangoApps; end
22
+
23
+ # Mix in the resources
24
+ MangoApps::Client.include(MangoApps::Client::Learn)
25
+ MangoApps::Client.include(MangoApps::Client::Users)
26
+ MangoApps::Client.include(MangoApps::Client::Recognitions)
27
+ MangoApps::Client.include(MangoApps::Client::Notifications)
28
+ MangoApps::Client.include(MangoApps::Client::Feeds)
29
+ MangoApps::Client.include(MangoApps::Client::Posts)
30
+ MangoApps::Client.include(MangoApps::Client::Libraries)
31
+ MangoApps::Client.include(MangoApps::Client::Trackers)
32
+ MangoApps::Client.include(MangoApps::Client::Attachments)
33
+ MangoApps::Client.include(MangoApps::Client::Tasks)
34
+ MangoApps::Client.include(MangoApps::Client::Wikis)