@rjsebening/n8n-nodes-learningsuite 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/dist/nodes/LearningSuite/descriptions/calendarEvent.properties.js +27 -3
- package/dist/nodes/LearningSuite/descriptions/course.properties.js +1 -1
- package/dist/nodes/LearningSuite/descriptions/customFields.properties.js +17 -0
- package/dist/nodes/LearningSuite/descriptions/hub.properties.js +14 -1
- package/dist/nodes/LearningSuite/descriptions/member.properties.js +62 -6
- package/dist/nodes/LearningSuite/descriptions/trigger.instant.properties.js +1 -0
- package/dist/nodes/LearningSuite/descriptions/webhook.properties.js +1 -0
- package/dist/nodes/LearningSuite/descriptions/webhook.sampleData.properties.js +2 -0
- package/dist/nodes/LearningSuite/execute/calendarEvent.handlers.d.ts +1 -0
- package/dist/nodes/LearningSuite/execute/calendarEvent.handlers.js +17 -0
- package/dist/nodes/LearningSuite/execute/customFields.handlers.js +7 -1
- package/dist/nodes/LearningSuite/execute/hub.handlers.d.ts +1 -0
- package/dist/nodes/LearningSuite/execute/hub.handlers.js +5 -0
- package/dist/nodes/LearningSuite/execute/member.handlers.d.ts +1 -0
- package/dist/nodes/LearningSuite/execute/member.handlers.js +46 -5
- package/dist/nodes/LearningSuite/shared/parsing.d.ts +2 -0
- package/dist/nodes/LearningSuite/shared/parsing.js +12 -0
- package/dist/nodes/LearningSuite/shared/request.d.ts +2 -1
- package/dist/nodes/LearningSuite/shared/request.js +6 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,18 +21,20 @@ The node uses the public LearningSuite API. LearningSuite, its trademarks, and i
|
|
|
21
21
|
## 🚀 Features
|
|
22
22
|
|
|
23
23
|
- **16 resources** fully supported (Member, Course, Group, Bundle, Hub, Module, Community, Calendar Event, Custom Fields, Popup, Webhook, Role, User, Team Member, AI, API Call)
|
|
24
|
-
- **
|
|
24
|
+
- **103 action endpoints** for maximum flexibility
|
|
25
25
|
- **Instant trigger (webhook-based)** with 19 event types for real-time automation
|
|
26
26
|
- **Polling trigger** with 11 event types for scheduled polling
|
|
27
27
|
- **Flexible API call** resource for custom endpoints
|
|
28
28
|
|
|
29
29
|
## 📋 Supported Operations
|
|
30
30
|
|
|
31
|
-
### 👤 **Member** (
|
|
31
|
+
### 👤 **Member** (21 operations)
|
|
32
32
|
|
|
33
33
|
- Get member by email, get member by ID, get many members, create a member, update a member, delete a member, find or create member
|
|
34
34
|
- Activate deactivate member, add member to courses, remove member from courses, add member to bundles, remove member from bundles
|
|
35
|
-
- Get member courses, get member course info, get member bundles
|
|
35
|
+
- Get member courses, get member course info, get member bundles, get member AI credits
|
|
36
|
+
- Get many members filters by creation date (`Created After` / `Created Before`)
|
|
37
|
+
- Add member to bundles also updates existing accesses: end date, remove end date, pause until a date, unlimited pause, resume
|
|
36
38
|
- Get course progress, get module progress, get lesson progress, set course progress, reset course progress
|
|
37
39
|
|
|
38
40
|
### 📚 **Course** (9 operations)
|
|
@@ -51,9 +53,9 @@ The node uses the public LearningSuite API. LearningSuite, its trademarks, and i
|
|
|
51
53
|
|
|
52
54
|
- Get bundles, get bundle members
|
|
53
55
|
|
|
54
|
-
### 🏠 **Hub** (
|
|
56
|
+
### 🏠 **Hub** (7 operations)
|
|
55
57
|
|
|
56
|
-
- Get many hubs, get hub templates, get hub template variables, create a hub, add hub access, remove hub access
|
|
58
|
+
- Get many hubs, get hub templates, get hub template variables, create a hub, delete a hub, add hub access, remove hub access
|
|
57
59
|
|
|
58
60
|
### 📖 **Module** (3 operations)
|
|
59
61
|
|
|
@@ -65,9 +67,9 @@ The node uses the public LearningSuite API. LearningSuite, its trademarks, and i
|
|
|
65
67
|
- Create community post, add comment to post
|
|
66
68
|
- Assign badges to member, remove badges from member
|
|
67
69
|
|
|
68
|
-
### 🗓️ **Calendar Event** (
|
|
70
|
+
### 🗓️ **Calendar Event** (5 operations)
|
|
69
71
|
|
|
70
|
-
- Get many calendar events, create a calendar event, update a calendar event, delete a calendar event
|
|
72
|
+
- Get many calendar events, create a calendar event, update a calendar event, delete a calendar event, get calendar event analytics
|
|
71
73
|
- Reminders (push/email), per-phase link stages (before/during/after), access via members/groups/bundles
|
|
72
74
|
- Update maps fields from incoming items automatically
|
|
73
75
|
|
|
@@ -294,6 +296,7 @@ The Calendar Event resource works on event **series**, never on single dates —
|
|
|
294
296
|
- **Clearing a value**: set the field to the expression `{{ null }}`. The API allows this for Description, Duration, and End Date only.
|
|
295
297
|
- **Link Stages** replace the single link with before/during/after phases. As soon as any phase is set, the During phase is required.
|
|
296
298
|
- **Access** (members, groups, bundles) can only be set when creating an event; the update endpoint does not accept it.
|
|
299
|
+
- **Get Analytics** reports per member what they did on one occurrence: `infoOpen`, `beforeLinkClick`, `liveJoin`, `afterLinkClick`. The `Occurrence Date` is the occurrence's original date, even if it was moved. By default the whole target group is returned, members with an empty `actions` list are the no-shows; `Only Members With Actions` returns just the participants. Page through with `Limit`/`Offset` (max. 100) until `members` comes back empty.
|
|
297
300
|
|
|
298
301
|
### ⚡ Instant Webhook Trigger Setup
|
|
299
302
|
|
|
@@ -133,6 +133,12 @@ exports.calendarEventProperties = [
|
|
|
133
133
|
description: 'Delete a calendar event series including all of its occurrences',
|
|
134
134
|
action: 'Delete a calendar event',
|
|
135
135
|
},
|
|
136
|
+
{
|
|
137
|
+
name: 'Get Analytics',
|
|
138
|
+
value: 'getAnalytics',
|
|
139
|
+
description: 'Get how members engaged with one occurrence of a calendar event series (info opened, links clicked, live joined)',
|
|
140
|
+
action: 'Get calendar event analytics',
|
|
141
|
+
},
|
|
136
142
|
{
|
|
137
143
|
name: 'Get Many',
|
|
138
144
|
value: 'getAll',
|
|
@@ -170,7 +176,7 @@ exports.calendarEventProperties = [
|
|
|
170
176
|
name: 'limit',
|
|
171
177
|
type: 'number',
|
|
172
178
|
description: 'Max number of results to return',
|
|
173
|
-
displayOptions: show('getAll'),
|
|
179
|
+
displayOptions: show('getAll', 'getAnalytics'),
|
|
174
180
|
typeOptions: { minValue: 1, maxValue: 100 },
|
|
175
181
|
default: 50,
|
|
176
182
|
},
|
|
@@ -178,7 +184,7 @@ exports.calendarEventProperties = [
|
|
|
178
184
|
displayName: 'Offset',
|
|
179
185
|
name: 'offset',
|
|
180
186
|
type: 'number',
|
|
181
|
-
displayOptions: show('getAll'),
|
|
187
|
+
displayOptions: show('getAll', 'getAnalytics'),
|
|
182
188
|
typeOptions: { minValue: 0 },
|
|
183
189
|
default: 0,
|
|
184
190
|
description: 'Number of results to skip for pagination',
|
|
@@ -188,11 +194,29 @@ exports.calendarEventProperties = [
|
|
|
188
194
|
name: 'calendarEventSeriesId',
|
|
189
195
|
type: 'options',
|
|
190
196
|
typeOptions: { loadOptionsMethod: 'calendarEvent_getCalendarEvents' },
|
|
191
|
-
displayOptions: show('update', 'delete'),
|
|
197
|
+
displayOptions: show('update', 'delete', 'getAnalytics'),
|
|
192
198
|
default: '',
|
|
193
199
|
required: true,
|
|
194
200
|
description: 'ID of the calendar event series. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
195
201
|
},
|
|
202
|
+
{
|
|
203
|
+
displayName: 'Occurrence Date',
|
|
204
|
+
name: 'occurrenceDate',
|
|
205
|
+
type: 'string',
|
|
206
|
+
placeholder: '2026-07-20',
|
|
207
|
+
displayOptions: show('getAnalytics'),
|
|
208
|
+
default: '',
|
|
209
|
+
required: true,
|
|
210
|
+
description: "The occurrence to report on (YYYY-MM-DD). Use the occurrence's original date in the event's time zone, even if it was later moved to another day.",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
displayName: 'Only Members With Actions',
|
|
214
|
+
name: 'onlyWithActions',
|
|
215
|
+
type: 'boolean',
|
|
216
|
+
displayOptions: show('getAnalytics'),
|
|
217
|
+
default: false,
|
|
218
|
+
description: 'Whether to return only members with at least one recorded action. Leave off to also get the no-shows (members with an empty actions list).',
|
|
219
|
+
},
|
|
196
220
|
{
|
|
197
221
|
displayName: 'Name',
|
|
198
222
|
name: 'name',
|
|
@@ -268,7 +268,7 @@ exports.courseProperties = [
|
|
|
268
268
|
name: 'videoUrl',
|
|
269
269
|
type: 'string',
|
|
270
270
|
default: '',
|
|
271
|
-
description: 'A direct-downloadable video URL to be inserted as the first element on the lesson page.
|
|
271
|
+
description: 'A direct-downloadable video URL to be inserted as the first element on the lesson page. Public Google Drive or Dropbox share links work as well. The video is imported asynchronously; if no explicit thumbnail is set, the first frame of the video will be used as thumbnail.',
|
|
272
272
|
},
|
|
273
273
|
],
|
|
274
274
|
},
|
|
@@ -140,6 +140,23 @@ exports.customFieldsProperties = [
|
|
|
140
140
|
},
|
|
141
141
|
},
|
|
142
142
|
},
|
|
143
|
+
{
|
|
144
|
+
displayName: 'Field Key Names or IDs',
|
|
145
|
+
name: 'fieldKeys',
|
|
146
|
+
type: 'multiOptions',
|
|
147
|
+
default: [],
|
|
148
|
+
description: 'Only return the definitions with these keys (max. 50). Leave empty to return all. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
149
|
+
typeOptions: {
|
|
150
|
+
loadOptionsMethod: 'customFields_getDefinitions',
|
|
151
|
+
loadOptionsDependsOn: ['customFieldCardId'],
|
|
152
|
+
},
|
|
153
|
+
displayOptions: {
|
|
154
|
+
show: {
|
|
155
|
+
resource: ['customFields'],
|
|
156
|
+
operation: ['getDefinitions'],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
143
160
|
{
|
|
144
161
|
displayName: 'Card Name or ID',
|
|
145
162
|
name: 'customFieldCardId',
|
|
@@ -11,6 +11,12 @@ exports.hubProperties = [
|
|
|
11
11
|
displayOptions: { show: { resource: ['hub'] } },
|
|
12
12
|
options: [
|
|
13
13
|
{ name: 'Create Hub', value: 'create', description: 'Create a new hub from a template', action: 'Create a hub' },
|
|
14
|
+
{
|
|
15
|
+
name: 'Delete Hub',
|
|
16
|
+
value: 'delete',
|
|
17
|
+
description: 'Permanently delete a hub or hub template',
|
|
18
|
+
action: 'Delete a hub',
|
|
19
|
+
},
|
|
14
20
|
{
|
|
15
21
|
name: 'Get Hub Template Variables',
|
|
16
22
|
value: 'getTemplateVariables',
|
|
@@ -42,12 +48,19 @@ exports.hubProperties = [
|
|
|
42
48
|
displayName: 'Hub Name or ID',
|
|
43
49
|
name: 'hubId',
|
|
44
50
|
type: 'options',
|
|
45
|
-
displayOptions: { show: { resource: ['hub'], operation: ['addAccess', 'removeAccess'] } },
|
|
51
|
+
displayOptions: { show: { resource: ['hub'], operation: ['addAccess', 'removeAccess', 'delete'] } },
|
|
46
52
|
typeOptions: { loadOptionsMethod: 'hub_getHubs' },
|
|
47
53
|
default: '',
|
|
48
54
|
required: true,
|
|
49
55
|
description: 'ID of the hub. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
50
56
|
},
|
|
57
|
+
{
|
|
58
|
+
displayName: 'The hub is deleted <b>permanently</b>. Hub templates can be deleted as well - pass the template ID via expression.',
|
|
59
|
+
name: 'deleteNotice',
|
|
60
|
+
type: 'notice',
|
|
61
|
+
displayOptions: { show: { resource: ['hub'], operation: ['delete'] } },
|
|
62
|
+
default: '',
|
|
63
|
+
},
|
|
51
64
|
{
|
|
52
65
|
displayName: 'You can grant/remove access via <b>Members</b>, <b>Groups</b>, or <b>Bundles</b> – or in combination. At least one selection is required.',
|
|
53
66
|
name: 'notice',
|
|
@@ -19,7 +19,7 @@ exports.memberProperties = [
|
|
|
19
19
|
{
|
|
20
20
|
name: 'Add Bundles to Member',
|
|
21
21
|
value: 'addToBundles',
|
|
22
|
-
description: 'Grant
|
|
22
|
+
description: 'Grant bundles to a member or update existing bundle accesses (dates, pause, resume)',
|
|
23
23
|
action: 'Add member to bundles',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
@@ -60,6 +60,12 @@ exports.memberProperties = [
|
|
|
60
60
|
description: 'List members with optional filters',
|
|
61
61
|
action: 'Get many members',
|
|
62
62
|
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Get Member AI Credits',
|
|
65
|
+
value: 'getAiCredits',
|
|
66
|
+
description: 'Get the AI credit limits of a member with current usage',
|
|
67
|
+
action: 'Get member AI credits',
|
|
68
|
+
},
|
|
63
69
|
{
|
|
64
70
|
name: 'Get Member Bundles',
|
|
65
71
|
value: 'getBundles',
|
|
@@ -136,6 +142,7 @@ exports.memberProperties = [
|
|
|
136
142
|
'addToBundles',
|
|
137
143
|
'removeFromBundles',
|
|
138
144
|
'getBundles',
|
|
145
|
+
'getAiCredits',
|
|
139
146
|
],
|
|
140
147
|
},
|
|
141
148
|
},
|
|
@@ -354,6 +361,20 @@ exports.memberProperties = [
|
|
|
354
361
|
placeholder: 'Add Option',
|
|
355
362
|
displayOptions: { show: { resource: ['member'], operation: ['getAll'] } },
|
|
356
363
|
options: [
|
|
364
|
+
{
|
|
365
|
+
displayName: 'Created After',
|
|
366
|
+
name: 'createdAtGte',
|
|
367
|
+
type: 'dateTime',
|
|
368
|
+
default: '',
|
|
369
|
+
description: 'Only include members created at or after this date',
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
displayName: 'Created Before',
|
|
373
|
+
name: 'createdAtLte',
|
|
374
|
+
type: 'dateTime',
|
|
375
|
+
default: '',
|
|
376
|
+
description: 'Only include members created at or before this date',
|
|
377
|
+
},
|
|
357
378
|
{
|
|
358
379
|
displayName: 'Days Not Logged In (GTE)',
|
|
359
380
|
name: 'daysNotLoggedInGte',
|
|
@@ -421,6 +442,13 @@ exports.memberProperties = [
|
|
|
421
442
|
required: true,
|
|
422
443
|
description: 'Select one or more bundles. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
423
444
|
},
|
|
445
|
+
{
|
|
446
|
+
displayName: 'If the member already has access to a bundle, that access is <b>updated</b>: only the options you set are changed. Accesses through groups are not affected.',
|
|
447
|
+
name: 'bundleAccessNotice',
|
|
448
|
+
type: 'notice',
|
|
449
|
+
displayOptions: { show: { resource: ['member'], operation: ['addToBundles'] } },
|
|
450
|
+
default: '',
|
|
451
|
+
},
|
|
424
452
|
{
|
|
425
453
|
displayName: 'Bundle Names or IDs',
|
|
426
454
|
name: 'bundles',
|
|
@@ -451,21 +479,49 @@ exports.memberProperties = [
|
|
|
451
479
|
name: 'accessUntilOverride',
|
|
452
480
|
type: 'dateTime',
|
|
453
481
|
default: '',
|
|
454
|
-
description: 'Override the date/time when access ends',
|
|
482
|
+
description: 'Override the date/time when access ends. The access duration of the bundle is ignored.',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
displayName: 'Disable Access Notification Email',
|
|
486
|
+
name: 'disableAccessNotificationEmail',
|
|
487
|
+
type: 'boolean',
|
|
488
|
+
default: false,
|
|
489
|
+
description: 'Whether the Access Notification Email option should be disabled',
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
displayName: 'Pause Until',
|
|
493
|
+
name: 'pauseUntil',
|
|
494
|
+
type: 'dateTime',
|
|
495
|
+
default: '',
|
|
496
|
+
description: 'Pause the access from today until this date (must be after today, only the date is used). An existing pause is replaced; the access duration is extended by the pause. Cannot be combined with Unlimited Pause or Resume Pause.',
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
displayName: 'Remove Access Until Override',
|
|
500
|
+
name: 'removeAccessUntilOverride',
|
|
501
|
+
type: 'boolean',
|
|
502
|
+
default: false,
|
|
503
|
+
description: "Whether to remove an existing end date. Access then follows the bundle's access duration, counted from the access given at date. Ignored if Access Until Override or Unlimited Access is set.",
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
displayName: 'Resume Pause',
|
|
507
|
+
name: 'resumePause',
|
|
508
|
+
type: 'boolean',
|
|
509
|
+
default: false,
|
|
510
|
+
description: 'Whether to end the current pause today. Does nothing if the access is not paused. Cannot be combined with Pause Until or Unlimited Pause.',
|
|
455
511
|
},
|
|
456
512
|
{
|
|
457
513
|
displayName: 'Unlimited Access',
|
|
458
514
|
name: 'unlimitedAccess',
|
|
459
515
|
type: 'boolean',
|
|
460
516
|
default: false,
|
|
461
|
-
description: 'Whether access never expires (overrides “Access Until Override”)',
|
|
517
|
+
description: 'Whether access never expires (overrides “Access Until Override” and clears an end date already stored on the access)',
|
|
462
518
|
},
|
|
463
519
|
{
|
|
464
|
-
displayName: '
|
|
465
|
-
name: '
|
|
520
|
+
displayName: 'Unlimited Pause',
|
|
521
|
+
name: 'unlimitedPause',
|
|
466
522
|
type: 'boolean',
|
|
467
523
|
default: false,
|
|
468
|
-
description: 'Whether the
|
|
524
|
+
description: 'Whether to pause the access from today until it is resumed. An existing pause is replaced. Cannot be combined with Pause Until or Resume Pause.',
|
|
469
525
|
},
|
|
470
526
|
],
|
|
471
527
|
},
|
|
@@ -161,6 +161,7 @@ exports.instantProperties = [
|
|
|
161
161
|
description: 'Filter by login type. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
162
162
|
options: [
|
|
163
163
|
{ name: 'All', value: '' },
|
|
164
|
+
{ name: 'App Email & Password', value: 'app-email-password' },
|
|
164
165
|
{ name: 'App Email Code', value: 'app-email-code' },
|
|
165
166
|
{ name: 'Auto Login', value: 'auto-login' },
|
|
166
167
|
{ name: 'Email & Password', value: 'email-password' },
|
|
@@ -218,6 +218,7 @@ exports.webhookProperties = [
|
|
|
218
218
|
description: 'Filter by login type. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
219
219
|
options: [
|
|
220
220
|
{ name: 'All', value: '' },
|
|
221
|
+
{ name: 'App Email & Password', value: 'app-email-password' },
|
|
221
222
|
{ name: 'App Email Code', value: 'app-email-code' },
|
|
222
223
|
{ name: 'Auto Login', value: 'auto-login' },
|
|
223
224
|
{ name: 'Email & Password', value: 'email-password' },
|
|
@@ -145,6 +145,8 @@ exports.webhookSampleDataProperties = [
|
|
|
145
145
|
type: 'options',
|
|
146
146
|
default: 'email-password',
|
|
147
147
|
options: [
|
|
148
|
+
{ name: 'App Email & Password', value: 'app-email-password' },
|
|
149
|
+
{ name: 'App Email Code', value: 'app-email-code' },
|
|
148
150
|
{ name: 'Auto Login', value: 'auto-login' },
|
|
149
151
|
{ name: 'Email & Password', value: 'email-password' },
|
|
150
152
|
{ name: 'Google', value: 'google' },
|
|
@@ -227,8 +227,25 @@ const deleteCalendarEvent = async (ctx, i) => {
|
|
|
227
227
|
}
|
|
228
228
|
return await shared_1.lsRequest.call(ctx, 'DELETE', `/calendar-events/${calendarEventSeriesId}`);
|
|
229
229
|
};
|
|
230
|
+
const getAnalytics = async (ctx, i) => {
|
|
231
|
+
const calendarEventSeriesId = String(ctx.getNodeParameter('calendarEventSeriesId', i, '') || '').trim();
|
|
232
|
+
if (!calendarEventSeriesId) {
|
|
233
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Please provide the ID of the calendar event.');
|
|
234
|
+
}
|
|
235
|
+
const occurrenceDate = String(ctx.getNodeParameter('occurrenceDate', i, '') || '').trim();
|
|
236
|
+
assertIsoDate(ctx, 'Occurrence Date', occurrenceDate);
|
|
237
|
+
const qs = {
|
|
238
|
+
occurrenceDate,
|
|
239
|
+
limit: ctx.getNodeParameter('limit', i, 50),
|
|
240
|
+
offset: ctx.getNodeParameter('offset', i, 0),
|
|
241
|
+
};
|
|
242
|
+
if (ctx.getNodeParameter('onlyWithActions', i, false) === true)
|
|
243
|
+
qs.onlyWithActions = true;
|
|
244
|
+
return await shared_1.lsRequest.call(ctx, 'GET', `/calendar-events/${calendarEventSeriesId}/analytics`, { qs });
|
|
245
|
+
};
|
|
230
246
|
exports.calendarEventHandlers = {
|
|
231
247
|
getAll,
|
|
248
|
+
getAnalytics,
|
|
232
249
|
create,
|
|
233
250
|
update,
|
|
234
251
|
delete: deleteCalendarEvent,
|
|
@@ -306,10 +306,16 @@ const getDefinitions = async (ctx, i) => {
|
|
|
306
306
|
const limit = ctx.getNodeParameter('limit', i, 15);
|
|
307
307
|
const offset = ctx.getNodeParameter('offset', i, 0);
|
|
308
308
|
const customFieldCardId = ctx.getNodeParameter('customFieldCardId', i, undefined);
|
|
309
|
+
const fieldKeys = (0, shared_1.toIdArray)(ctx.getNodeParameter('fieldKeys', i, []));
|
|
310
|
+
if (fieldKeys.length > 50) {
|
|
311
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'At most 50 field keys can be requested at once', { itemIndex: i });
|
|
312
|
+
}
|
|
309
313
|
const qs = { limit, offset };
|
|
310
314
|
if (customFieldCardId)
|
|
311
315
|
qs.customFieldCardId = customFieldCardId;
|
|
312
|
-
|
|
316
|
+
if (fieldKeys.length)
|
|
317
|
+
qs.fieldKeys = fieldKeys;
|
|
318
|
+
return await shared_1.lsRequest.call(ctx, 'GET', '/custom-fields/definitions', { qs, arrayFormat: 'repeat' });
|
|
313
319
|
};
|
|
314
320
|
const getCategories = async (ctx, i) => {
|
|
315
321
|
const limit = ctx.getNodeParameter('limit', i, 15);
|
|
@@ -49,6 +49,10 @@ const removeAccess = async (ctx, i) => {
|
|
|
49
49
|
body.bundleIds = bundleIds;
|
|
50
50
|
return await shared_1.lsRequest.call(ctx, 'DELETE', `/hub/${hubId}/access`, { body });
|
|
51
51
|
};
|
|
52
|
+
const deleteHub = async (ctx, i) => {
|
|
53
|
+
const hubId = ctx.getNodeParameter('hubId', i);
|
|
54
|
+
return await shared_1.lsRequest.call(ctx, 'DELETE', `/hub/${encodeURIComponent(hubId)}`);
|
|
55
|
+
};
|
|
52
56
|
const getTemplateVariables = async (ctx, i) => {
|
|
53
57
|
const templateId = ctx.getNodeParameter('templateId', i);
|
|
54
58
|
const raw = await shared_1.lsRequest.call(ctx, 'GET', `/hub-template/${templateId}/variables`);
|
|
@@ -59,6 +63,7 @@ exports.hubHandlers = {
|
|
|
59
63
|
getAll,
|
|
60
64
|
getTemplates,
|
|
61
65
|
create,
|
|
66
|
+
delete: deleteHub,
|
|
62
67
|
addAccess,
|
|
63
68
|
removeAccess,
|
|
64
69
|
getTemplateVariables,
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.memberHandlers = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
4
5
|
const shared_1 = require("../shared");
|
|
6
|
+
/** Converts an optional date option to IsoDateTime, failing loudly on unparsable input. */
|
|
7
|
+
function isoDateTimeOption(ctx, i, label, value) {
|
|
8
|
+
if (value === undefined || value === null || value === '')
|
|
9
|
+
return undefined;
|
|
10
|
+
const iso = (0, shared_1.toIsoDateTime)(value);
|
|
11
|
+
if (!iso) {
|
|
12
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `"${label}" is not a valid date/time: ${String(value)}`, {
|
|
13
|
+
itemIndex: i,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return iso;
|
|
17
|
+
}
|
|
5
18
|
const getByEmail = async (ctx, i) => {
|
|
6
19
|
const email = ctx.getNodeParameter('email', i);
|
|
7
20
|
const includeGroups = ctx.getNodeParameter('includeGroups', i);
|
|
@@ -36,6 +49,12 @@ const getAll = async (ctx, i) => {
|
|
|
36
49
|
qs.days_not_logged_in_gte = days;
|
|
37
50
|
if (includeNever !== undefined)
|
|
38
51
|
qs.include_never_logged_in = includeNever;
|
|
52
|
+
const createdAtGte = isoDateTimeOption(ctx, i, 'Created After', additionalOptions.createdAtGte);
|
|
53
|
+
const createdAtLte = isoDateTimeOption(ctx, i, 'Created Before', additionalOptions.createdAtLte);
|
|
54
|
+
if (createdAtGte)
|
|
55
|
+
qs.created_at_gte = createdAtGte;
|
|
56
|
+
if (createdAtLte)
|
|
57
|
+
qs.created_at_lte = createdAtLte;
|
|
39
58
|
return await shared_1.lsRequest.call(ctx, 'GET', '/members', { qs });
|
|
40
59
|
};
|
|
41
60
|
const create = async (ctx, i) => {
|
|
@@ -168,15 +187,32 @@ const addToBundles = async (ctx, i) => {
|
|
|
168
187
|
const bundles = (0, shared_1.toIdArray)(ctx.getNodeParameter('bundles', i));
|
|
169
188
|
const add = ctx.getNodeParameter('additionalOptions', i, {});
|
|
170
189
|
const body = { bundles };
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
190
|
+
const accessGivenAtOverride = isoDateTimeOption(ctx, i, 'Access Given At Override', add.accessGivenAtOverride);
|
|
191
|
+
if (accessGivenAtOverride)
|
|
192
|
+
body.accessGivenAtOverride = accessGivenAtOverride;
|
|
193
|
+
const accessUntilOverride = isoDateTimeOption(ctx, i, 'Access Until Override', add.accessUntilOverride);
|
|
174
194
|
if (add.unlimitedAccess === true) {
|
|
175
195
|
body.unlimitedAccess = true;
|
|
176
196
|
}
|
|
177
|
-
else if (
|
|
178
|
-
body.accessUntilOverride =
|
|
197
|
+
else if (accessUntilOverride) {
|
|
198
|
+
body.accessUntilOverride = accessUntilOverride;
|
|
199
|
+
}
|
|
200
|
+
else if (add.removeAccessUntilOverride === true) {
|
|
201
|
+
body.accessUntilOverride = null;
|
|
179
202
|
}
|
|
203
|
+
const pauseUntil = isoDateTimeOption(ctx, i, 'Pause Until', add.pauseUntil);
|
|
204
|
+
const pauseOptions = [pauseUntil, add.unlimitedPause === true, add.resumePause === true].filter(Boolean);
|
|
205
|
+
if (pauseOptions.length > 1) {
|
|
206
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Pause Until, Unlimited Pause and Resume Pause cannot be combined', {
|
|
207
|
+
itemIndex: i,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (pauseUntil)
|
|
211
|
+
body.pauseUntil = pauseUntil;
|
|
212
|
+
if (add.unlimitedPause === true)
|
|
213
|
+
body.unlimitedPause = true;
|
|
214
|
+
if (add.resumePause === true)
|
|
215
|
+
body.resumePause = true;
|
|
180
216
|
if (typeof add.disableAccessNotificationEmail === 'boolean') {
|
|
181
217
|
body.disableAccessNotificationEmail = add.disableAccessNotificationEmail;
|
|
182
218
|
}
|
|
@@ -193,6 +229,10 @@ const getBundles = async (ctx, i) => {
|
|
|
193
229
|
const memberId = ctx.getNodeParameter('memberId', i);
|
|
194
230
|
return await shared_1.lsRequest.call(ctx, 'GET', `/members/${memberId}/bundles`);
|
|
195
231
|
};
|
|
232
|
+
const getAiCredits = async (ctx, i) => {
|
|
233
|
+
const memberId = ctx.getNodeParameter('memberId', i);
|
|
234
|
+
return await shared_1.lsRequest.call(ctx, 'GET', `/members/${encodeURIComponent(memberId)}/ai-credits`);
|
|
235
|
+
};
|
|
196
236
|
const findOrCreate = async (ctx, i) => {
|
|
197
237
|
const email = ctx.getNodeParameter('email', i);
|
|
198
238
|
try {
|
|
@@ -240,5 +280,6 @@ exports.memberHandlers = {
|
|
|
240
280
|
addToBundles,
|
|
241
281
|
removeFromBundles,
|
|
242
282
|
getBundles,
|
|
283
|
+
getAiCredits,
|
|
243
284
|
findOrCreate,
|
|
244
285
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toIdArray = toIdArray;
|
|
4
|
+
exports.toIsoDateTime = toIsoDateTime;
|
|
4
5
|
function toIdArray(input) {
|
|
5
6
|
if (Array.isArray(input))
|
|
6
7
|
return input.map((v) => String(v).trim()).filter(Boolean);
|
|
@@ -13,3 +14,14 @@ function toIdArray(input) {
|
|
|
13
14
|
return [];
|
|
14
15
|
return [String(input).trim()].filter(Boolean);
|
|
15
16
|
}
|
|
17
|
+
const ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
18
|
+
/** Normalizes a date/time to the API's IsoDateTime format (time zone required). Returns undefined if unparsable. */
|
|
19
|
+
function toIsoDateTime(input) {
|
|
20
|
+
const value = String(input !== null && input !== void 0 ? input : '').trim();
|
|
21
|
+
if (!value)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (ISO_DATE_TIME.test(value))
|
|
24
|
+
return value;
|
|
25
|
+
const date = new Date(value);
|
|
26
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
27
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type IDataObject, type IHttpRequestOptions, type IExecuteFunctions, type IHookFunctions, type ILoadOptionsFunctions, type IWebhookFunctions, type ITriggerFunctions, type IPollFunctions } from 'n8n-workflow';
|
|
2
2
|
export type ApiThis = IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions | ITriggerFunctions | IPollFunctions;
|
|
3
3
|
export declare function normalizeEndpoint(endpoint: string): string;
|
|
4
|
-
export declare function lsRequest(this: ApiThis, method: IHttpRequestOptions['method'], endpoint: string, { qs, body }?: {
|
|
4
|
+
export declare function lsRequest(this: ApiThis, method: IHttpRequestOptions['method'], endpoint: string, { qs, body, arrayFormat, }?: {
|
|
5
5
|
qs?: IDataObject;
|
|
6
6
|
body?: IDataObject;
|
|
7
|
+
arrayFormat?: IHttpRequestOptions['arrayFormat'];
|
|
7
8
|
}): Promise<IDataObject | IDataObject[]>;
|
|
8
9
|
export declare function apiRequest(this: ApiThis, { method, path, qs, body, }: {
|
|
9
10
|
method: IHttpRequestOptions['method'];
|
|
@@ -17,7 +17,7 @@ function normalizeEndpoint(endpoint) {
|
|
|
17
17
|
}
|
|
18
18
|
return endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
19
19
|
}
|
|
20
|
-
async function requestCore({ method, endpoint, qs, body, }) {
|
|
20
|
+
async function requestCore({ method, endpoint, qs, body, arrayFormat, }) {
|
|
21
21
|
const url = normalizeEndpoint(endpoint);
|
|
22
22
|
const creds = (await this.getCredentials('learningSuiteApi'));
|
|
23
23
|
const baseURL = String(creds.baseUrl || '').trim();
|
|
@@ -32,6 +32,8 @@ async function requestCore({ method, endpoint, qs, body, }) {
|
|
|
32
32
|
};
|
|
33
33
|
if (qs && Object.keys(qs).length)
|
|
34
34
|
options.qs = qs;
|
|
35
|
+
if (arrayFormat)
|
|
36
|
+
options.arrayFormat = arrayFormat;
|
|
35
37
|
if (body && Object.keys(body).length && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(String(method).toUpperCase())) {
|
|
36
38
|
options.body = body;
|
|
37
39
|
}
|
|
@@ -41,13 +43,11 @@ async function requestCore({ method, endpoint, qs, body, }) {
|
|
|
41
43
|
return await this.helpers.httpRequestWithAuthentication.call(this, 'learningSuiteApi', options);
|
|
42
44
|
}
|
|
43
45
|
catch (error) {
|
|
44
|
-
throw error instanceof n8n_workflow_1.NodeApiError
|
|
45
|
-
? error
|
|
46
|
-
: new n8n_workflow_1.NodeApiError(this.getNode(), error);
|
|
46
|
+
throw error instanceof n8n_workflow_1.NodeApiError ? error : new n8n_workflow_1.NodeApiError(this.getNode(), error);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
async function lsRequest(method, endpoint, { qs, body } = {}) {
|
|
50
|
-
return requestCore.call(this, { method, endpoint, qs, body });
|
|
49
|
+
async function lsRequest(method, endpoint, { qs, body, arrayFormat, } = {}) {
|
|
50
|
+
return requestCore.call(this, { method, endpoint, qs, body, arrayFormat });
|
|
51
51
|
}
|
|
52
52
|
async function apiRequest({ method, path, qs, body, }) {
|
|
53
53
|
return requestCore.call(this, { method, endpoint: path, qs, body });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rjsebening/n8n-nodes-learningsuite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "n8n node for LearningSuite API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@n8n/node-cli": "^0.
|
|
57
|
+
"@n8n/node-cli": "^0.52.6",
|
|
58
58
|
"@typescript-eslint/parser": "^8.52.0",
|
|
59
59
|
"eslint": "^9.39.4",
|
|
60
60
|
"eslint-plugin-n8n-nodes-base": "^1.16.3",
|