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
data/README.md ADDED
@@ -0,0 +1,2040 @@
1
+ # MangoApps Ruby SDK
2
+
3
+ A clean, **real TDD** Ruby SDK for MangoApps APIs with OAuth2/OpenID Connect authentication. Features intuitive dot notation access, automatic response wrapping, and comprehensive real-world testing with actual MangoApps credentials.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿ” **OAuth2/OpenID Connect** authentication with automatic token refresh and userinfo endpoint
8
+ - ๐Ÿ”‘ **Internal API Authentication** - Alternative authentication using API keys for server-to-server communication
9
+ - ๐Ÿค– **Automatic Authentication Detection** - Automatically detects and prioritizes Internal API over OAuth credentials
10
+ - ๐Ÿ“„ **Pagination Support** - All list APIs support optional page and limit parameters for efficient data retrieval
11
+ - ๐Ÿš€ **Simple API** with intuitive method names and clean dot notation
12
+ - ๐Ÿงช **Real TDD** - no mocking, only actual OAuth testing
13
+ - ๐Ÿ”„ **Automatic retries** with exponential backoff
14
+ - ๐Ÿ“ **Comprehensive error handling** with specific exception types
15
+ - ๐Ÿ›ก๏ธ **Security-first** design with PKCE support and HTTPS enforcement
16
+ - ๐Ÿ”ง **Environment variable configuration** for secure credentials
17
+ - ๐Ÿ“š **Well-documented** with examples and guides
18
+ - โœจ **Clean Response API** - Automatic response wrapping with intuitive dot notation access
19
+ - ๐Ÿ”” **Notifications Module** - User priority items including requests, events, quizzes, surveys, tasks, and todos
20
+ - ๐Ÿ“ฐ **Feeds Module** - User activity feeds with unread counts and feed details
21
+ - ๐Ÿ“ **Posts Module** - Get all posts with filtering options and post management
22
+ - ๐Ÿ”’ **HTTPS Always** - All OAuth endpoints use HTTPS with automatic redirect handling
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem "mangoapps-ex-sdk-ruby"
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ ```bash
35
+ bundle install
36
+ ```
37
+
38
+ Or install it directly:
39
+
40
+ ```bash
41
+ gem install mangoapps-ex-sdk-ruby
42
+ ```
43
+
44
+ **Current Version**: 0.15.2 (includes OAuth userinfo endpoint, HTTPS security enhancements, Internal API authentication, automatic authentication detection, and pagination support)
45
+
46
+ ## Quick Start
47
+
48
+ ### 1. Environment Setup
49
+
50
+ For testing purposes, create a `.env` file with your MangoApps credentials:
51
+
52
+ ```bash
53
+ # .env (for testing only)
54
+ MANGOAPPS_DOMAIN=yourdomain.mangoapps.com
55
+
56
+ # OAuth2 Authentication (Option 1)
57
+ MANGOAPPS_CLIENT_ID=your_client_id_here
58
+ MANGOAPPS_CLIENT_SECRET=your_client_secret_here
59
+ MANGOAPPS_REDIRECT_URI=https://localhost:3000/oauth/callback
60
+ MANGOAPPS_SCOPE=openid profile email
61
+
62
+ # Internal API Authentication (Option 2 - Alternative to OAuth)
63
+ # Contact MangoApps support team to get your Internal API Key and Secret
64
+ MANGOAPPS_INTERNAL_API_KEY=824938b1a5ea1c29d472ba79254d68b62ce8e5df
65
+ MANGOAPPS_INTERNAL_API_SECRET=387103e2c91779d3bafb1a713bfa99a5
66
+ MANGOAPPS_TEST_HEADER=test1111
67
+ ```
68
+
69
+ **Note**: The `.env` file is only used for testing. In production, you should handle token storage and management according to your application's security requirements.
70
+
71
+ ### 2. Rails Integration
72
+
73
+ For Rails applications, see our comprehensive [Rails OAuth Guide](RAILS_OAUTH_GUIDE.md) which includes:
74
+
75
+ - Simple OAuth 2.0 flow implementation
76
+ - Token storage and management
77
+ - OAuth userinfo endpoint integration
78
+ - HTTPS security enforcement
79
+ - Complete callback handling
80
+
81
+ ### 3. Configuration
82
+
83
+ ```ruby
84
+ require "mangoapps"
85
+
86
+ # For testing - automatically loads from .env file and detects authentication method
87
+ config = MangoApps::Config.new
88
+ # Output: ๐Ÿ”‘ Using Internal API authentication (if internal API credentials found)
89
+ # Output: ๐Ÿ” Using OAuth2 authentication (if OAuth credentials found)
90
+
91
+ # For production - OAuth2 configuration
92
+ config = MangoApps::Config.new(
93
+ domain: "yourdomain.mangoapps.com",
94
+ client_id: "your_client_id",
95
+ client_secret: "your_client_secret",
96
+ redirect_uri: "https://localhost:3000/oauth/callback",
97
+ scope: "openid profile email"
98
+ )
99
+ client = MangoApps::Client.new(config)
100
+
101
+ # For production - Internal API configuration (Alternative to OAuth)
102
+ # Note: Contact MangoApps support team to get your Internal API Key and Secret
103
+ config = MangoApps::Config.new(
104
+ domain: "yourdomain.mangoapps.com",
105
+ internal_api_key: "824938b1a5ea1c29d472ba79254d68b62ce8e5df",
106
+ internal_api_secret: "387103e2c91779d3bafb1a713bfa99a5",
107
+ test_header: "test1111" # Optional
108
+ )
109
+ client = MangoApps::Client.new(config)
110
+ ```
111
+
112
+ ### 3. OAuth Authentication
113
+
114
+ #### Quick Start (Recommended)
115
+ ```bash
116
+ # Get OAuth token (interactive)
117
+ ./run_auth.sh
118
+
119
+ # Run tests to verify everything works
120
+ ./run_tests.sh
121
+ ```
122
+
123
+ #### Basic Usage Example
124
+ ```ruby
125
+ require 'mangoapps-ex-sdk-ruby'
126
+
127
+ # Initialize client
128
+ client = MangoApps::Client.new
129
+
130
+ # Get user profile
131
+ user = client.me
132
+ puts "Hello, #{user.name}!"
133
+
134
+ # Get OAuth user info (from OAuth userinfo endpoint)
135
+ userinfo = client.get_userinfo
136
+ puts "OAuth User: #{userinfo.name} (#{userinfo.email})"
137
+ puts "Username: #{userinfo.preferred_username}"
138
+ puts "Subject ID: #{userinfo.sub}"
139
+
140
+ # Get priority items
141
+ priority_items = client.my_priority_items
142
+ puts "You have #{priority_items.data.length} priority items:"
143
+ priority_items.data.each do |item|
144
+ puts " โ€ข #{item.title}: #{item.count} pending"
145
+ end
146
+
147
+ # Get available courses
148
+ courses = client.course_catalog
149
+ puts "Available courses: #{courses.courses.length}"
150
+
151
+ # Get activity feeds
152
+ feeds = client.feeds
153
+ puts "Activity feeds: #{feeds.feeds.length} (unread: #{feeds.unread_counts.unread_feeds_count})"
154
+
155
+ # Get all posts
156
+ posts = client.get_all_posts(filter_by: "all")
157
+ puts "All posts: #{posts.feeds.length}"
158
+
159
+ # Get post details by ID
160
+ if posts.feeds.any?
161
+ post_id = posts.feeds.first.post_id
162
+ post_details = client.get_post_by_id(post_id, full_description: "Y")
163
+ puts "Post details: #{post_details.post.title}"
164
+ end
165
+
166
+ # Get user libraries
167
+ libraries = client.get_libraries
168
+ puts "User libraries: #{libraries.libraries.length}"
169
+
170
+ # Get library categories by ID
171
+ if libraries.libraries.any?
172
+ library_id = libraries.libraries.first.id
173
+ library_details = client.get_library_categories(library_id)
174
+ puts "Library details: #{library_details.library.name}"
175
+
176
+ # Get library items from first category
177
+ if library_details.library.categories.any?
178
+ category_id = library_details.library.categories.first.id
179
+ library_items = client.get_library_items(library_id, category_id)
180
+ puts "Library items: #{library_items.library_items.length} items in #{library_items.category_name}"
181
+ end
182
+ end
183
+
184
+ # Get user trackers
185
+ trackers = client.get_trackers
186
+ puts "User trackers: #{trackers.trackers.length}"
187
+
188
+ # Get user folders
189
+ folders = client.get_folders
190
+ puts "User folders: #{folders.folders.length}"
191
+
192
+ # Get files from first folder with content
193
+ if folders.folders.any?
194
+ folder_with_content = folders.folders.find { |f| f.child_count.to_i > 0 }
195
+ if folder_with_content
196
+ folder_files = client.get_folder_files(folder_with_content.id, include_folders: "Y")
197
+ puts "Folder files: #{folder_files.files.length} items in #{folder_files.name}"
198
+ end
199
+ end
200
+
201
+ # Get user tasks
202
+ tasks = client.get_tasks(filter: "Pending_Tasks", page: 1, limit: 3)
203
+ puts "User tasks: #{tasks.tasks.task.length}"
204
+
205
+ # Get detailed information for a specific task
206
+ if tasks.tasks.task.any?
207
+ first_task = tasks.tasks.task.first
208
+ task_id = first_task.is_a?(Array) ? first_task[1] : first_task.id
209
+ task_details = client.get_task_details(task_id)
210
+ puts "Task details: #{task_details.task.task_title} (Status: #{task_details.task.status})"
211
+ end
212
+
213
+ # Get user wikis
214
+ wikis = client.get_wikis(mode: "my", limit: 5, offset: 0)
215
+ puts "User wikis: #{wikis.wikis.length}"
216
+
217
+ # Get detailed information for the first wiki
218
+ if wikis.wikis.any?
219
+ first_wiki = wikis.wikis.first
220
+ wiki_details = client.get_wiki_details(first_wiki.id)
221
+ puts "Wiki details: #{wiki_details.wiki.details.title} (Read count: #{wiki_details.wiki.details.total_read_count})"
222
+ end
223
+ ```
224
+
225
+ #### Manual OAuth Flow
226
+ ```ruby
227
+ # Generate authorization URL
228
+ state = SecureRandom.hex(16)
229
+ auth_url = client.authorization_url(state: state)
230
+ puts "Open this URL to authorize: #{auth_url}"
231
+
232
+ # After user authorizes, exchange code for tokens
233
+ tokens = client.authenticate!(authorization_code: params[:code])
234
+
235
+ # Store tokens securely in your application
236
+ # (implementation depends on your storage solution)
237
+ store_tokens(tokens.access_token, tokens.refresh_token)
238
+
239
+ # Now you can make API calls with clean dot notation
240
+ user = client.me
241
+ puts "Welcome, #{user.user_profile.minimal_profile.name}!"
242
+
243
+ # Get OAuth user info from userinfo endpoint
244
+ userinfo = client.get_userinfo
245
+ puts "OAuth User: #{userinfo.name} (#{userinfo.email})"
246
+ puts "Subject ID: #{userinfo.sub}"
247
+ puts "Username: #{userinfo.preferred_username}"
248
+ ```
249
+
250
+ #### OAuth Userinfo Endpoint
251
+
252
+ The SDK provides access to the OAuth userinfo endpoint for getting authenticated user information:
253
+
254
+ ```ruby
255
+ # Get user info from OAuth userinfo endpoint
256
+ userinfo = client.get_userinfo
257
+
258
+ # Access OAuth user data with clean dot notation
259
+ puts "Name: #{userinfo.name}"
260
+ puts "Email: #{userinfo.email}"
261
+ puts "Subject ID: #{userinfo.sub}"
262
+ puts "Username: #{userinfo.preferred_username}"
263
+
264
+ # Available OAuth userinfo fields:
265
+ # - sub: Subject identifier (unique user ID)
266
+ # - name: Full name
267
+ # - email: Email address
268
+ # - preferred_username: Username
269
+ # - email_verified: Email verification status
270
+ # - locale: User locale
271
+ # - zoneinfo: Timezone information
272
+ ```
273
+
274
+ #### HTTPS Security Features
275
+
276
+ The SDK enforces HTTPS for all OAuth operations:
277
+
278
+ ```ruby
279
+ # Automatic HTTPS enforcement
280
+ # - Discovery endpoints always use HTTPS
281
+ # - Userinfo endpoints always use HTTPS
282
+ # - Automatic redirect handling (301/302)
283
+ # - SSL certificate verification
284
+
285
+ # Example: Even if discovery returns HTTP URLs, they're converted to HTTPS
286
+ config = MangoApps::Config.new(domain: "example.mangoapps.com")
287
+ client = MangoApps::Client.new(config)
288
+
289
+ # This will automatically use HTTPS
290
+ userinfo = client.get_userinfo
291
+ ```
292
+
293
+ ### 4. Automatic Authentication Detection
294
+
295
+ The SDK automatically detects and prioritizes authentication methods:
296
+
297
+ 1. **Internal API Credentials** (highest priority) - If `MANGOAPPS_INTERNAL_API_KEY` and `MANGOAPPS_INTERNAL_API_SECRET` are found
298
+ 2. **OAuth Credentials** (fallback) - If `MANGOAPPS_CLIENT_ID` and `MANGOAPPS_CLIENT_SECRET` are found
299
+
300
+ ```ruby
301
+ # Automatic detection - no configuration needed
302
+ config = MangoApps::Config.new
303
+ # Shows: ๐Ÿ”‘ Using Internal API authentication (if internal API found)
304
+ # Shows: ๐Ÿ” Using OAuth2 authentication (if OAuth found)
305
+
306
+ client = MangoApps::Client.new(config)
307
+ # All API calls work automatically with detected authentication method
308
+ ```
309
+
310
+ ### 5. Pagination Support
311
+
312
+ All list APIs support optional pagination parameters for efficient data retrieval:
313
+
314
+ ```ruby
315
+ # Basic usage with default pagination
316
+ posts = client.get_all_posts
317
+ courses = client.course_catalog
318
+
319
+ # Pagination with custom page and limit
320
+ posts = client.get_all_posts(page: 2, limit: 10)
321
+ courses = client.course_catalog(page: 1, limit: 5)
322
+
323
+ # Filtering with pagination
324
+ posts = client.get_all_posts(filter_by: "all", page: 1, limit: 20)
325
+ tasks = client.get_tasks(filter: "Pending_Tasks", page: 1, limit: 10)
326
+
327
+ # Library items with pagination
328
+ items = client.get_library_items(library_id, category_id, page: 1, limit: 15)
329
+ ```
330
+
331
+ **Default Pagination Values:**
332
+ - Most APIs: `page: 1, limit: 20`
333
+ - Tasks: `page: 1, limit: 5` (smaller default for task management)
334
+ - Posts: `page: 1, limit: 20` with `filter_by: "all"`
335
+
336
+ ### 6. Internal API Authentication (Alternative to OAuth)
337
+
338
+ For applications that need direct API access without OAuth flow, you can use internal API authentication:
339
+
340
+ > **๐Ÿ“ž Getting API Credentials**: Contact the MangoApps support team to obtain your Internal API Key and Secret for server-to-server authentication.
341
+
342
+ ```ruby
343
+ # Configure with internal API credentials
344
+ config = MangoApps::Config.new(
345
+ domain: "yourdomain.mangoapps.com",
346
+ internal_api_key: "824938b1a5ea1c29d472ba79254d68b62ce8e5df",
347
+ internal_api_secret: "387103e2c91779d3bafb1a713bfa99a5",
348
+ )
349
+ client = MangoApps::Client.new(config)
350
+
351
+ # Use the SDK normally - authentication is handled automatically
352
+ user = client.me
353
+ puts "Hello, #{user.user_profile.minimal_profile.name}!"
354
+
355
+ # Get user info (uses /me endpoint for internal API)
356
+ userinfo = client.get_userinfo
357
+ puts "User: #{userinfo.name} (#{userinfo.email})"
358
+
359
+ # All other API calls work the same way
360
+ posts = client.get_all_posts
361
+ notifications = client.notifications
362
+ ```
363
+
364
+ #### Internal API Headers
365
+
366
+ The SDK automatically adds the required headers for internal API authentication:
367
+
368
+ ```ruby
369
+ # These headers are automatically added to all requests:
370
+ # - Internal-Api-Key: your_api_key
371
+ # - Internal-Api-Secret: your_api_secret
372
+ # - Test: your_test_header (if provided)
373
+ ```
374
+
375
+ #### When to Use Internal API vs OAuth
376
+
377
+ - **OAuth2**: Use for user-facing applications where users need to authenticate
378
+ - **Internal API**: Use for server-to-server communication, automation, or internal tools
379
+
380
+ ## Response Format
381
+
382
+ The SDK automatically wraps all API responses in a `MangoApps::Response` object that provides clean dot notation access:
383
+
384
+ ```ruby
385
+ # Clean dot notation access (automatic response wrapping):
386
+ user = client.me
387
+ name = user.user_profile.minimal_profile.name
388
+ email = user.user_profile.minimal_profile.email
389
+ ```
390
+
391
+ ### Response Features
392
+
393
+ - **๐ŸŽฏ Dot Notation Access**: `response.user.name` for clean, intuitive API access
394
+ - **๐Ÿ”„ Automatic Wrapping**: All responses are automatically wrapped in `MangoApps::Response`
395
+ - **๐Ÿ”— Hash Compatibility**: Still supports `[]` access if needed
396
+ - **๐Ÿ“Š Enumerable Support**: Arrays and hashes work as expected
397
+ - **๐Ÿ” Raw Data Access**: Use `response.raw_data` for original response
398
+ - **โšก Type Safety**: Better IDE support and autocomplete
399
+ - **๐ŸŽจ Clean Code**: No more verbose nested hash access
400
+
401
+ ## API Resources
402
+
403
+ ### Users Module
404
+
405
+ ```ruby
406
+ # Get current user profile
407
+ user = client.me
408
+
409
+ # Access user data with clean dot notation
410
+ puts "User: #{user.user_profile.minimal_profile.name}"
411
+ puts "Email: #{user.user_profile.minimal_profile.email}"
412
+ puts "Points: #{user.user_profile.gamification.total_points}"
413
+ puts "Followers: #{user.user_profile.user_data.followers}"
414
+ ```
415
+
416
+ ### Learn Module
417
+
418
+ #### Course Catalog
419
+ ```ruby
420
+ # Get course catalog
421
+ courses = client.course_catalog
422
+
423
+ # Access course data with clean dot notation
424
+ courses.courses.each do |course|
425
+ puts "#{course.name} - #{course.course_type}"
426
+ end
427
+ ```
428
+
429
+ #### Course Categories
430
+ ```ruby
431
+ # Get all course categories
432
+ categories = client.course_categories
433
+
434
+ # Access category data with clean dot notation
435
+ categories.all_categories.each do |category|
436
+ puts "#{category.name} - Position: #{category.position}"
437
+ end
438
+
439
+ # Get specific category details
440
+ category = client.course_category(category_id)
441
+ ```
442
+
443
+ #### Course Details
444
+ ```ruby
445
+ # Get detailed course information by course ID
446
+ course_id = 604
447
+ course = client.course_details(course_id)
448
+
449
+ # Access course data with clean dot notation
450
+ course_data = course.course
451
+ puts "Course: #{course_data.name} (ID: #{course_data.id})"
452
+ puts "Description: #{course_data.description}"
453
+ puts "Type: #{course_data.course_type}"
454
+ puts "Delivery Mode: #{course_data.delivery_mode}"
455
+ puts "Instructors: #{course_data.instructors.length}"
456
+ puts "Fields: #{course_data.fields.length}"
457
+
458
+ # Access course URLs
459
+ puts "Start Course URL: #{course_data.start_course_url}"
460
+ puts "Go to Course URL: #{course_data.goto_course_url}"
461
+
462
+ # Access course fields (details, certification, etc.)
463
+ course_data.fields.each do |field|
464
+ puts "Field: #{field.field_name}"
465
+ field.course_sub_fields.each do |sub_field|
466
+ puts " #{sub_field.field_name}: #{sub_field.field_value}"
467
+ end
468
+ end
469
+ ```
470
+
471
+ #### My Learning
472
+ ```ruby
473
+ # Get user's learning progress and courses
474
+ learning = client.my_learning
475
+
476
+ # Access user learning data with clean dot notation
477
+ puts "User: #{learning.user_name} (ID: #{learning.user_id})"
478
+ puts "Total training time: #{learning.total_training_time}"
479
+ puts "Ongoing courses: #{learning.ongoing_course_count}"
480
+ puts "Completed courses: #{learning.completed_course_count}"
481
+ puts "Registered courses: #{learning.registered_course_count}"
482
+
483
+ # Access learning sections
484
+ learning.section.each do |section|
485
+ puts "#{section.label} - #{section.count} courses"
486
+
487
+ # Access courses in each section
488
+ section.courses.each do |course|
489
+ puts " ๐Ÿ“š #{course.name} - #{course.course_progress}% progress"
490
+ end
491
+ end
492
+ ```
493
+
494
+ ### Recognitions Module
495
+
496
+ #### Award Categories
497
+ ```ruby
498
+ # Get award categories
499
+ categories = client.award_categories
500
+
501
+ # Access award category data with clean dot notation
502
+ categories.award_categories.each do |category|
503
+ puts "#{category.name} (ID: #{category.id}) - Permission: #{category.recipient_permission}"
504
+ end
505
+ ```
506
+
507
+ #### Core Value Tags
508
+ ```ruby
509
+ # Get core value tags
510
+ tags = client.core_value_tags
511
+
512
+ # Access core value tag data with clean dot notation
513
+ tags.core_value_tags.each do |tag|
514
+ puts "#{tag.name} (ID: #{tag.id}) - Color: ##{tag.color}"
515
+ end
516
+ ```
517
+
518
+ #### Leaderboard Info
519
+ ```ruby
520
+ # Get leaderboard information
521
+ leaderboard = client.leaderboard_info
522
+
523
+ # Check if leaderboard data is available
524
+ if leaderboard.leaderboard_info
525
+ # Access user leaderboard with clean dot notation
526
+ leaderboard.leaderboard_info.user_info.each do |user|
527
+ puts "๐Ÿ… #{user.name} (Rank: #{user.rank}) - Awards: #{user.award_count}"
528
+ end
529
+
530
+ # Access team leaderboard with clean dot notation
531
+ leaderboard.leaderboard_info.team_info.each do |team|
532
+ puts "๐Ÿ† #{team.name} (Rank: #{team.rank}) - Awards: #{team.award_count}"
533
+ end
534
+ else
535
+ puts "No leaderboard data configured"
536
+ end
537
+ ```
538
+
539
+ #### Tango Gift Cards
540
+ ```ruby
541
+ # Get tango gift cards information
542
+ gift_cards = client.tango_gift_cards
543
+
544
+ # Access gift card data with clean dot notation
545
+ puts "Available points: #{gift_cards.tango_cards.available_points}"
546
+ puts "Terms: #{gift_cards.tango_cards.terms}"
547
+ ```
548
+
549
+ #### Gift Cards
550
+ ```ruby
551
+ # Get gift cards information
552
+ gift_cards = client.gift_cards
553
+
554
+ # Access gift card data with clean dot notation
555
+ gift_cards.cards.each do |card|
556
+ puts "#{card.brand_name} (Key: #{card.brand_key}) - Enabled: #{card.enabled}"
557
+ end
558
+ ```
559
+
560
+ #### Get Awards List
561
+ ```ruby
562
+ # Get awards list for a specific category
563
+ response = client.get_awards_list(category_id: 4303)
564
+
565
+ # Access award data with clean dot notation
566
+ response.get_awards_list.each do |award|
567
+ puts "#{award.name} (ID: #{award.id})"
568
+ puts " Description: #{award.description}"
569
+ puts " Points: #{award.points}"
570
+ puts " Reward Points: #{award.reward_points}"
571
+ puts " Image: #{award.attachment_url}"
572
+ end
573
+ ```
574
+
575
+ #### Get Profile Awards
576
+ ```ruby
577
+ # Get user profile awards
578
+ response = client.get_profile_awards
579
+
580
+ # Access core value tags with counts
581
+ response.core_value_tags.each do |tag|
582
+ puts "#{tag.name} (ID: #{tag.id}) - Count: #{tag.count}"
583
+ end
584
+
585
+ # Access award feeds
586
+ response.feeds.each do |feed|
587
+ puts "#{feed.feed_property.title} - Points: #{feed.recognition_points}"
588
+ puts "From: #{feed.from_user.name}"
589
+ puts "Body: #{feed.body}"
590
+ end
591
+ ```
592
+
593
+ #### Get Team Awards
594
+ ```ruby
595
+ # Get team awards for a specific project/team
596
+ response = client.get_team_awards(project_id: 117747)
597
+
598
+ # Access team core value tags with counts
599
+ response.core_value_tags.each do |tag|
600
+ puts "#{tag.name} (ID: #{tag.id}) - Count: #{tag.count}"
601
+ end
602
+
603
+ # Access team award feeds
604
+ response.feeds.each do |feed|
605
+ puts "#{feed.feed_property.title} - Points: #{feed.recognition_points}"
606
+ puts "From: #{feed.from_user.name}"
607
+ puts "Team: #{feed.group_name} (ID: #{feed.group_id})"
608
+ puts "Body: #{feed.body}"
609
+ end
610
+ ```
611
+
612
+ #### My Priority Items
613
+ ```ruby
614
+ # Get user's priority items
615
+ response = client.my_priority_items
616
+
617
+ # Access priority items data
618
+ response.data.each do |item|
619
+ puts "#{item.title} (ID: #{item.id}) - Count: #{item.count}"
620
+ puts " Action Type: #{item.action_type}"
621
+ puts " Icon: #{item.icon} (#{item.icon_color})"
622
+ puts " Details: #{item.info_details}"
623
+ end
624
+
625
+ # Check response status
626
+ puts "Success: #{response.success}"
627
+ puts "Display Type: #{response.display_type}"
628
+ ```
629
+
630
+ #### Feeds
631
+ ```ruby
632
+ # Get user's activity feeds
633
+ response = client.feeds
634
+
635
+ # Access feeds data
636
+ response.feeds.each do |feed|
637
+ puts "#{feed.feed_property.title} (ID: #{feed.id})"
638
+ puts " From: #{feed.from_user.name} | Group: #{feed.group_name}"
639
+ puts " Type: #{feed.feed_type} | Category: #{feed.category}"
640
+ puts " Created: #{Time.at(feed.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
641
+ puts " Unread: #{feed.unread}"
642
+ puts " Body: #{feed.body[0..100]}..."
643
+ end
644
+
645
+ # Access unread counts
646
+ puts "Unread feeds: #{response.unread_counts.unread_feeds_count}"
647
+ puts "Direct messages: #{response.unread_counts.direct_messages_count}"
648
+ puts "What's new: #{response.unread_counts.whats_new_count}"
649
+
650
+ # Check response metadata
651
+ puts "Limit: #{response.limit} | Version: #{response.mangoapps_version}"
652
+ ```
653
+
654
+ #### Get All Posts
655
+ ```ruby
656
+ # Get all posts with filtering
657
+ response = client.get_all_posts(filter_by: "all")
658
+
659
+ # Access posts data
660
+ response.feeds.each do |post|
661
+ puts "#{post.tile.tile_name} (ID: #{post.id})"
662
+ puts " From: #{post.from_user.name} | Group: #{post.group_name}"
663
+ puts " Post ID: #{post.post_id} | View count: #{post.total_view_count}"
664
+ puts " Created: #{Time.at(post.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
665
+ puts " Comments: #{post.comments.length} | Likes: #{post.like_count}"
666
+ puts " Content: #{post.tile.tile_content[0..100]}..."
667
+ end
668
+
669
+ # Access post configuration
670
+ puts "Post view count visibility: #{response.post_view_count_visibility}"
671
+ puts "Post view count link config: #{response.post_view_count_link_config}"
672
+ ```
673
+
674
+ #### Get Post By ID
675
+ ```ruby
676
+ # Get detailed post information by ID
677
+ post_id = 59101
678
+ response = client.get_post_by_id(post_id, full_description: "Y")
679
+
680
+ # Access post details
681
+ post = response.post
682
+ puts "#{post.title} (ID: #{post.id})"
683
+ puts " Created by: #{post.created_name} (ID: #{post.creator_by})"
684
+ puts " Created: #{Time.at(post.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
685
+ puts " Conversation: #{post.conversation_name}"
686
+ puts " View count: #{post.total_view_count} | Likes: #{post.like_count} | Comments: #{post.comment_count}"
687
+
688
+ # Access tile information
689
+ if post.tile
690
+ puts " Tile: #{post.tile.tile_name}"
691
+ puts " Full description: #{post.tile.tile_full_description[0..200]}..."
692
+ puts " Image: #{post.tile.tile_image}"
693
+ end
694
+
695
+ # Check post permissions
696
+ puts " Can edit: #{post.can_edit} | Can comment: #{post.can_comment} | Can delete: #{post.can_delete}"
697
+ puts " Is draft: #{post.is_draft} | Archived: #{post.archived}"
698
+ ```
699
+
700
+ #### Libraries Management
701
+ ```ruby
702
+ # Get user's document libraries
703
+ libraries = client.get_libraries
704
+
705
+ # Access libraries data
706
+ puts "๐Ÿ“š User Libraries:"
707
+ libraries.libraries.each do |library|
708
+ puts " โ€ข #{library.name} (ID: #{library.id})"
709
+ puts " Type: #{library.library_type} | View: #{library.view_mode}"
710
+ puts " Items: #{library.total_items_count} | Categories: #{library.categories.length}"
711
+ puts " Edit access: #{library.edit_access} | Position: #{library.position}"
712
+ puts " Banner: #{library.banner_color} | Icon: #{library.icon_properties.color}"
713
+
714
+ # Access library categories
715
+ if library.categories.any?
716
+ puts " Categories:"
717
+ library.categories.first(3).each do |category|
718
+ puts " - #{category.name} (#{category.library_items_count} items)"
719
+ end
720
+ end
721
+ puts ""
722
+ end
723
+ ```
724
+
725
+ #### Get Library Categories
726
+ ```ruby
727
+ # Get detailed library information and categories by library ID
728
+ library_id = 9776
729
+ response = client.get_library_categories(library_id)
730
+
731
+ # Access library details
732
+ library = response.library
733
+ puts "#{library.name} (ID: #{library.id})"
734
+ puts " Type: #{library.library_type} | View: #{library.view_mode}"
735
+ puts " Description: #{library.description}"
736
+ puts " Total items: #{library.total_items_count} | Categories: #{library.categories.length}"
737
+ puts " Edit access: #{library.edit_access} | Position: #{library.position}"
738
+
739
+ # Access library properties
740
+ puts " Banner color: #{library.banner_color} | Icon color enabled: #{library.enable_icon_color}"
741
+ puts " Created: #{Time.at(library.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
742
+ puts " Updated: #{Time.at(library.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
743
+
744
+ # Access icon properties
745
+ if library.icon_properties
746
+ puts " Icon color: #{library.icon_properties.color} | Icon class: #{library.icon_properties.class}"
747
+ end
748
+
749
+ # Access categories
750
+ if library.categories.any?
751
+ puts " Categories:"
752
+ library.categories.each do |category|
753
+ puts " โ€ข #{category.name} (ID: #{category.id})"
754
+ puts " Items: #{category.library_items_count} | Rank: #{category.rank}"
755
+ puts " Is system: #{category.is_system} | Icon: #{category.icon || 'None'}"
756
+ if category.description
757
+ puts " Description: #{category.description[0..100]}..."
758
+ end
759
+ end
760
+ end
761
+ ```
762
+
763
+ #### Get Library Items
764
+ ```ruby
765
+ # Get library items by library ID and category ID
766
+ library_id = 9776
767
+ category_id = 50114
768
+ response = client.get_library_items(library_id, category_id)
769
+
770
+ # Access library items data
771
+ puts "Category: #{response.category_name}"
772
+ puts "View mode: #{response.view_mode} | Library type: #{response.library_type}"
773
+ puts "Library ID: #{response.library_id} | Can add: #{response.can_add}"
774
+ puts "Icon color: #{response.enable_icon_color}"
775
+
776
+ # Access library items
777
+ if response.library_items.any?
778
+ puts "Library Items:"
779
+ response.library_items.each do |item|
780
+ puts " โ€ข #{item.name} (ID: #{item.id})"
781
+ puts " Link type: #{item.link_type} | Link: #{item.link[0..50]}..."
782
+
783
+ # Handle different link types
784
+ if item.link_type == "ExternalLink"
785
+ if item.icon_properties
786
+ puts " Icon: #{item.icon_properties.color} | Class: #{item.icon_properties.class}"
787
+ end
788
+ elsif item.link_type == "Attachment"
789
+ puts " Attachment ID: #{item.attachment_id} | File type: #{item.file_type}"
790
+ puts " Likes: #{item.likes_count} | Is liked: #{item.is_liked}"
791
+ puts " Image: #{item.image_url}"
792
+ puts " Short URL: #{item.short_url[0..50]}..."
793
+ end
794
+ end
795
+ else
796
+ puts "No library items found"
797
+ end
798
+ ```
799
+
800
+ #### Trackers Management
801
+ ```ruby
802
+ # Get user's trackers
803
+ trackers = client.get_trackers
804
+
805
+ # Access trackers data
806
+ puts "๐Ÿ“Š User Trackers:"
807
+ puts " Total trackers: #{trackers.trackers.length}"
808
+ puts " Transaction ID: #{trackers.transaction_id || 'None'}"
809
+ puts ""
810
+
811
+ # Display recent trackers
812
+ puts "๐Ÿ“Š Recent Trackers:"
813
+ trackers.trackers.first(5).each do |tracker|
814
+ puts " โ€ข #{tracker.name} (ID: #{tracker.id})"
815
+ puts " Last submission: #{Time.at(tracker.last_submission_date.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
816
+ puts " Conversation: #{tracker.conversation_name} (ID: #{tracker.conversation_id})"
817
+ puts " Pinned: #{tracker.is_pinned} | Can share: #{tracker.can_share}"
818
+
819
+ # Access tracker icon info
820
+ if tracker.tracker_icon_info
821
+ puts " Icon: #{tracker.tracker_icon_info.color_code} | Class: #{tracker.tracker_icon_info.icon_url}"
822
+ end
823
+
824
+ puts " MLink: #{tracker.mlink[0..50]}..."
825
+ puts ""
826
+ end
827
+ ```
828
+
829
+ #### Attachments Management
830
+ ```ruby
831
+ # Get user's folders
832
+ folders = client.get_folders
833
+
834
+ # Access folders data
835
+ puts "๐Ÿ“ User Folders:"
836
+ puts " Total folders: #{folders.folders.length}"
837
+ puts " Transaction ID: #{folders.transaction_id || 'None'}"
838
+ puts ""
839
+
840
+ # Display folders
841
+ puts "๐Ÿ“ Available Folders:"
842
+ folders.folders.each do |folder|
843
+ puts " โ€ข #{folder.name} (ID: #{folder.id})"
844
+ puts " Path: #{folder.relativePath}"
845
+ puts " Child count: #{folder.child_count} | Can save: #{folder.can_save}"
846
+ puts " Pinned: #{folder.is_pinned} | Virtual: #{folder.is_virtual_folder}"
847
+ puts " Folder rel: #{folder.folder_rel} | Type: #{folder.folder_type_from_db || 'None'}"
848
+ puts " Show in upload: #{folder.show_in_upload} | Show in move: #{folder.show_in_move}"
849
+ puts " Filter: #{folder.filter} | Show permissions: #{folder.show_permission_options}"
850
+
851
+ # Show conversation and user IDs if available
852
+ if folder.conversation_id
853
+ puts " Conversation ID: #{folder.conversation_id}"
854
+ end
855
+ if folder.user_id
856
+ puts " User ID: #{folder.user_id}"
857
+ end
858
+
859
+ puts ""
860
+ end
861
+ ```
862
+
863
+ #### File and Folder Management
864
+ ```ruby
865
+ # Get user's folders first
866
+ folders = client.get_folders
867
+
868
+ # Find a folder with content
869
+ if folders.folders.any?
870
+ folder = folders.folders.find { |f| f.child_count.to_i > 0 }
871
+
872
+ if folder
873
+ puts "๐Ÿ“ Exploring folder: #{folder.name} (ID: #{folder.id})"
874
+
875
+ # Get files and folders inside this folder
876
+ folder_contents = client.get_folder_files(folder.id, include_folders: "Y")
877
+
878
+ puts "๐Ÿ“ Folder Contents:"
879
+ puts " Folder name: #{folder_contents.name}"
880
+ puts " Total count: #{folder_contents.total_count}"
881
+ puts " Role: #{folder_contents.role_name}"
882
+ puts " Domain suspended: #{folder_contents.is_domain_suspended}"
883
+ puts " Show in upload: #{folder_contents.show_in_upload}"
884
+ puts ""
885
+
886
+ # Display files and folders
887
+ puts "๐Ÿ“ Files and Folders:"
888
+ folder_contents.files.first(10).each do |item|
889
+ puts " โ€ข #{item.filename} (ID: #{item.id})"
890
+ puts " Type: #{item.is_folder ? 'Folder' : 'File'}"
891
+ puts " Size: #{item.size} bytes"
892
+ puts " Uploader: #{item.uploader_name} (ID: #{item.user_id})"
893
+ puts " Updated: #{Time.at(item.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
894
+ puts " Visibility: #{item.visibility} | Privacy: #{item.privacy_type}"
895
+ puts " Pinned: #{item.is_pinned} | Liked: #{item.is_liked}"
896
+ puts " Can save: #{item.can_save} | Show permissions: #{item.show_permission_options}"
897
+
898
+ # Show role permissions
899
+ if item.role
900
+ puts " Permissions: Edit: #{item.role.can_edit} | Share: #{item.role.can_share} | Restore: #{item.role.can_restore}"
901
+ end
902
+
903
+ # Show links
904
+ if item.mLink
905
+ puts " MLink: #{item.mLink[0..50]}..."
906
+ end
907
+
908
+ puts ""
909
+ end
910
+ else
911
+ puts "No folders with content found"
912
+ end
913
+ else
914
+ puts "No folders found"
915
+ end
916
+ ```
917
+
918
+ #### Task Management
919
+ ```ruby
920
+ # Get user's tasks with filtering and pagination
921
+ tasks = client.get_tasks(filter: "Pending_Tasks", page: 1, limit: 5)
922
+
923
+ # Access tasks data
924
+ puts "๐Ÿ“‹ User Tasks:"
925
+ puts " Total tasks: #{tasks.tasks.task.length}"
926
+ puts " Transaction ID: #{tasks.transaction_id || 'None'}"
927
+ puts ""
928
+
929
+ # Display tasks
930
+ puts "๐Ÿ“‹ Task List:"
931
+ tasks.tasks.task.each do |task|
932
+ puts " โ€ข #{task.task_title} (ID: #{task.id})"
933
+ puts " Status: #{task.status} | Bucket: #{task.bucket}"
934
+ puts " Assigned to: #{task.assigned_to_name} (ID: #{task.assigned_to})"
935
+ puts " Created by: #{task.creator_name} (ID: #{task.creator_id})"
936
+ puts " Created: #{Time.at(task.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
937
+ puts " Assigned: #{Time.at(task.assigned_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
938
+ puts " Due: #{task.due} | Due on: #{task.due_on ? Time.at(task.due_on.to_i).strftime('%Y-%m-%d %H:%M:%S') : 'None'}"
939
+ puts " Is overdue: #{task.is_overdue} | Can be started: #{task.task_can_be_started}"
940
+ puts " Milestone: #{task.milestone_name || 'None'} (ID: #{task.milestone_id || 'None'})"
941
+ puts " Project: #{task.conversation_name} (ID: #{task.project_id})"
942
+ puts " Visibility: #{task.visibility} | Priority: #{task.personal_priority}"
943
+
944
+ # Show reviewers
945
+ if task.reviewers && task.reviewers.reviewer
946
+ reviewers = task.reviewers.reviewer
947
+ puts " Reviewers: #{reviewers.length} reviewers"
948
+ reviewers.first(3).each do |reviewer|
949
+ puts " - #{reviewer.user_name} (Status: #{reviewer.status})"
950
+ end
951
+ end
952
+
953
+ # Show next actions
954
+ if task.next_actions && task.next_actions.action
955
+ actions = task.next_actions.action
956
+ puts " Available actions: #{actions.join(', ')}"
957
+ end
958
+
959
+ # Show links
960
+ if task.mlink
961
+ puts " MLink: #{task.mlink[0..50]}..."
962
+ end
963
+
964
+ puts ""
965
+ end
966
+ ```
967
+
968
+ #### Task Details Management
969
+ ```ruby
970
+ # Get detailed information for a specific task
971
+ task_details = client.get_task_details("394153")
972
+
973
+ # Access task details data
974
+ puts "๐Ÿ“‹ Task Details:"
975
+ puts " Task ID: #{task_details.task.id}"
976
+ puts " Title: #{task_details.task.task_title}"
977
+ puts " Status: #{task_details.task.status} | Bucket: #{task_details.task.bucket}"
978
+ puts " Assigned to: #{task_details.task.assigned_to_name} (ID: #{task_details.task.assigned_to})"
979
+ puts " Created by: #{task_details.task.creator_name} (ID: #{task_details.task.creator_id})"
980
+ puts " Created: #{Time.at(task_details.task.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
981
+ puts " Assigned: #{Time.at(task_details.task.assigned_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
982
+ puts " Due: #{task_details.task.due} | Due on: #{task_details.task.due_on ? Time.at(task_details.task.due_on.to_i).strftime('%Y-%m-%d %H:%M:%S') : 'None'}"
983
+ puts " Is overdue: #{task_details.task.is_overdue} | Can be started: #{task_details.task.task_can_be_started}"
984
+ puts " Milestone: #{task_details.task.milestone_name || 'None'} (ID: #{task_details.task.milestone_id || 'None'})"
985
+ puts " Project: #{task_details.task.conversation_name} (ID: #{task_details.task.project_id})"
986
+ puts " Visibility: #{task_details.task.visibility} | Priority: #{task_details.task.personal_priority}"
987
+ puts " Transaction ID: #{task_details.transaction_id || 'None'}"
988
+ puts ""
989
+
990
+ # Show task timeline
991
+ if task_details.task.started_on
992
+ puts "๐Ÿ“… Started: #{Time.at(task_details.task.started_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
993
+ end
994
+ if task_details.task.finished_on
995
+ puts "๐Ÿ“… Finished: #{Time.at(task_details.task.finished_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
996
+ end
997
+ if task_details.task.delivered_on
998
+ puts "๐Ÿ“… Delivered: #{Time.at(task_details.task.delivered_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
999
+ end
1000
+
1001
+ # Show task history
1002
+ if task_details.task.reopened_on
1003
+ puts "๐Ÿ“… Reopened: #{Time.at(task_details.task.reopened_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1004
+ end
1005
+ if task_details.task.restarted_on
1006
+ puts "๐Ÿ“… Restarted: #{Time.at(task_details.task.restarted_on.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1007
+ end
1008
+
1009
+ # Show reviewers
1010
+ if task_details.task.reviewers && task_details.task.reviewers.reviewer
1011
+ reviewers = task_details.task.reviewers.reviewer
1012
+ puts "๐Ÿ‘ฅ Reviewers: #{reviewers.length} reviewers"
1013
+ reviewers.first(5).each do |reviewer|
1014
+ puts " - #{reviewer.user_name} (Status: #{reviewer.status})"
1015
+ end
1016
+ end
1017
+
1018
+ # Show next actions
1019
+ if task_details.task.next_actions && task_details.task.next_actions.action
1020
+ actions = task_details.task.next_actions.action
1021
+ puts "โšก Available actions: #{actions.join(', ')}"
1022
+ end
1023
+
1024
+ # Show task content
1025
+ if task_details.task.name
1026
+ puts "๐Ÿ“ Task content: #{task_details.task.name[0..200]}..."
1027
+ end
1028
+ if task_details.task.notes
1029
+ puts "๐Ÿ“ Notes: #{task_details.task.notes[0..200]}..."
1030
+ end
1031
+
1032
+ # Show links
1033
+ if task_details.task.mlink
1034
+ puts "๐Ÿ”— MLink: #{task_details.task.mlink}"
1035
+ end
1036
+
1037
+ # Show attachments
1038
+ if task_details.task.attachments
1039
+ puts "๐Ÿ“Ž Attachments: #{task_details.task.attachments.length} attachments"
1040
+ end
1041
+ if task_details.task.attachment_references
1042
+ puts "๐Ÿ“Ž Attachment references: #{task_details.task.attachment_references.length} references"
1043
+ end
1044
+ ```
1045
+
1046
+ #### Wiki Management
1047
+ ```ruby
1048
+ # Get user's wikis with filtering and pagination
1049
+ wikis = client.get_wikis(mode: "my", limit: 20, offset: 0)
1050
+
1051
+ # Access wikis data
1052
+ puts "๐Ÿ“š User Wikis:"
1053
+ puts " Total wikis: #{wikis.wikis.length}"
1054
+ puts " Transaction ID: #{wikis.transaction_id || 'None'}"
1055
+ puts ""
1056
+
1057
+ # Display wikis
1058
+ puts "๐Ÿ“š Wiki List:"
1059
+ wikis.wikis.each do |wiki|
1060
+ puts " โ€ข #{wiki.title} (ID: #{wiki.id})"
1061
+ puts " Updated: #{Time.at(wiki.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1062
+ puts " Children: #{wiki.children_count} | Can edit: #{wiki.can_edit}"
1063
+ puts " Conversation: #{wiki.conversation_name || 'None'} (ID: #{wiki.conversation_id})"
1064
+ puts " Is draft: #{wiki.is_draft} | PDF access: #{wiki.generate_pdf_access}"
1065
+ puts " User: #{wiki.user_name || 'None'} | Status: #{wiki.status || 'None'}"
1066
+ puts " Governance enabled: #{wiki.governance_enabled} | Governance date: #{wiki.governance_date || 'None'}"
1067
+
1068
+ # Show icon properties
1069
+ if wiki.icon_properties
1070
+ background_color = wiki.icon_properties.respond_to?(:'background-color') ? wiki.icon_properties.send(:'background-color') : nil
1071
+ puts " Icon: #{wiki.icon_properties.class} | Color: #{background_color || 'None'}"
1072
+ end
1073
+
1074
+ # Show user image URL
1075
+ if wiki.user_image_url
1076
+ puts " User image: #{wiki.user_image_url[0..50]}..."
1077
+ end
1078
+
1079
+ puts ""
1080
+ end
1081
+ ```
1082
+
1083
+ #### Wiki Details Management
1084
+ ```ruby
1085
+ # Get detailed information for a specific wiki
1086
+ wiki_details = client.get_wiki_details("7212")
1087
+
1088
+ # Access wiki details data
1089
+ puts "๐Ÿ“š Wiki Details:"
1090
+ puts " Wiki ID: #{wiki_details.wiki.details.id}"
1091
+ puts " Title: #{wiki_details.wiki.details.title}"
1092
+ puts " Status: #{wiki_details.wiki.details.status} | Platform: #{wiki_details.wiki.details.platform}"
1093
+ puts " Created: #{Time.at(wiki_details.wiki.details.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1094
+ puts " Updated: #{Time.at(wiki_details.wiki.details.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1095
+ puts " Modified: #{wiki_details.wiki.details.modified_on}"
1096
+ puts " Created by: #{wiki_details.wiki.details.created_by_name} (ID: #{wiki_details.wiki.details.user_id})"
1097
+ puts " Updated by: #{wiki_details.wiki.details.updated_by_name} (ID: #{wiki_details.wiki.details.last_updated_by})"
1098
+ puts " Conversation: #{wiki_details.wiki.details.conversation_name} (ID: #{wiki_details.wiki.details.conversation_id})"
1099
+ puts " Read count: #{wiki_details.wiki.details.total_read_count} | Children: #{wiki_details.wiki.details.children_count}"
1100
+ puts " Edit permissions: #{wiki_details.wiki.details.edit_permissions} | Commentable: #{wiki_details.wiki.details.is_commentable}"
1101
+ puts " Generate PDF access: #{wiki_details.wiki.details.generate_pdf_access} | Show TOC: #{wiki_details.wiki.details.show_toc}"
1102
+ puts " Has TOC: #{wiki_details.wiki.details.has_toc} | Archived: #{wiki_details.wiki.details.archived}"
1103
+ puts " Domain ID: #{wiki_details.wiki.details.domain_id} | Feed ID: #{wiki_details.wiki.details.feed_id}"
1104
+ puts ""
1105
+
1106
+ # Show wiki content
1107
+ if wiki_details.wiki.details.description
1108
+ puts "๐Ÿ“ Description: #{wiki_details.wiki.details.description[0..300]}..."
1109
+ end
1110
+
1111
+ # Show banner URL
1112
+ if wiki_details.wiki.details.banner_url
1113
+ puts "๐Ÿ–ผ๏ธ Banner URL: #{wiki_details.wiki.details.banner_url}"
1114
+ end
1115
+
1116
+ # Show wiki permissions
1117
+ puts "๐Ÿ” Wiki Permissions:"
1118
+ puts " Can comment: #{wiki_details.wiki.can_comment}"
1119
+ puts " Can edit: #{wiki_details.wiki.can_edit}"
1120
+ puts " Can delete: #{wiki_details.wiki.can_delete}"
1121
+ puts " Can rename: #{wiki_details.wiki.can_rename}"
1122
+ puts " Can move: #{wiki_details.wiki.can_move}"
1123
+ puts " Can duplicate: #{wiki_details.wiki.can_duplicate}"
1124
+
1125
+ # Show wiki links
1126
+ if wiki_details.wiki.mlink
1127
+ puts "๐Ÿ”— MLink: #{wiki_details.wiki.mlink}"
1128
+ end
1129
+
1130
+ # Show attachments
1131
+ if wiki_details.wiki.attachments
1132
+ puts "๐Ÿ“Ž Attachments: #{wiki_details.wiki.attachments.length} attachments"
1133
+ end
1134
+ if wiki_details.wiki.attachment_references
1135
+ puts "๐Ÿ“Ž Attachment references: #{wiki_details.wiki.attachment_references.length} references"
1136
+ end
1137
+
1138
+ # Show reactions
1139
+ if wiki_details.wiki.reactions
1140
+ reactions = wiki_details.wiki.reactions
1141
+ puts "๐Ÿ‘ Reactions: Like: #{reactions.like_count}, Superlike: #{reactions.superlike_count}"
1142
+ puts "๐Ÿ˜„ Reactions: Haha: #{reactions.haha_count}, Yay: #{reactions.yay_count}, Wow: #{reactions.wow_count}, Sad: #{reactions.sad_count}"
1143
+ puts "๐Ÿ‘ค User reactions: Liked: #{reactions.liked}, Superliked: #{reactions.superliked}"
1144
+ end
1145
+
1146
+ # Show reaction data
1147
+ if wiki_details.wiki.reaction_data
1148
+ reaction_data = wiki_details.wiki.reaction_data
1149
+ puts "๐Ÿ“Š Reaction data: #{reaction_data.length} reaction types"
1150
+ reaction_data.each do |reaction|
1151
+ puts " - #{reaction.label}: #{reaction.count} (Reacted: #{reaction.reacted})"
1152
+ end
1153
+ end
1154
+
1155
+ # Show comment count
1156
+ puts "๐Ÿ’ฌ Comment count: #{wiki_details.wiki.comment_count}"
1157
+
1158
+ # Show wiki status
1159
+ puts "๐Ÿ“Œ Is pinned: #{wiki_details.wiki.is_pinned}"
1160
+ puts "๐Ÿ“„ Is draft: #{wiki_details.wiki.is_draft}"
1161
+ puts "๐Ÿ”’ Governance enabled: #{wiki_details.wiki.governance_enabled}"
1162
+
1163
+ # Show hashtags
1164
+ if wiki_details.wiki.hashtags
1165
+ hashtags = wiki_details.wiki.hashtags
1166
+ puts "๐Ÿท๏ธ Hashtags: #{hashtags.length} hashtags"
1167
+ hashtags.each do |hashtag|
1168
+ puts " - #{hashtag}"
1169
+ end
1170
+ end
1171
+ ```
1172
+
1173
+ #### Award Feeds Management
1174
+ ```ruby
1175
+ # Get award feeds with comprehensive recognition data
1176
+ award_feeds = client.get_award_feeds
1177
+
1178
+ # Access award feeds data
1179
+ puts "๐Ÿ† Award Feeds:"
1180
+ puts " Transaction ID: #{award_feeds.transaction_id || 'None'}"
1181
+ puts " Limit: #{award_feeds.limit || 'None'}"
1182
+ puts " Current priority: #{award_feeds.current_priority || 'None'}"
1183
+ puts " Enable mobile pin: #{award_feeds.enable_mobile_pin}"
1184
+ puts " MangoApps version: #{award_feeds.mangoapps_version}"
1185
+ puts " Comments order: #{award_feeds.comments_order}"
1186
+ puts " Private message reply order: #{award_feeds.private_message_reply_order || 'None'}"
1187
+ puts " Photo shape: #{award_feeds.photo_shape || 'None'}"
1188
+ puts " Moderation feed IDs: #{award_feeds.moderation_feed_ids || 'None'}"
1189
+ puts " Moderation HTML: #{award_feeds.moderation_html || 'None'}"
1190
+ puts ""
1191
+
1192
+ # Show unread counts
1193
+ if award_feeds.unread_counts
1194
+ unread_counts = award_feeds.unread_counts
1195
+ puts "๐Ÿ“Š Unread Counts:"
1196
+ puts " Direct messages: #{unread_counts.direct_messages_count}"
1197
+ puts " What's new: #{unread_counts.whats_new_count}"
1198
+ puts " Unread feeds: #{unread_counts.unread_feeds_count}"
1199
+ puts " Mentions: #{unread_counts.mention_count}"
1200
+ puts " Primary unread: #{unread_counts.primary_unread_count}"
1201
+ puts " Secondary unread: #{unread_counts.secondary_unread_count}"
1202
+ puts " Unread notifications: #{unread_counts.unread_notification_count}"
1203
+ puts ""
1204
+ end
1205
+
1206
+ # Display award feeds
1207
+ puts "๐Ÿ† Award Feed List:"
1208
+ award_feeds.feeds.each do |feed|
1209
+ puts " โ€ข Feed ID: #{feed.id} | Type: #{feed.feed_type}"
1210
+ puts " Category: #{feed.category} | Sub-category: #{feed.sub_category}"
1211
+ puts " Recognition points: #{feed.recognition_points}"
1212
+ puts " From: #{feed.from_user.name if feed.from_user} | To: #{feed.to_users.length if feed.to_users} users"
1213
+ puts " Reactions: Like: #{feed.like_count}, Superlike: #{feed.superlike_count}"
1214
+ puts " Comments: #{feed.comment_count} | Attachments: #{feed.attachment_count}"
1215
+ puts " Created: #{Time.at(feed.created_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1216
+ puts " Updated: #{Time.at(feed.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1217
+
1218
+ # Show award details
1219
+ if feed.feed_property
1220
+ puts " Award: #{feed.feed_property.title}"
1221
+ puts " Labels: #{feed.feed_property.label_1}, #{feed.feed_property.label_2}"
1222
+ puts " Image URL: #{feed.feed_property.image_url[0..50]}..." if feed.feed_property.image_url
1223
+ puts " Status: #{feed.feed_property.status} | Result format: #{feed.feed_property.result_format}"
1224
+ end
1225
+
1226
+ # Show from user
1227
+ if feed.from_user
1228
+ puts " From user: #{feed.from_user.name} (ID: #{feed.from_user.id})"
1229
+ puts " Email: #{feed.from_user.email}"
1230
+ puts " Photo: #{feed.from_user.photo[0..50]}..." if feed.from_user.photo
1231
+ end
1232
+
1233
+ # Show to users
1234
+ if feed.to_users
1235
+ puts " To users: #{feed.to_users.length} users"
1236
+ feed.to_users.each do |user|
1237
+ puts " - #{user.name} (ID: #{user.id})"
1238
+ end
1239
+ end
1240
+
1241
+ # Show feed story users
1242
+ if feed.feed_story_users
1243
+ puts " Story users: #{feed.feed_story_users.length} users"
1244
+ feed.feed_story_users.each do |user|
1245
+ puts " - #{user.name} (ID: #{user.id})"
1246
+ end
1247
+ end
1248
+
1249
+ # Show core value tags
1250
+ if feed.core_value_tags
1251
+ puts " Core value tags: #{feed.core_value_tags.length} tags"
1252
+ feed.core_value_tags.each do |tag|
1253
+ puts " - #{tag.name} (ID: #{tag.id}, Color: #{tag.color})"
1254
+ end
1255
+ end
1256
+
1257
+ # Show reaction data
1258
+ if feed.reaction_data
1259
+ puts " Reaction data: #{feed.reaction_data.length} reaction types"
1260
+ feed.reaction_data.each do |reaction|
1261
+ puts " - #{reaction.label}: #{reaction.count} (Reacted: #{reaction.reacted})"
1262
+ end
1263
+ end
1264
+
1265
+ # Show comments
1266
+ if feed.comments
1267
+ puts " Comments: #{feed.comments.length} comments"
1268
+ feed.comments.first(3).each do |comment|
1269
+ puts " - #{comment.body[0..50]}... by #{comment.user.name if comment.user}"
1270
+ end
1271
+ end
1272
+
1273
+ puts ""
1274
+ end
1275
+ ```
1276
+
1277
+ ## Available Modules
1278
+
1279
+ ### โœ… Currently Implemented
1280
+
1281
+ #### Users Module
1282
+ - **User Profile**: `client.me` - Get current user information with clean dot notation access
1283
+
1284
+ #### Learn Module
1285
+ - **Course Catalog**: `client.course_catalog` - Get available courses
1286
+ - **Course Categories**: `client.course_categories` - Get course categories
1287
+ - **Course Details**: `client.course_details(course_id)` - Get detailed course information by ID
1288
+ - **My Learning**: `client.my_learning` - Get user's learning progress and courses
1289
+
1290
+ #### Recognitions Module
1291
+ - **Award Categories**: `client.award_categories` - Get recognition award categories
1292
+ - **Get Awards List**: `client.get_awards_list(category_id: id)` - Get awards for a specific category
1293
+ - **Get Profile Awards**: `client.get_profile_awards` - Get user's personal awards and activity
1294
+ - **Get Team Awards**: `client.get_team_awards(project_id: id)` - Get team awards and activity
1295
+ - **Get Award Feeds**: `client.get_award_feeds` - Get award feeds with comprehensive recognition data
1296
+ - **Core Value Tags**: `client.core_value_tags` - Get core value tags for recognition
1297
+ - **Leaderboard Info**: `client.leaderboard_info` - Get user and team leaderboard information
1298
+ - **Gift Cards**: `client.gift_cards` - Get available gift cards for recognition rewards
1299
+
1300
+ #### Notifications Module
1301
+ - **My Priority Items**: `client.my_priority_items` - Get user's priority items including requests, events, quizzes, surveys, tasks, and todos
1302
+ - **Notifications**: `client.notifications` - Get user's notifications with unread counts and detailed notification information
1303
+
1304
+ #### Feeds Module
1305
+ - **Feeds**: `client.feeds` - Get user's activity feeds with unread counts and feed details
1306
+
1307
+ #### Posts Module
1308
+ - **Get All Posts**: `client.get_all_posts(filter_by: "all")` - Get all posts with filtering options
1309
+ - **Get Post By ID**: `client.get_post_by_id(post_id, full_description: "Y")` - Get detailed post information by ID
1310
+
1311
+ #### Libraries Module
1312
+ - **Get Libraries**: `client.get_libraries` - Get user's document libraries with categories and items
1313
+ - **Get Library Categories**: `client.get_library_categories(library_id)` - Get detailed library information and categories by library ID
1314
+ - **Get Library Items**: `client.get_library_items(library_id, category_id)` - Get library items by library ID and category ID
1315
+
1316
+ #### Trackers Module
1317
+ - **Get Trackers**: `client.get_trackers` - Get user's trackers with submission dates and conversation details
1318
+
1319
+ #### Attachments Module
1320
+ - **Get Folders**: `client.get_folders` - Get user's file folders with permissions and metadata
1321
+ - **Get Folder Files**: `client.get_folder_files(folder_id, include_folders: "Y")` - Get files and folders inside a specific folder
1322
+
1323
+ #### Tasks Module
1324
+ - **Get Tasks**: `client.get_tasks(filter: "Pending_Tasks", page: 1, limit: 5)` - Get user's tasks with filtering, pagination, and detailed task information
1325
+ - **Get Task Details**: `client.get_task_details(task_id)` - Get detailed information for a specific task by ID
1326
+
1327
+ #### Wikis Module
1328
+ - **Get Wikis**: `client.get_wikis(mode: "my", limit: 20, offset: 0)` - Get user's wikis with filtering, pagination, and detailed wiki information
1329
+ - **Get Wiki Details**: `client.get_wiki_details(wiki_id)` - Get detailed information for a specific wiki by ID
1330
+
1331
+ ## Complete Examples
1332
+
1333
+ ### Notifications Management
1334
+ ```ruby
1335
+ require 'mangoapps-ex-sdk-ruby'
1336
+
1337
+ # Initialize client
1338
+ client = MangoApps::Client.new
1339
+
1340
+ # Get user's priority items
1341
+ priority_items = client.my_priority_items
1342
+
1343
+ # Display all priority items with details
1344
+ puts "๐Ÿ”” User Priority Items Dashboard"
1345
+ puts "================================"
1346
+ puts "Success: #{priority_items.success}"
1347
+ puts "Display Type: #{priority_items.display_type}"
1348
+ puts ""
1349
+
1350
+ priority_items.data.each do |item|
1351
+ puts "๐Ÿ“‹ #{item.title} (ID: #{item.id})"
1352
+ puts " Count: #{item.count} pending items"
1353
+ puts " Action Type: #{item.action_type}"
1354
+ puts " Icon: #{item.icon} (#{item.icon_color})"
1355
+ puts " Background: #{item.icon_bg_color}"
1356
+ puts " Description: #{item.info_details.gsub(/<[^>]*>/, '').strip[0..100]}..."
1357
+ puts ""
1358
+ end
1359
+
1360
+ # Get user's notifications
1361
+ notifications = client.notifications
1362
+
1363
+ # Display notifications dashboard
1364
+ puts "๐Ÿ”” User Notifications Dashboard"
1365
+ puts "==============================="
1366
+ puts "What's new: #{notifications.whats_new_count}"
1367
+ puts "Unread feeds: #{notifications.unread_feeds_count}"
1368
+ puts "Mentions: #{notifications.mention_count}"
1369
+ puts "Direct messages: #{notifications.direct_messages_count}"
1370
+ puts "Unread notifications: #{notifications.unread_notification_count}"
1371
+ puts ""
1372
+
1373
+ notifications.notifications.first(5).each do |notification|
1374
+ puts "๐Ÿ“ข #{notification.sender_name} (ID: #{notification.id})"
1375
+ puts " Text: #{notification.text[0..100]}..."
1376
+ puts " Type: #{notification.notification_type || 'None'}"
1377
+ puts " Read: #{notification.is_read} | Updated: #{Time.at(notification.updated_at.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1378
+ puts " MLink: #{notification.mlink || 'None'}"
1379
+ if notification.mention_tags && notification.mention_tags.any?
1380
+ puts " Mentions: #{notification.mention_tags.map { |tag| tag.mention }.join(', ')}"
1381
+ end
1382
+ puts ""
1383
+ end
1384
+
1385
+ # Filter by specific action types
1386
+ approval_items = priority_items.data.select { |item| item.action_type == 'approval' }
1387
+ puts "๐Ÿ” Approval Items: #{approval_items.length}"
1388
+ approval_items.each do |item|
1389
+ puts " โ€ข #{item.title}: #{item.count} items"
1390
+ end
1391
+
1392
+ # Get high-priority items (count > 5)
1393
+ high_priority = priority_items.data.select { |item| item.count > 5 }
1394
+ puts "โš ๏ธ High Priority Items: #{high_priority.length}"
1395
+ high_priority.each do |item|
1396
+ puts " โ€ข #{item.title}: #{item.count} items"
1397
+ end
1398
+ ```
1399
+
1400
+ ### User Profile Management
1401
+ ```ruby
1402
+ # Get current user profile
1403
+ user = client.me
1404
+
1405
+ # Access user information with clean dot notation
1406
+ puts "Name: #{user.user_profile.minimal_profile.name}"
1407
+ puts "Email: #{user.user_profile.minimal_profile.email}"
1408
+ puts "User Type: #{user.user_profile.minimal_profile.user_type}"
1409
+
1410
+ # Access user statistics
1411
+ puts "Followers: #{user.user_profile.user_data.followers}"
1412
+ puts "Following: #{user.user_profile.user_data.following}"
1413
+
1414
+ # Access gamification data
1415
+ puts "Current Level: #{user.user_profile.gamification.current_level}"
1416
+ puts "Total Points: #{user.user_profile.gamification.total_points}"
1417
+ puts "Badges: #{user.user_profile.gamification.badges.length}"
1418
+
1419
+ # Access recognition data
1420
+ puts "Reward Points Received: #{user.user_profile.recognition.total_reward_points_received}"
1421
+ ```
1422
+
1423
+ ### Learning Management
1424
+ ```ruby
1425
+ # Get course catalog
1426
+ courses = client.course_catalog
1427
+
1428
+ # Browse available courses
1429
+ courses.courses.each do |course|
1430
+ puts "๐Ÿ“š #{course.name}"
1431
+ puts " Type: #{course.course_type}"
1432
+ puts " Delivery: #{course.delivery_mode}"
1433
+ puts " URL: #{course.start_course_url}"
1434
+ puts ""
1435
+
1436
+ # Get detailed course information
1437
+ course_details = client.course_details(course.id)
1438
+ detailed_course = course_details.course
1439
+ puts " ๐Ÿ“– Detailed Info:"
1440
+ puts " Description: #{detailed_course.description}"
1441
+ puts " Instructors: #{detailed_course.instructors.length}"
1442
+ puts " Fields: #{detailed_course.fields.length}"
1443
+ puts ""
1444
+ end
1445
+
1446
+ # Get course categories
1447
+ categories = client.course_categories
1448
+
1449
+ # Browse course categories
1450
+ categories.all_categories.each do |category|
1451
+ puts "๐Ÿ“‚ #{category.name}"
1452
+ puts " Position: #{category.position}"
1453
+ puts " Icon: #{category.icon_properties}"
1454
+ puts ""
1455
+ end
1456
+
1457
+ # Get user's learning progress
1458
+ learning = client.my_learning
1459
+
1460
+ # Display learning summary
1461
+ puts "๐ŸŽ“ Learning Summary for #{learning.user_name}"
1462
+ puts "โฑ๏ธ Total training time: #{learning.total_training_time}"
1463
+ puts "๐Ÿ“š Ongoing: #{learning.ongoing_course_count} | โœ… Completed: #{learning.completed_course_count} | ๐Ÿ“ Registered: #{learning.registered_course_count}"
1464
+ puts ""
1465
+
1466
+ # Browse learning sections
1467
+ learning.section.each do |section|
1468
+ puts "๐Ÿ“‚ #{section.label} (#{section.count} courses)"
1469
+
1470
+ section.courses.each do |course|
1471
+ puts " ๐Ÿ“š #{course.name}"
1472
+ puts " Progress: #{course.course_progress}%"
1473
+ puts " Type: #{course.course_type}"
1474
+ puts " URL: #{course.start_course_url}"
1475
+ puts ""
1476
+ end
1477
+ end
1478
+ ```
1479
+
1480
+ ### Recognition Management
1481
+ ```ruby
1482
+ # Get award categories
1483
+ categories = client.award_categories
1484
+
1485
+ # Display available award categories
1486
+ puts "๐Ÿ† Available Award Categories:"
1487
+ categories.award_categories.each do |category|
1488
+ puts " โ€ข #{category.name} (ID: #{category.id})"
1489
+ puts " Permission: #{category.recipient_permission}"
1490
+ end
1491
+ puts ""
1492
+
1493
+ # Get awards for a specific category
1494
+ category_id = 4303 # Safety & Quality category
1495
+ awards = client.get_awards_list(category_id: category_id)
1496
+
1497
+ # Display awards in the category
1498
+ puts "๐Ÿ† Awards in Category #{category_id}:"
1499
+ awards.get_awards_list.each do |award|
1500
+ puts " โ€ข #{award.name} (ID: #{award.id})"
1501
+ puts " Points: #{award.points} | Reward Points: #{award.reward_points || 'None'}"
1502
+ puts " Description: #{award.description}"
1503
+ end
1504
+ puts ""
1505
+
1506
+ # Get user profile awards
1507
+ profile_awards = client.get_profile_awards
1508
+
1509
+ # Display user's personal awards
1510
+ puts "๐Ÿ† User Profile Awards:"
1511
+ puts " Core Value Tags:"
1512
+ profile_awards.core_value_tags.each do |tag|
1513
+ puts " โ€ข #{tag.name} (ID: #{tag.id}) - Count: #{tag.count}"
1514
+ end
1515
+ puts " Recent Awards:"
1516
+ profile_awards.feeds.each do |feed|
1517
+ puts " โ€ข #{feed.feed_property.title} - Points: #{feed.recognition_points}"
1518
+ puts " From: #{feed.from_user.name}"
1519
+ end
1520
+ puts ""
1521
+
1522
+ # Get team awards
1523
+ team_id = 117747 # All Users team
1524
+ team_awards = client.get_team_awards(project_id: team_id)
1525
+
1526
+ # Display team awards
1527
+ puts "๐Ÿ† Team Awards (Team ID: #{team_id}):"
1528
+ puts " Team Core Value Tags:"
1529
+ team_awards.core_value_tags.each do |tag|
1530
+ puts " โ€ข #{tag.name} (ID: #{tag.id}) - Count: #{tag.count}"
1531
+ end
1532
+ puts " Team Recent Awards:"
1533
+ team_awards.feeds.each do |feed|
1534
+ puts " โ€ข #{feed.feed_property.title} - Points: #{feed.recognition_points}"
1535
+ puts " From: #{feed.from_user.name} | Team: #{feed.group_name}"
1536
+ end
1537
+ puts ""
1538
+
1539
+ # Get user priority items
1540
+ priority_items = client.my_priority_items
1541
+
1542
+ # Display priority items
1543
+ puts "๐Ÿ”” User Priority Items:"
1544
+ priority_items.data.each do |item|
1545
+ puts " โ€ข #{item.title} (ID: #{item.id}) - Count: #{item.count}"
1546
+ puts " Action Type: #{item.action_type} | Icon: #{item.icon}"
1547
+ end
1548
+ puts ""
1549
+
1550
+ # Get user activity feeds
1551
+ feeds = client.feeds
1552
+
1553
+ # Display feeds
1554
+ puts "๐Ÿ“ฐ User Activity Feeds:"
1555
+ puts " Total feeds: #{feeds.feeds.length}"
1556
+ puts " Unread feeds: #{feeds.unread_counts.unread_feeds_count}"
1557
+ puts " Direct messages: #{feeds.unread_counts.direct_messages_count}"
1558
+ puts " What's new: #{feeds.unread_counts.whats_new_count}"
1559
+ puts ""
1560
+
1561
+ # Display recent feeds
1562
+ puts "๐Ÿ“ฐ Recent Feeds:"
1563
+ feeds.feeds.first(3).each do |feed|
1564
+ puts " โ€ข #{feed.feed_property.title} (ID: #{feed.id})"
1565
+ puts " From: #{feed.from_user.name} | Group: #{feed.group_name}"
1566
+ puts " Type: #{feed.feed_type} | Unread: #{feed.unread}"
1567
+ end
1568
+ puts ""
1569
+
1570
+ # Get all posts
1571
+ posts = client.get_all_posts(filter_by: "all")
1572
+
1573
+ # Display posts
1574
+ puts "๐Ÿ“ All Posts:"
1575
+ puts " Total posts: #{posts.feeds.length}"
1576
+ puts " Post view count visibility: #{posts.post_view_count_visibility}"
1577
+ puts ""
1578
+
1579
+ # Display recent posts
1580
+ puts "๐Ÿ“ Recent Posts:"
1581
+ posts.feeds.first(3).each do |post|
1582
+ puts " โ€ข #{post.tile.tile_name} (ID: #{post.id})"
1583
+ puts " From: #{post.from_user.name} | Group: #{post.group_name}"
1584
+ puts " Views: #{post.total_view_count} | Comments: #{post.comments.length}"
1585
+ end
1586
+ puts ""
1587
+
1588
+ # Get detailed post information
1589
+ if posts.feeds.any?
1590
+ first_post_id = posts.feeds.first.post_id
1591
+ post_details = client.get_post_by_id(first_post_id, full_description: "Y")
1592
+
1593
+ puts "๐Ÿ“ Post Details (ID: #{first_post_id}):"
1594
+ puts " Title: #{post_details.post.title}"
1595
+ puts " Created by: #{post_details.post.created_name}"
1596
+ puts " View count: #{post_details.post.total_view_count}"
1597
+ puts " Can edit: #{post_details.post.can_edit} | Can comment: #{post_details.post.can_comment}"
1598
+ puts " Full description available: #{post_details.post.tile.tile_full_description.length > 0 ? 'Yes' : 'No'}"
1599
+ end
1600
+ puts ""
1601
+
1602
+ # Get user libraries
1603
+ libraries = client.get_libraries
1604
+
1605
+ puts "๐Ÿ“š User Libraries:"
1606
+ puts " Total libraries: #{libraries.libraries.length}"
1607
+ puts ""
1608
+
1609
+ # Display recent libraries
1610
+ puts "๐Ÿ“š Recent Libraries:"
1611
+ libraries.libraries.first(3).each do |library|
1612
+ puts " โ€ข #{library.name} (ID: #{library.id})"
1613
+ puts " Type: #{library.library_type} | Items: #{library.total_items_count}"
1614
+ puts " Categories: #{library.categories.length} | Edit access: #{library.edit_access}"
1615
+ end
1616
+ puts ""
1617
+
1618
+ # Get detailed library information
1619
+ if libraries.libraries.any?
1620
+ first_library_id = libraries.libraries.first.id
1621
+ library_details = client.get_library_categories(first_library_id)
1622
+
1623
+ puts "๐Ÿ“š Library Details (ID: #{first_library_id}):"
1624
+ puts " Name: #{library_details.library.name}"
1625
+ puts " Type: #{library_details.library.library_type} | View: #{library_details.library.view_mode}"
1626
+ puts " Total items: #{library_details.library.total_items_count}"
1627
+ puts " Categories: #{library_details.library.categories.length}"
1628
+ puts " Edit access: #{library_details.library.edit_access}"
1629
+
1630
+ # Get library items from first category
1631
+ if library_details.library.categories.any?
1632
+ first_category_id = library_details.library.categories.first.id
1633
+ library_items = client.get_library_items(first_library_id, first_category_id)
1634
+
1635
+ puts "๐Ÿ“š Library Items (Category: #{library_items.category_name}):"
1636
+ puts " View mode: #{library_items.view_mode} | Library type: #{library_items.library_type}"
1637
+ puts " Items count: #{library_items.library_items.length}"
1638
+ puts " Can add: #{library_items.can_add}"
1639
+ end
1640
+ end
1641
+ puts ""
1642
+
1643
+ # Get user trackers
1644
+ trackers = client.get_trackers
1645
+
1646
+ puts "๐Ÿ“Š User Trackers:"
1647
+ puts " Total trackers: #{trackers.trackers.length}"
1648
+ puts ""
1649
+
1650
+ # Display recent trackers
1651
+ puts "๐Ÿ“Š Recent Trackers:"
1652
+ trackers.trackers.first(3).each do |tracker|
1653
+ puts " โ€ข #{tracker.name} (ID: #{tracker.id})"
1654
+ puts " Last submission: #{Time.at(tracker.last_submission_date.to_i).strftime('%Y-%m-%d %H:%M:%S')}"
1655
+ puts " Conversation: #{tracker.conversation_name}"
1656
+ puts " Pinned: #{tracker.is_pinned} | Can share: #{tracker.can_share}"
1657
+ end
1658
+ puts ""
1659
+
1660
+ # Get user folders
1661
+ folders = client.get_folders
1662
+
1663
+ puts "๐Ÿ“ User Folders:"
1664
+ puts " Total folders: #{folders.folders.length}"
1665
+ puts ""
1666
+
1667
+ # Display first few folders
1668
+ puts "๐Ÿ“ Available Folders:"
1669
+ folders.folders.first(3).each do |folder|
1670
+ puts " โ€ข #{folder.name} (ID: #{folder.id})"
1671
+ puts " Path: #{folder.relativePath} | Child count: #{folder.child_count}"
1672
+ puts " Can save: #{folder.can_save} | Pinned: #{folder.is_pinned}"
1673
+ end
1674
+ puts ""
1675
+
1676
+ # Get files from first folder with content
1677
+ if folders.folders.any?
1678
+ folder_with_content = folders.folders.find { |f| f.child_count.to_i > 0 }
1679
+ if folder_with_content
1680
+ folder_files = client.get_folder_files(folder_with_content.id, include_folders: "Y")
1681
+ puts "๐Ÿ“ Folder Files:"
1682
+ puts " Folder: #{folder_files.name} | Total: #{folder_files.total_count}"
1683
+ puts " Role: #{folder_files.role_name} | Upload enabled: #{folder_files.show_in_upload}"
1684
+ end
1685
+ end
1686
+ puts ""
1687
+
1688
+ # Get user tasks
1689
+ tasks = client.get_tasks(filter: "Pending_Tasks", page: 1, limit: 3)
1690
+
1691
+ puts "๐Ÿ“‹ User Tasks:"
1692
+ puts " Total tasks: #{tasks.tasks.task.length}"
1693
+ puts ""
1694
+
1695
+ # Display first few tasks
1696
+ puts "๐Ÿ“‹ Recent Tasks:"
1697
+ tasks.tasks.task.first(3).each do |task|
1698
+ puts " โ€ข #{task.task_title} (ID: #{task.id})"
1699
+ puts " Status: #{task.status} | Assigned to: #{task.assigned_to_name}"
1700
+ puts " Due: #{task.due_on ? Time.at(task.due_on.to_i).strftime('%Y-%m-%d') : 'None'}"
1701
+ puts " Is overdue: #{task.is_overdue} | Priority: #{task.personal_priority}"
1702
+ end
1703
+ puts ""
1704
+
1705
+ # Get detailed information for the first task
1706
+ if tasks.tasks.task.any?
1707
+ first_task = tasks.tasks.task.first
1708
+ task_id = first_task.is_a?(Array) ? first_task[1] : first_task.id
1709
+ task_details = client.get_task_details(task_id)
1710
+
1711
+ puts "๐Ÿ“‹ Task Details:"
1712
+ puts " Task: #{task_details.task.task_title} (ID: #{task_details.task.id})"
1713
+ puts " Status: #{task_details.task.status} | Bucket: #{task_details.task.bucket}"
1714
+ puts " Assigned to: #{task_details.task.assigned_to_name} | Created by: #{task_details.task.creator_name}"
1715
+ puts " Due: #{task_details.task.due_on ? Time.at(task_details.task.due_on.to_i).strftime('%Y-%m-%d') : 'None'}"
1716
+ puts " Is overdue: #{task_details.task.is_overdue} | Priority: #{task_details.task.personal_priority}"
1717
+ puts " Milestone: #{task_details.task.milestone_name || 'None'}"
1718
+ puts " Project: #{task_details.task.conversation_name}"
1719
+ puts " Visibility: #{task_details.task.visibility}"
1720
+
1721
+ # Show reviewers
1722
+ if task_details.task.reviewers && task_details.task.reviewers.reviewer
1723
+ reviewers = task_details.task.reviewers.reviewer
1724
+ puts " Reviewers: #{reviewers.length} reviewers"
1725
+ end
1726
+
1727
+ # Show next actions
1728
+ if task_details.task.next_actions && task_details.task.next_actions.action
1729
+ actions = task_details.task.next_actions.action
1730
+ puts " Available actions: #{actions.join(', ')}"
1731
+ end
1732
+ end
1733
+ puts ""
1734
+
1735
+ # Get user wikis
1736
+ wikis = client.get_wikis(mode: "my", limit: 5, offset: 0)
1737
+
1738
+ puts "๐Ÿ“š User Wikis:"
1739
+ puts " Total wikis: #{wikis.wikis.length}"
1740
+ puts ""
1741
+
1742
+ # Display first few wikis
1743
+ puts "๐Ÿ“š Recent Wikis:"
1744
+ wikis.wikis.first(3).each do |wiki|
1745
+ puts " โ€ข #{wiki.title} (ID: #{wiki.id})"
1746
+ puts " Updated: #{Time.at(wiki.updated_at.to_i).strftime('%Y-%m-%d')}"
1747
+ puts " Children: #{wiki.children_count} | Can edit: #{wiki.can_edit}"
1748
+ puts " Conversation: #{wiki.conversation_name || 'None'} (ID: #{wiki.conversation_id})"
1749
+ puts " Is draft: #{wiki.is_draft} | PDF access: #{wiki.generate_pdf_access}"
1750
+ end
1751
+ puts ""
1752
+
1753
+ # Get detailed information for the first wiki
1754
+ if wikis.wikis.any?
1755
+ first_wiki = wikis.wikis.first
1756
+ wiki_details = client.get_wiki_details(first_wiki.id)
1757
+
1758
+ puts "๐Ÿ“š Wiki Details:"
1759
+ puts " Wiki: #{wiki_details.wiki.details.title} (ID: #{wiki_details.wiki.details.id})"
1760
+ puts " Status: #{wiki_details.wiki.details.status} | Platform: #{wiki_details.wiki.details.platform}"
1761
+ puts " Created by: #{wiki_details.wiki.details.created_by_name} | Updated by: #{wiki_details.wiki.details.updated_by_name}"
1762
+ puts " Read count: #{wiki_details.wiki.details.total_read_count} | Children: #{wiki_details.wiki.details.children_count}"
1763
+ puts " Conversation: #{wiki_details.wiki.details.conversation_name}"
1764
+ puts " Edit permissions: #{wiki_details.wiki.details.edit_permissions} | Commentable: #{wiki_details.wiki.details.is_commentable}"
1765
+ puts " Generate PDF access: #{wiki_details.wiki.details.generate_pdf_access} | Show TOC: #{wiki_details.wiki.details.show_toc}"
1766
+ puts " Archived: #{wiki_details.wiki.details.archived} | Is draft: #{wiki_details.wiki.is_draft}"
1767
+
1768
+ # Show permissions
1769
+ puts " Permissions: Comment: #{wiki_details.wiki.can_comment}, Edit: #{wiki_details.wiki.can_edit}, Delete: #{wiki_details.wiki.can_delete}"
1770
+
1771
+ # Show reactions
1772
+ if wiki_details.wiki.reactions
1773
+ reactions = wiki_details.wiki.reactions
1774
+ puts " Reactions: Like: #{reactions.like_count}, Superlike: #{reactions.superlike_count}"
1775
+ end
1776
+
1777
+ # Show comment count
1778
+ puts " Comment count: #{wiki_details.wiki.comment_count}"
1779
+ end
1780
+ puts ""
1781
+
1782
+ # Get award feeds
1783
+ award_feeds = client.get_award_feeds
1784
+
1785
+ # Display award feeds
1786
+ puts "๐Ÿ† Award Feeds:"
1787
+ puts " Unread counts:"
1788
+ if award_feeds.unread_counts
1789
+ puts " Direct messages: #{award_feeds.unread_counts.direct_messages_count}"
1790
+ puts " What's new: #{award_feeds.unread_counts.whats_new_count}"
1791
+ puts " Unread feeds: #{award_feeds.unread_counts.unread_feeds_count}"
1792
+ puts " Mentions: #{award_feeds.unread_counts.mention_count}"
1793
+ puts " Primary unread: #{award_feeds.unread_counts.primary_unread_count}"
1794
+ puts " Secondary unread: #{award_feeds.unread_counts.secondary_unread_count}"
1795
+ puts " Unread notifications: #{award_feeds.unread_counts.unread_notification_count}"
1796
+ end
1797
+ puts " Recent Award Feeds:"
1798
+ award_feeds.feeds.first(3).each do |feed|
1799
+ puts " โ€ข #{feed.feed_property.title if feed.feed_property} - Points: #{feed.recognition_points}"
1800
+ puts " From: #{feed.from_user.name if feed.from_user} | To: #{feed.to_users.length if feed.to_users} users"
1801
+ puts " Reactions: Like: #{feed.like_count}, Superlike: #{feed.superlike_count}"
1802
+ puts " Comments: #{feed.comment_count} | Created: #{Time.at(feed.created_at.to_i).strftime('%Y-%m-%d')}"
1803
+ end
1804
+ puts ""
1805
+
1806
+ # Get core value tags
1807
+ tags = client.core_value_tags
1808
+
1809
+ # Display core value tags
1810
+ puts "๐ŸŽฏ Core Value Tags:"
1811
+ tags.core_value_tags.each do |tag|
1812
+ puts " โ€ข #{tag.name} (ID: #{tag.id})"
1813
+ puts " Color: ##{tag.color}"
1814
+ end
1815
+ puts ""
1816
+
1817
+ # Get leaderboard information
1818
+ leaderboard = client.leaderboard_info
1819
+
1820
+ # Display leaderboard if available
1821
+ if leaderboard.leaderboard_info
1822
+ puts "๐Ÿ… User Leaderboard:"
1823
+ leaderboard.leaderboard_info.user_info.each do |user|
1824
+ puts " #{user.rank}. #{user.name} - #{user.award_count} awards"
1825
+ end
1826
+ puts ""
1827
+
1828
+ puts "๐Ÿ† Team Leaderboard:"
1829
+ leaderboard.leaderboard_info.team_info.each do |team|
1830
+ puts " #{team.rank}. #{team.name} - #{team.award_count} awards"
1831
+ end
1832
+ else
1833
+ puts "๐Ÿ“Š No leaderboard data configured"
1834
+ end
1835
+
1836
+ # Get tango gift cards information
1837
+ tango_gift_cards = client.tango_gift_cards
1838
+
1839
+ # Display tango gift card information
1840
+ puts "๐ŸŽ Tango Gift Cards:"
1841
+ puts " Available points: #{tango_gift_cards.tango_cards.available_points}"
1842
+ puts " Terms: #{tango_gift_cards.tango_cards.terms[0..100]}..." if tango_gift_cards.tango_cards.terms
1843
+ puts ""
1844
+
1845
+ # Get gift cards information
1846
+ gift_cards = client.gift_cards
1847
+
1848
+ # Display available gift cards
1849
+ puts "๐ŸŽ Available Gift Cards:"
1850
+ gift_cards.cards.each do |card|
1851
+ puts " โ€ข #{card.brand_name} (Key: #{card.brand_key}) - Enabled: #{card.enabled}"
1852
+ end
1853
+ ```
1854
+
1855
+ ### Error Handling with Clean Responses
1856
+ ```ruby
1857
+ begin
1858
+ user = client.me
1859
+
1860
+ # Clean dot notation access
1861
+ puts "Welcome, #{user.user_profile.minimal_profile.name}!"
1862
+
1863
+ rescue MangoApps::AuthenticationError => e
1864
+ puts "Authentication failed: #{e.message}"
1865
+ # Redirect to OAuth flow
1866
+
1867
+ rescue MangoApps::APIError => e
1868
+ puts "API error: #{e.message}"
1869
+ puts "Status: #{e.status_code}"
1870
+
1871
+ rescue MangoApps::RateLimitError => e
1872
+ puts "Rate limited: #{e.message}"
1873
+ # Implement backoff strategy
1874
+ end
1875
+ ```
1876
+
1877
+
1878
+ ## Error Handling
1879
+
1880
+ The SDK provides specific exception types for different error scenarios:
1881
+
1882
+ ```ruby
1883
+ begin
1884
+ posts = client.posts_list
1885
+ rescue MangoApps::AuthenticationError => e
1886
+ puts "Authentication failed: #{e.message}"
1887
+ rescue MangoApps::APIError => e
1888
+ puts "API error: #{e.message}"
1889
+ rescue MangoApps::RateLimitError => e
1890
+ puts "Rate limited: #{e.message}"
1891
+ end
1892
+
1893
+ # OAuth-specific error handling
1894
+ begin
1895
+ userinfo = client.get_userinfo
1896
+ rescue MangoApps::TokenExpiredError => e
1897
+ puts "OAuth token expired: #{e.message}"
1898
+ # Refresh token or redirect to OAuth flow
1899
+ rescue MangoApps::APIError => e
1900
+ puts "OAuth userinfo error: #{e.message}"
1901
+ end
1902
+ ```
1903
+
1904
+ ### Available Exception Types
1905
+
1906
+ - `MangoApps::Error` - Base error class
1907
+ - `MangoApps::APIError` - General API errors
1908
+ - `MangoApps::AuthenticationError` - Authentication failures
1909
+ - `MangoApps::TokenExpiredError` - Token expiration
1910
+ - `MangoApps::DiscoveryError` - OAuth discovery endpoint errors
1911
+ - `MangoApps::TokenExchangeError` - OAuth token exchange errors
1912
+ - `MangoApps::BadRequestError` - 400 errors
1913
+ - `MangoApps::UnauthorizedError` - 401 errors
1914
+ - `MangoApps::ForbiddenError` - 403 errors
1915
+ - `MangoApps::NotFoundError` - 404 errors
1916
+ - `MangoApps::RateLimitError` - 429 errors
1917
+ - `MangoApps::ServerError` - 5xx errors
1918
+
1919
+ ## Configuration Options
1920
+
1921
+ ```ruby
1922
+ config = MangoApps::Config.new(
1923
+ domain: "yourdomain.mangoapps.com",
1924
+ client_id: "your_client_id",
1925
+ client_secret: "your_client_secret",
1926
+ redirect_uri: "https://localhost:3000/oauth/callback",
1927
+ scope: "openid profile email",
1928
+ timeout: 30,
1929
+ open_timeout: 10,
1930
+ logger: Logger.new(STDOUT)
1931
+ )
1932
+
1933
+ # OAuth-specific configuration
1934
+ # - All OAuth endpoints automatically use HTTPS
1935
+ # - Automatic redirect handling for 301/302 responses
1936
+ # - SSL certificate verification enabled
1937
+ ```
1938
+
1939
+
1940
+ ## Development
1941
+
1942
+ ### For SDK Users
1943
+
1944
+ If you're using this SDK in your application, you only need:
1945
+
1946
+ 1. **Install the gem**: `gem install mangoapps-ex-sdk-ruby`
1947
+ 2. **Configure OAuth**: Set up your MangoApps OAuth credentials
1948
+ 3. **Start coding**: Use the examples above
1949
+
1950
+ ### For SDK Developers
1951
+
1952
+ If you're contributing to or extending this SDK, see our comprehensive developer documentation:
1953
+
1954
+ ๐Ÿ“š **[DEVELOPER.md](DEVELOPER.md)** - Complete guide for SDK development including:
1955
+ - Adding new APIs and modules
1956
+ - Testing guidelines (real TDD approach)
1957
+ - Development workflow
1958
+ - Code style and standards
1959
+ - Module architecture
1960
+ - Error handling
1961
+ - Documentation standards
1962
+ - Release process
1963
+
1964
+ ### Quick Development Setup
1965
+
1966
+ ```bash
1967
+ git clone https://github.com/MangoAppsInc/mangoapps-ex-sdk-ruby.git
1968
+ cd mangoapps-ex-sdk-ruby
1969
+ cp .env.example .env
1970
+ # Edit .env with your MangoApps credentials (for testing only)
1971
+ bundle install
1972
+ ```
1973
+
1974
+ ### Testing the SDK
1975
+
1976
+ This SDK uses **real TDD** - no mocking, only actual OAuth testing:
1977
+
1978
+ ```bash
1979
+ # Get OAuth token (first time or when expired)
1980
+ ./run_auth.sh
1981
+
1982
+ # Run tests
1983
+ ./run_tests.sh
1984
+
1985
+ # Run specific module tests
1986
+ ./run_tests.sh learn
1987
+ ./run_tests.sh users
1988
+ ./run_tests.sh recognitions
1989
+ ./run_tests.sh notifications
1990
+ ./run_tests.sh feeds
1991
+ ./run_tests.sh posts
1992
+
1993
+ # Interactive testing
1994
+ ./run_irb.sh
1995
+ ```
1996
+
1997
+ ### Current API Coverage
1998
+
1999
+ - โœ… **Learn Module**: Course catalog, categories, course details, and my learning (4 endpoints)
2000
+ - โœ… **Users Module**: User profile and authentication (1 endpoint)
2001
+ - โœ… **Recognitions Module**: Award categories, get awards list, get profile awards, get team awards, get award feeds, core value tags, leaderboard info, and gift cards (8 endpoints)
2002
+ - โœ… **Notifications Module**: My priority items for requests, events, quizzes, surveys, tasks, and todos, and user notifications with unread counts (2 endpoints)
2003
+ - โœ… **Feeds Module**: User activity feeds with unread counts and feed details (1 endpoint)
2004
+ - โœ… **Posts Module**: Get all posts with filtering options and get post by ID (2 endpoints)
2005
+ - โœ… **Libraries Module**: Get user's document libraries with categories and items, get library categories by ID, and get library items by library and category ID (3 endpoints)
2006
+ - โœ… **Trackers Module**: Get user's trackers with submission dates and conversation details (1 endpoint)
2007
+ - โœ… **Attachments Module**: Get user's file folders with permissions and metadata, and get files and folders inside specific folders (2 endpoints)
2008
+ - โœ… **Tasks Module**: Get user's tasks with filtering, pagination, and detailed task information, and get detailed information for specific tasks (2 endpoints)
2009
+ - โœ… **Wikis Module**: Get user's wikis with filtering, pagination, and detailed wiki information, and get detailed information for specific wikis (2 endpoints)
2010
+ - โœ… **Error Handling**: Comprehensive error logging and testing
2011
+ - โœ… **OAuth Flow**: Token management, refresh, and userinfo endpoint
2012
+ - โœ… **Internal API Auth**: Alternative authentication using API keys
2013
+ - โœ… **Auto Detection**: Automatic authentication method detection and prioritization
2014
+ - โœ… **Pagination Support**: Optional page and limit parameters for all list APIs
2015
+ - โœ… **HTTPS Security**: Automatic HTTPS enforcement with redirect handling
2016
+
2017
+ **Total: 29 API endpoints across 11 modules + OAuth + Internal API + Auto Detection + Pagination**
2018
+
2019
+ ## Contributing
2020
+
2021
+ 1. Fork the repository
2022
+ 2. Create a feature branch
2023
+ 3. Write real tests first (no mocking)
2024
+ 4. Implement the feature
2025
+ 5. Ensure all tests pass
2026
+ 6. Submit a pull request
2027
+
2028
+ ## License
2029
+
2030
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
2031
+
2032
+ ## Support
2033
+
2034
+ - **Documentation**: [RubyDoc](https://rubydoc.info/gems/mangoapps-ex-sdk-ruby)
2035
+ - **Issues**: [GitHub Issues](https://github.com/MangoAppsInc/mangoapps-ex-sdk-ruby/issues)
2036
+ - **MangoApps**: [Official Website](https://www.mangoapps.com)
2037
+
2038
+ ## Changelog
2039
+
2040
+ See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.