n8n-nodes-smartschool 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,6 +57,8 @@ For **Portal** operations, create a **Smartschool Portal Login** credential with
57
57
 
58
58
  Presence operations require the portal user ID to fetch a session token, so you will need to provide a `Presence User ID` when calling the presence endpoints.
59
59
 
60
+ The legacy Playwright-based session flow was removed from the Portal node UI, but it is still documented in `docs/portal-legacy-session.md`.
61
+
60
62
  These operations rely on Playwright. Install browser dependencies once with:
61
63
 
62
64
  ```bash
@@ -8,6 +8,23 @@ class SmartschoolPortalApi {
8
8
  this.icon = { light: 'file:../icons/smartschool.logo.svg', dark: 'file:../icons/smartschool.logo.dark.svg' };
9
9
  this.documentationUrl = 'https://schoolsync.gitbook.io/smartschool-kit';
10
10
  this.properties = [
11
+ {
12
+ displayName: 'Login Service URL',
13
+ name: 'loginServiceUrl',
14
+ type: 'string',
15
+ default: 'http://localhost:8000/api/v1/portal/login/',
16
+ description: 'Endpoint that generates Smartschool portal sessions',
17
+ required: true,
18
+ },
19
+ {
20
+ displayName: 'Login Service API Key',
21
+ name: 'loginServiceApiKey',
22
+ type: 'string',
23
+ typeOptions: { password: true },
24
+ default: '',
25
+ description: 'API key for the login service',
26
+ required: true,
27
+ },
11
28
  {
12
29
  displayName: 'Smartschool Domain',
13
30
  placeholder: 'school.smartschool.be',
@@ -1 +1 @@
1
- {"version":3,"file":"SmartschoolPortalApi.credentials.js","sourceRoot":"","sources":["../../credentials/SmartschoolPortalApi.credentials.ts"],"names":[],"mappings":";;;AAEA,MAAa,oBAAoB;IAAjC;QACC,SAAI,GAAG,sBAAsB,CAAC;QAC9B,gBAAW,GAAG,wBAAwB,CAAC;QACvC,SAAI,GAAS,EAAE,KAAK,EAAE,oCAAoC,EAAE,IAAI,EAAE,yCAAyC,EAAE,CAAC;QAC9G,qBAAgB,GAAG,+CAA+C,CAAC;QACnE,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,oBAAoB;gBACjC,WAAW,EAAE,uBAAuB;gBACpC,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,mBAAmB;gBAChC,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,4BAA4B;gBACzC,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;SACD,CAAC;IACH,CAAC;CAAA;AA9CD,oDA8CC"}
1
+ {"version":3,"file":"SmartschoolPortalApi.credentials.js","sourceRoot":"","sources":["../../credentials/SmartschoolPortalApi.credentials.ts"],"names":[],"mappings":";;;AAEA,MAAa,oBAAoB;IAAjC;QACC,SAAI,GAAG,sBAAsB,CAAC;QAC9B,gBAAW,GAAG,wBAAwB,CAAC;QACvC,SAAI,GAAS,EAAE,KAAK,EAAE,oCAAoC,EAAE,IAAI,EAAE,yCAAyC,EAAE,CAAC;QAC9G,qBAAgB,GAAG,+CAA+C,CAAC;QACnE,eAAU,GAAsB;YAC/B;gBACC,WAAW,EAAE,mBAAmB;gBAChC,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,4CAA4C;gBACrD,WAAW,EAAE,qDAAqD;gBAClE,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,uBAAuB;gBACpC,IAAI,EAAE,oBAAoB;gBAC1B,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,+BAA+B;gBAC5C,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,oBAAoB;gBACjC,WAAW,EAAE,uBAAuB;gBACpC,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,IAAI;aACd;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,mBAAmB;gBAChC,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC/B,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;YACD;gBACC,WAAW,EAAE,4BAA4B;gBACzC,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,EAAE;aACX;SACD,CAAC;IACH,CAAC;CAAA;AA/DD,oDA+DC"}
@@ -358,9 +358,15 @@ class SmartSchool {
358
358
  {
359
359
  name: 'Generate Session',
360
360
  value: 'generateSession',
361
- description: 'Automatic login is not supported; supply PHPSESSID manually instead',
361
+ description: 'Generate a portal session via the external login service',
362
362
  action: 'Generate session',
363
363
  },
364
+ {
365
+ name: 'Generate Session (Legacy)',
366
+ value: 'generateSessionLegacy',
367
+ description: 'Generate a portal session using Playwright (self-hosted only)',
368
+ action: 'Generate session legacy',
369
+ },
364
370
  {
365
371
  name: 'Get Course List (Portal)',
366
372
  value: 'getPortalCourses',
@@ -2108,7 +2114,7 @@ class SmartSchool {
2108
2114
  };
2109
2115
  }
2110
2116
  async execute() {
2111
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
2117
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
2112
2118
  const items = this.getInputData();
2113
2119
  const returnData = [];
2114
2120
  let accesscode = null;
@@ -2244,7 +2250,68 @@ class SmartSchool {
2244
2250
  return await parsePortalJson(response, `gradebook ${endpoint}`);
2245
2251
  };
2246
2252
  if (operation === 'generateSession') {
2247
- const result = await smscHeadlessLogin_1.smscHeadlessLogin.call(this, sessionCreds);
2253
+ const loginServiceUrl = (_a = sessionCreds.loginServiceUrl) !== null && _a !== void 0 ? _a : '';
2254
+ const loginServiceApiKey = (_b = sessionCreds.loginServiceApiKey) !== null && _b !== void 0 ? _b : '';
2255
+ if (!loginServiceUrl || !loginServiceApiKey) {
2256
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Login service URL and API key are required to generate a portal session.', { itemIndex });
2257
+ }
2258
+ const loginPayload = {
2259
+ domain: sessionCreds.domain,
2260
+ username: sessionCreds.username,
2261
+ password: sessionCreds.password,
2262
+ };
2263
+ if (sessionCreds.birthdate) {
2264
+ loginPayload.birthdate = sessionCreds.birthdate;
2265
+ }
2266
+ if (sessionCreds.totpSecret) {
2267
+ loginPayload.totpSecret = sessionCreds.totpSecret;
2268
+ }
2269
+ let result;
2270
+ try {
2271
+ result = (await this.helpers.httpRequest({
2272
+ method: 'POST',
2273
+ url: loginServiceUrl,
2274
+ headers: {
2275
+ 'Content-Type': 'application/json',
2276
+ 'X-API-Key': loginServiceApiKey,
2277
+ },
2278
+ body: loginPayload,
2279
+ json: true,
2280
+ }));
2281
+ }
2282
+ catch (error) {
2283
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Login service request failed: ${error.message}`, { itemIndex });
2284
+ }
2285
+ const phpSessId = result.phpSessId;
2286
+ const userId = result.userId;
2287
+ const cookieHeader = result.cookieHeader;
2288
+ if (!phpSessId || !cookieHeader) {
2289
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Login service did not return phpSessId or cookieHeader.', { itemIndex });
2290
+ }
2291
+ const userIdRaw = userId ? String(userId) : '';
2292
+ let userIdNumeric = null;
2293
+ if (userIdRaw) {
2294
+ const parts = userIdRaw.split('_');
2295
+ const candidate = parts.length >= 2 ? parts[1] : userIdRaw;
2296
+ const parsed = Number(candidate);
2297
+ if (!Number.isNaN(parsed)) {
2298
+ userIdNumeric = parsed;
2299
+ }
2300
+ }
2301
+ returnData.push({
2302
+ json: {
2303
+ success: true,
2304
+ phpSessId,
2305
+ userId,
2306
+ cookieHeader,
2307
+ userIdNumeric,
2308
+ },
2309
+ pairedItem: { item: itemIndex },
2310
+ });
2311
+ continue;
2312
+ }
2313
+ if (operation === 'generateSessionLegacy') {
2314
+ const result = await smscHeadlessLogin_1.smscHeadlessLoginLegacy.call(this, sessionCreds);
2248
2315
  const userIdRaw = result.userId ? String(result.userId) : '';
2249
2316
  let userIdNumeric = null;
2250
2317
  if (userIdRaw) {
@@ -2302,17 +2369,16 @@ class SmartSchool {
2302
2369
  if (operation === 'getPlannerElements') {
2303
2370
  const phpSessId = this.getNodeParameter('phpSessId', itemIndex);
2304
2371
  const userIdParam = this.getNodeParameter('userId', itemIndex, '');
2305
- const inputUserId = (_c = (_b = (_a = this.getInputData()[itemIndex]) === null || _a === void 0 ? void 0 : _a.json) === null || _b === void 0 ? void 0 : _b.userId) !== null && _c !== void 0 ? _c : '';
2372
+ const inputUserId = (_e = (_d = (_c = this.getInputData()[itemIndex]) === null || _c === void 0 ? void 0 : _c.json) === null || _d === void 0 ? void 0 : _d.userId) !== null && _e !== void 0 ? _e : '';
2306
2373
  const userId = userIdParam || inputUserId;
2307
- if (!userId) {
2308
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'User ID is required for planner elements. Provide it in the node parameters or pass it from Generate Session.', { itemIndex });
2309
- }
2310
2374
  const fromDate = this.getNodeParameter('fromDate', itemIndex);
2311
2375
  const toDate = this.getNodeParameter('toDate', itemIndex);
2312
- const plannerUrl = `https://${normalizedDomain}/planner/api/v1/planned-elements/user/${userId}?from=${fromDate}&to=${toDate}`;
2376
+ const plannerUrl = userId
2377
+ ? `https://${normalizedDomain}/planner/api/v1/planned-elements/user/${userId}?from=${fromDate}&to=${toDate}`
2378
+ : `https://${normalizedDomain}/planner/api/v1/planned-elements?from=${fromDate}&to=${toDate}`;
2313
2379
  const response = await safeFetch_1.safeFetch.call(this, plannerUrl, {
2314
2380
  headers: {
2315
- cookie: buildCookieHeader(phpSessId),
2381
+ cookie: `PHPSESSID=${phpSessId}`,
2316
2382
  },
2317
2383
  });
2318
2384
  const data = await parsePortalJson(response, 'planner elements');
@@ -2576,26 +2642,26 @@ class SmartSchool {
2576
2642
  const hourId = hourEntry.hourID;
2577
2643
  const hourTitle = hourEntry.title;
2578
2644
  const classData = (await fetchPresenceClass(classId, hourId));
2579
- const pupils = ((_d = classData.pupils) !== null && _d !== void 0 ? _d : []);
2645
+ const pupils = ((_f = classData.pupils) !== null && _f !== void 0 ? _f : []);
2580
2646
  for (const pupil of pupils) {
2581
- const presences = ((_e = pupil.presence) !== null && _e !== void 0 ? _e : []);
2647
+ const presences = ((_g = pupil.presence) !== null && _g !== void 0 ? _g : []);
2582
2648
  for (const presence of presences) {
2583
- const code = ((_f = presence.code) !== null && _f !== void 0 ? _f : {});
2649
+ const code = ((_h = presence.code) !== null && _h !== void 0 ? _h : {});
2584
2650
  rows.push({
2585
2651
  classId,
2586
2652
  className,
2587
2653
  hourId,
2588
2654
  hourTitle,
2589
- presenceDate: (_g = presence.presenceDate) !== null && _g !== void 0 ? _g : presenceDateOnly,
2590
- pupilId: (_h = pupil.userID) !== null && _h !== void 0 ? _h : pupil.userId,
2655
+ presenceDate: (_j = presence.presenceDate) !== null && _j !== void 0 ? _j : presenceDateOnly,
2656
+ pupilId: (_k = pupil.userID) !== null && _k !== void 0 ? _k : pupil.userId,
2591
2657
  pupilName: pupil.name,
2592
2658
  pupilSurname: pupil.surname,
2593
- pupilFullName: (_j = pupil.nameBIN) !== null && _j !== void 0 ? _j : pupil.name,
2659
+ pupilFullName: (_l = pupil.nameBIN) !== null && _l !== void 0 ? _l : pupil.name,
2594
2660
  presenceId: presence.presenceID,
2595
- codeId: (_k = presence.codeID) !== null && _k !== void 0 ? _k : code.codeID,
2661
+ codeId: (_m = presence.codeID) !== null && _m !== void 0 ? _m : code.codeID,
2596
2662
  codeName: code.name,
2597
2663
  codeColor: code.color,
2598
- isOfficial: (_l = code.isOfficial) !== null && _l !== void 0 ? _l : presence.isOfficial,
2664
+ isOfficial: (_o = code.isOfficial) !== null && _o !== void 0 ? _o : presence.isOfficial,
2599
2665
  lastAuthorName: presence.lastAuthorName,
2600
2666
  lastAuthorUserIdentifier: presence.lastAuthorUserIdentifier,
2601
2667
  encodedAt: presence.date,
@@ -2657,12 +2723,12 @@ class SmartSchool {
2657
2723
  const mails = [];
2658
2724
  const startMailsJson = await fetchMailWithCommand(fetchInboxCommand);
2659
2725
  let moreMails = false;
2660
- const startActions = toArray(((_m = startMailsJson.server) === null || _m === void 0 ? void 0 : _m.response) &&
2726
+ const startActions = toArray(((_p = startMailsJson.server) === null || _p === void 0 ? void 0 : _p.response) &&
2661
2727
  startMailsJson.server.response.actions &&
2662
2728
  startMailsJson.server.response.actions
2663
2729
  .action);
2664
- const startMessages = toArray(((_p = (_o = startActions[0]) === null || _o === void 0 ? void 0 : _o.data) === null || _p === void 0 ? void 0 : _p.messages) &&
2665
- ((_q = startActions[0]) === null || _q === void 0 ? void 0 : _q.data).messages.message);
2730
+ const startMessages = toArray(((_r = (_q = startActions[0]) === null || _q === void 0 ? void 0 : _q.data) === null || _r === void 0 ? void 0 : _r.messages) &&
2731
+ ((_s = startActions[0]) === null || _s === void 0 ? void 0 : _s.data).messages.message);
2666
2732
  for (const msg of startMessages) {
2667
2733
  mails.push(msg);
2668
2734
  }
@@ -2674,12 +2740,12 @@ class SmartSchool {
2674
2740
  while (moreMails) {
2675
2741
  moreMails = false;
2676
2742
  const moreMailsJson = await fetchMailWithCommand(fetchMoreMailsCommand);
2677
- const moreActions = toArray(((_r = moreMailsJson.server) === null || _r === void 0 ? void 0 : _r.response) &&
2743
+ const moreActions = toArray(((_t = moreMailsJson.server) === null || _t === void 0 ? void 0 : _t.response) &&
2678
2744
  moreMailsJson.server.response.actions &&
2679
2745
  moreMailsJson.server.response.actions
2680
2746
  .action);
2681
- const moreMessages = toArray(((_t = (_s = moreActions[0]) === null || _s === void 0 ? void 0 : _s.data) === null || _t === void 0 ? void 0 : _t.messages) &&
2682
- ((_u = moreActions[0]) === null || _u === void 0 ? void 0 : _u.data).messages.message);
2747
+ const moreMessages = toArray(((_v = (_u = moreActions[0]) === null || _u === void 0 ? void 0 : _u.data) === null || _v === void 0 ? void 0 : _v.messages) &&
2748
+ ((_w = moreActions[0]) === null || _w === void 0 ? void 0 : _w.data).messages.message);
2683
2749
  for (const msg of moreMessages) {
2684
2750
  mails.push(msg);
2685
2751
  }
@@ -2709,10 +2775,10 @@ class SmartSchool {
2709
2775
  </request>`;
2710
2776
  const mailJson = await fetchMailWithCommand(fetchMailCommand);
2711
2777
  let mail = null;
2712
- const mailActions = toArray(((_v = mailJson.server) === null || _v === void 0 ? void 0 : _v.response) &&
2778
+ const mailActions = toArray(((_x = mailJson.server) === null || _x === void 0 ? void 0 : _x.response) &&
2713
2779
  mailJson.server.response.actions &&
2714
2780
  mailJson.server.response.actions.action);
2715
- const msg = (_x = (_w = mailActions[0]) === null || _w === void 0 ? void 0 : _w.data) === null || _x === void 0 ? void 0 : _x.message;
2781
+ const msg = (_z = (_y = mailActions[0]) === null || _y === void 0 ? void 0 : _y.data) === null || _z === void 0 ? void 0 : _z.message;
2716
2782
  if (msg) {
2717
2783
  const body = msg.body;
2718
2784
  msg.body = body ? body.replace(/\n/g, '') : body;
@@ -2775,8 +2841,8 @@ class SmartSchool {
2775
2841
  }
2776
2842
  if (operation === 'saveGroup' || operation === 'saveClass') {
2777
2843
  const details = this.getNodeParameter('groupClassDetails', itemIndex, {});
2778
- const required = ((_y = details.required) !== null && _y !== void 0 ? _y : {});
2779
- const optional = ((_z = details.optional) !== null && _z !== void 0 ? _z : {});
2844
+ const required = ((_0 = details.required) !== null && _0 !== void 0 ? _0 : {});
2845
+ const optional = ((_1 = details.optional) !== null && _1 !== void 0 ? _1 : {});
2780
2846
  const payload = {
2781
2847
  name: required.name,
2782
2848
  desc: required.desc,
@@ -2963,10 +3029,10 @@ class SmartSchool {
2963
3029
  }
2964
3030
  if (operation === 'saveUser') {
2965
3031
  const profile = this.getNodeParameter('userProfile', itemIndex, {});
2966
- const required = ((_0 = profile.required) !== null && _0 !== void 0 ? _0 : {});
2967
- const optional = ((_1 = profile.optional) !== null && _1 !== void 0 ? _1 : {});
2968
- const custom = ((_2 = profile.custom) !== null && _2 !== void 0 ? _2 : {});
2969
- const customFieldsRaw = ((_3 = custom.customFields) !== null && _3 !== void 0 ? _3 : '');
3032
+ const required = ((_2 = profile.required) !== null && _2 !== void 0 ? _2 : {});
3033
+ const optional = ((_3 = profile.optional) !== null && _3 !== void 0 ? _3 : {});
3034
+ const custom = ((_4 = profile.custom) !== null && _4 !== void 0 ? _4 : {});
3035
+ const customFieldsRaw = ((_5 = custom.customFields) !== null && _5 !== void 0 ? _5 : '');
2970
3036
  let customFields = {};
2971
3037
  if (customFieldsRaw) {
2972
3038
  try {
@@ -3315,7 +3381,7 @@ class SmartSchool {
3315
3381
  coaccount,
3316
3382
  copyToLVS,
3317
3383
  };
3318
- const attachmentValues = ((_4 = attachmentCollection.attachment) !== null && _4 !== void 0 ? _4 : []);
3384
+ const attachmentValues = ((_6 = attachmentCollection.attachment) !== null && _6 !== void 0 ? _6 : []);
3319
3385
  const cleanedAttachments = attachmentValues.filter((entry) => (entry === null || entry === void 0 ? void 0 : entry.filename) && (entry === null || entry === void 0 ? void 0 : entry.filedata));
3320
3386
  if (cleanedAttachments.length) {
3321
3387
  payload.attachments = JSON.stringify(cleanedAttachments);