@docbrasil/api-systemmanager 1.2.4 → 1.2.6

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/doc/api.md CHANGED
@@ -1287,6 +1287,7 @@ Admin Class for user, permission admin
1287
1287
  * [.emailExist(email, session)](#AdminUser+emailExist)
1288
1288
  * [.findByIdAndUpdate(userId, payload, session)](#AdminUser+findByIdAndUpdate) ⇒ <code>Promise.&lt;\*&gt;</code>
1289
1289
  * [.create(payload, session)](#AdminUser+create) ⇒ <code>Promise.&lt;object&gt;</code>
1290
+ * [.batchCreate(formData, session)](#AdminUser+batchCreate) ⇒ <code>Promise.&lt;object&gt;</code>
1290
1291
  * [.remove(userId, session)](#AdminUser+remove) ⇒ <code>Promise.&lt;object&gt;</code>
1291
1292
  * [.getChangePasswordGuid(email)](#AdminUser+getChangePasswordGuid) ⇒ <code>Promise.&lt;\*&gt;</code>
1292
1293
  * [.changePasswordGuid(Payload)](#AdminUser+changePasswordGuid) ⇒ <code>Promise.&lt;\*&gt;</code>
@@ -1468,6 +1469,72 @@ const payload = {
1468
1469
  const session = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
1469
1470
  await api.admin.user.create(payload, session);
1470
1471
  ```
1472
+ <a name="AdminUser+batchCreate"></a>
1473
+
1474
+ ### adminUser.batchCreate(formData, session) ⇒ <code>Promise.&lt;object&gt;</code>
1475
+ Batch-create users from an uploaded Excel (.xlsx) or CSV file.
1476
+
1477
+ Uploads the file as multipart/form-data. The server parses it, validates
1478
+ headers, de-duplicates emails, admits rows FIFO against the organization's
1479
+ user cap, and delegates the actual creation to the existing registration
1480
+ chain. Response is a per-row result array (created / existing / skipped).
1481
+
1482
+ Status codes:
1483
+ - 200 when at least one row was created or matched an existing user.
1484
+ - 422 (same JSON body shape) when EVERY row was skipped — callers
1485
+ should promote the 422 response body to a completed result, not an
1486
+ error. Axios throws on 422 by default, so catch and inspect
1487
+ `ex.response.data.results`.
1488
+ - 400 for structural failures (invalid_file, missing_columns, empty_file,
1489
+ too_many_rows) — `response.data.code` carries the machine-readable code.
1490
+ - 403 when the caller does not belong to the target organization or lacks
1491
+ user-admin role (code: 'forbidden').
1492
+ - 413 when the uploaded file exceeds 2 MB.
1493
+
1494
+ **Kind**: instance method of [<code>AdminUser</code>](#AdminUser)
1495
+ **Returns**: <code>Promise.&lt;object&gt;</code> - Batch result:
1496
+ {
1497
+ total: number,
1498
+ created: number,
1499
+ existing: number,
1500
+ skipped: number,
1501
+ results: Array<{
1502
+ row: number, // spreadsheet row (1-based, header = 1)
1503
+ email: string,
1504
+ status: 'created' | 'existing' | 'skipped',
1505
+ userId: string | null,
1506
+ message: string | null // snake_case code, optionally `code:detail`
1507
+ }>
1508
+ }
1509
+ **Access**: public
1510
+ **Author**: Myndware <augusto.pissarra@myndware.com>
1511
+
1512
+ | Param | Type | Description |
1513
+ | --- | --- | --- |
1514
+ | formData | <code>FormData</code> | A browser FormData instance with a single field named `file` whose value is the .xlsx or .csv File/Blob. Must be FormData so the browser/axios can set the multipart boundary. |
1515
+ | session | <code>string</code> | JWT session token |
1516
+
1517
+ **Example**
1518
+ ```js
1519
+ const API = require('@docbrasil/api-systemmanager');
1520
+ const api = new API();
1521
+ const fd = new FormData();
1522
+ fd.append('file', fileInput.files[0]); // .xlsx or .csv
1523
+ const session = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
1524
+ // Ensure the client is scoped to the caller's org:
1525
+ api.admin.user.setOrgId(myOrgId);
1526
+ try {
1527
+ const result = await api.admin.user.batchCreate(fd, session);
1528
+ console.log(`${result.created} created, ${result.skipped} skipped`);
1529
+ } catch (ex) {
1530
+ if (ex?.response?.status === 422 && ex.response.data?.results) {
1531
+ // All-skipped batch — still a valid result to render.
1532
+ console.warn('All rows skipped:', ex.response.data.results);
1533
+ } else {
1534
+ throw ex;
1535
+ }
1536
+ }
1537
+ ```
1471
1538
  <a name="AdminUser+remove"></a>
1472
1539
 
1473
1540
  ### adminUser.remove(userId, session) ⇒ <code>Promise.&lt;object&gt;</code>
@@ -224,6 +224,271 @@
224
224
 
225
225
 
226
226
 
227
+ <h4 class="name" id="batchCreate">
228
+ <a class="href-link" href="#batchCreate">#</a>
229
+
230
+
231
+ <span class='tag'>async</span>
232
+
233
+
234
+ <span class="code-name">
235
+
236
+ batchCreate<span class="signature">(formData, session)</span><span class="type-signature"> &rarr; {Promise.&lt;object>}</span>
237
+
238
+ </span>
239
+ </h4>
240
+
241
+
242
+
243
+
244
+ <div class="description">
245
+ Batch-create users from an uploaded Excel (.xlsx) or CSV file.
246
+
247
+ Uploads the file as multipart/form-data. The server parses it, validates
248
+ headers, de-duplicates emails, admits rows FIFO against the organization's
249
+ user cap, and delegates the actual creation to the existing registration
250
+ chain. Response is a per-row result array (created / existing / skipped).
251
+
252
+ Status codes:
253
+ - 200 when at least one row was created or matched an existing user.
254
+ - 422 (same JSON body shape) when EVERY row was skipped — callers
255
+ should promote the 422 response body to a completed result, not an
256
+ error. Axios throws on 422 by default, so catch and inspect
257
+ `ex.response.data.results`.
258
+ - 400 for structural failures (invalid_file, missing_columns, empty_file,
259
+ too_many_rows) — `response.data.code` carries the machine-readable code.
260
+ - 403 when the caller does not belong to the target organization or lacks
261
+ user-admin role (code: 'forbidden').
262
+ - 413 when the uploaded file exceeds 2 MB.
263
+ </div>
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+ <h5>Parameters:</h5>
275
+
276
+ <div class="table-container">
277
+ <table class="params table">
278
+ <thead>
279
+ <tr>
280
+
281
+ <th>Name</th>
282
+
283
+
284
+ <th>Type</th>
285
+
286
+
287
+
288
+
289
+
290
+ <th class="last">Description</th>
291
+ </tr>
292
+ </thead>
293
+
294
+ <tbody>
295
+
296
+
297
+
298
+ <tr class="deep-level-0">
299
+
300
+ <td class="name"><code>formData</code></td>
301
+
302
+
303
+ <td class="type">
304
+
305
+
306
+ <code class="param-type">FormData</code>
307
+
308
+
309
+
310
+ </td>
311
+
312
+
313
+
314
+
315
+
316
+ <td class="description last">A browser FormData instance with a single field
317
+ named `file` whose value is the .xlsx or .csv File/Blob. Must be
318
+ FormData so the browser/axios can set the multipart boundary.</td>
319
+ </tr>
320
+
321
+
322
+
323
+
324
+
325
+ <tr class="deep-level-0">
326
+
327
+ <td class="name"><code>session</code></td>
328
+
329
+
330
+ <td class="type">
331
+
332
+
333
+ <code class="param-type">string</code>
334
+
335
+
336
+
337
+ </td>
338
+
339
+
340
+
341
+
342
+
343
+ <td class="description last">JWT session token</td>
344
+ </tr>
345
+
346
+
347
+
348
+ </tbody>
349
+ </table>
350
+ </div>
351
+
352
+
353
+
354
+
355
+
356
+ <dl class="details">
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+ <dt class="tag-author">Author:</dt>
376
+ <dd class="tag-author">
377
+ <ul>
378
+ <li><a href="mailto:augusto.pissarra@myndware.com">Myndware</a></li>
379
+ </ul>
380
+ </dd>
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+ <p class="tag-source">
396
+ <a href="admin_user.js.html" class="button">View Source</a>
397
+ <span>
398
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line363">line 363</a>
399
+ </span>
400
+ </p>
401
+
402
+ </dl>
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+ <div class='columns method-parameter'>
422
+ <div class="column is-2"><label>Returns:</label></div>
423
+ <div class="column is-10">
424
+
425
+
426
+
427
+ <div class="columns">
428
+
429
+ <div class='param-desc column is-7'>Batch result:
430
+ {
431
+ total: number,
432
+ created: number,
433
+ existing: number,
434
+ skipped: number,
435
+ results: Array<{
436
+ row: number, // spreadsheet row (1-based, header = 1)
437
+ email: string,
438
+ status: 'created' | 'existing' | 'skipped',
439
+ userId: string | null,
440
+ message: string | null // snake_case code, optionally `code:detail`
441
+ }>
442
+ }</div>
443
+
444
+
445
+ <div class='column is-5 has-text-left'>
446
+ <label>Type: </label>
447
+
448
+ <code class="param-type">Promise.&lt;object></code>
449
+
450
+
451
+ </div>
452
+
453
+ </div>
454
+
455
+
456
+ </div>
457
+ </div>
458
+
459
+
460
+
461
+
462
+ <h5>Example</h5>
463
+
464
+
465
+ <pre class="prettyprint"><code>const API = require('@docbrasil/api-systemmanager');
466
+ const api = new API();
467
+ const fd = new FormData();
468
+ fd.append('file', fileInput.files[0]); // .xlsx or .csv
469
+ const session = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
470
+ // Ensure the client is scoped to the caller's org:
471
+ api.admin.user.setOrgId(myOrgId);
472
+ try {
473
+ const result = await api.admin.user.batchCreate(fd, session);
474
+ console.log(`${result.created} created, ${result.skipped} skipped`);
475
+ } catch (ex) {
476
+ if (ex?.response?.status === 422 &amp;&amp; ex.response.data?.results) {
477
+ // All-skipped batch — still a valid result to render.
478
+ console.warn('All rows skipped:', ex.response.data.results);
479
+ } else {
480
+ throw ex;
481
+ }
482
+ }</code></pre>
483
+
484
+
485
+
486
+ </div>
487
+
488
+ <div class="member">
489
+
490
+
491
+
227
492
  <h4 class="name" id="block">
228
493
  <a class="href-link" href="#block">#</a>
229
494
 
@@ -376,7 +641,7 @@
376
641
  <p class="tag-source">
377
642
  <a href="admin_user.js.html" class="button">View Source</a>
378
643
  <span>
379
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line465">line 465</a>
644
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line549">line 549</a>
380
645
  </span>
381
646
  </p>
382
647
 
@@ -596,7 +861,7 @@ await api.admin.user.block(userId, session);</code></pre>
596
861
  <p class="tag-source">
597
862
  <a href="admin_user.js.html" class="button">View Source</a>
598
863
  <span>
599
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line518">line 518</a>
864
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line602">line 602</a>
600
865
  </span>
601
866
  </p>
602
867
 
@@ -822,7 +1087,7 @@ await api.admin.user.block(userId, session);</code></pre>
822
1087
  <p class="tag-source">
823
1088
  <a href="admin_user.js.html" class="button">View Source</a>
824
1089
  <span>
825
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line376">line 376</a>
1090
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line460">line 460</a>
826
1091
  </span>
827
1092
  </p>
828
1093
 
@@ -2680,7 +2945,7 @@ await api.admin.user.findByIds(userIds, apiKey);</code></pre>
2680
2945
  <p class="tag-source">
2681
2946
  <a href="admin_user.js.html" class="button">View Source</a>
2682
2947
  <span>
2683
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line344">line 344</a>
2948
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line428">line 428</a>
2684
2949
  </span>
2685
2950
  </p>
2686
2951
 
@@ -2897,7 +3162,7 @@ const session = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';</code></pre>
2897
3162
  <p class="tag-source">
2898
3163
  <a href="admin_user.js.html" class="button">View Source</a>
2899
3164
  <span>
2900
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line606">line 606</a>
3165
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line690">line 690</a>
2901
3166
  </span>
2902
3167
  </p>
2903
3168
 
@@ -3202,7 +3467,7 @@ const groups = await api.admin.user.getGroupsPermissions(orgId, session);</code>
3202
3467
  <p class="tag-source">
3203
3468
  <a href="admin_user.js.html" class="button">View Source</a>
3204
3469
  <span>
3205
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line713">line 713</a>
3470
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line797">line 797</a>
3206
3471
  </span>
3207
3472
  </p>
3208
3473
 
@@ -3397,7 +3662,7 @@ const users = await api.admin.user.getOrgUsers(params, session);</code></pre>
3397
3662
  <p class="tag-source">
3398
3663
  <a href="admin_user.js.html" class="button">View Source</a>
3399
3664
  <span>
3400
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line682">line 682</a>
3665
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line766">line 766</a>
3401
3666
  </span>
3402
3667
  </p>
3403
3668
 
@@ -3745,7 +4010,7 @@ const orgs = await api.admin.user.getOrganizations(session);</code></pre>
3745
4010
  <p class="tag-source">
3746
4011
  <a href="admin_user.js.html" class="button">View Source</a>
3747
4012
  <span>
3748
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line415">line 415</a>
4013
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line499">line 499</a>
3749
4014
  </span>
3750
4015
  </p>
3751
4016
 
@@ -3965,7 +4230,7 @@ await api.user.form.getUserList(params, session);</code></pre>
3965
4230
  <p class="tag-source">
3966
4231
  <a href="admin_user.js.html" class="button">View Source</a>
3967
4232
  <span>
3968
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line317">line 317</a>
4233
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line401">line 401</a>
3969
4234
  </span>
3970
4235
  </p>
3971
4236
 
@@ -4358,7 +4623,7 @@ await api.admin.user.remove(userId, session);</code></pre>
4358
4623
  <p class="tag-source">
4359
4624
  <a href="admin_user.js.html" class="button">View Source</a>
4360
4625
  <span>
4361
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line495">line 495</a>
4626
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line579">line 579</a>
4362
4627
  </span>
4363
4628
  </p>
4364
4629
 
@@ -4578,7 +4843,7 @@ await api.admin.user.unblock(userId, session);</code></pre>
4578
4843
  <p class="tag-source">
4579
4844
  <a href="admin_user.js.html" class="button">View Source</a>
4580
4845
  <span>
4581
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line541">line 541</a>
4846
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line625">line 625</a>
4582
4847
  </span>
4583
4848
  </p>
4584
4849
 
@@ -4859,7 +5124,7 @@ await api.admin.user.unblock(userId, session);</code></pre>
4859
5124
  <p class="tag-source">
4860
5125
  <a href="admin_user.js.html" class="button">View Source</a>
4861
5126
  <span>
4862
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line646">line 646</a>
5127
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line730">line 730</a>
4863
5128
  </span>
4864
5129
  </p>
4865
5130
 
@@ -5132,7 +5397,7 @@ await api.admin.user.updateUserGroups(params, session);</code></pre>
5132
5397
  <p class="tag-source">
5133
5398
  <a href="admin_user.js.html" class="button">View Source</a>
5134
5399
  <span>
5135
- <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line573">line 573</a>
5400
+ <a href="admin_user.js.html">admin/user.js</a>, <a href="admin_user.js.html#line657">line 657</a>
5136
5401
  </span>
5137
5402
  </p>
5138
5403
 
@@ -385,6 +385,90 @@ class AdminUser {
385
385
  }
386
386
  }
387
387
 
388
+ /**
389
+ * @author Myndware &lt;augusto.pissarra@myndware.com>
390
+ * @description Batch-create users from an uploaded Excel (.xlsx) or CSV file.
391
+ *
392
+ * Uploads the file as multipart/form-data. The server parses it, validates
393
+ * headers, de-duplicates emails, admits rows FIFO against the organization's
394
+ * user cap, and delegates the actual creation to the existing registration
395
+ * chain. Response is a per-row result array (created / existing / skipped).
396
+ *
397
+ * Status codes:
398
+ * - 200 when at least one row was created or matched an existing user.
399
+ * - 422 (same JSON body shape) when EVERY row was skipped — callers
400
+ * should promote the 422 response body to a completed result, not an
401
+ * error. Axios throws on 422 by default, so catch and inspect
402
+ * `ex.response.data.results`.
403
+ * - 400 for structural failures (invalid_file, missing_columns, empty_file,
404
+ * too_many_rows) — `response.data.code` carries the machine-readable code.
405
+ * - 403 when the caller does not belong to the target organization or lacks
406
+ * user-admin role (code: 'forbidden').
407
+ * - 413 when the uploaded file exceeds 2 MB.
408
+ *
409
+ * @param {FormData} formData A browser FormData instance with a single field
410
+ * named `file` whose value is the .xlsx or .csv File/Blob. Must be
411
+ * FormData so the browser/axios can set the multipart boundary.
412
+ * @param {string} session JWT session token
413
+ * @return {Promise&lt;object>} Batch result:
414
+ * {
415
+ * total: number,
416
+ * created: number,
417
+ * existing: number,
418
+ * skipped: number,
419
+ * results: Array&lt;{
420
+ * row: number, // spreadsheet row (1-based, header = 1)
421
+ * email: string,
422
+ * status: 'created' | 'existing' | 'skipped',
423
+ * userId: string | null,
424
+ * message: string | null // snake_case code, optionally `code:detail`
425
+ * }>
426
+ * }
427
+ * @public
428
+ * @async
429
+ * @example
430
+ *
431
+ * const API = require('@docbrasil/api-systemmanager');
432
+ * const api = new API();
433
+ * const fd = new FormData();
434
+ * fd.append('file', fileInput.files[0]); // .xlsx or .csv
435
+ * const session = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
436
+ * // Ensure the client is scoped to the caller's org:
437
+ * api.admin.user.setOrgId(myOrgId);
438
+ * try {
439
+ * const result = await api.admin.user.batchCreate(fd, session);
440
+ * console.log(`${result.created} created, ${result.skipped} skipped`);
441
+ * } catch (ex) {
442
+ * if (ex?.response?.status === 422 &amp;&amp; ex.response.data?.results) {
443
+ * // All-skipped batch — still a valid result to render.
444
+ * console.warn('All rows skipped:', ex.response.data.results);
445
+ * } else {
446
+ * throw ex;
447
+ * }
448
+ * }
449
+ */
450
+ async batchCreate(formData, session) {
451
+ const self = this;
452
+
453
+ try {
454
+ Joi.assert(formData, Joi.any().required(), 'Multipart FormData with a `file` field');
455
+ Joi.assert(session, Joi.string().required(), 'Session token');
456
+
457
+ // Do NOT force Content-Type — let the browser/axios set it with the
458
+ // correct multipart boundary. Raise the axios body-size caps to 5 MB
459
+ // (server enforces its own 2 MB cap via Hapi `maxBytes`).
460
+ const cfg = {
461
+ ...self._setHeader(session),
462
+ maxContentLength: 5 * 1024 * 1024,
463
+ maxBodyLength: 5 * 1024 * 1024
464
+ };
465
+ const apiCall = self.client.put(`${self._basePath()}/batch`, formData, cfg);
466
+ return self._returnData(await apiCall);
467
+ } catch (ex) {
468
+ throw ex;
469
+ }
470
+ }
471
+
388
472
  /**
389
473
  * @author Myndware &lt;augusto.pissarra@myndware.com>
390
474
  * @description Remove a user
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@docbrasil/api-systemmanager",
3
3
  "description": "Module API System Manager",
4
- "version": "1.2.4",
4
+ "version": "1.2.6",
5
5
  "scripts": {
6
6
  "htmldoc": "rm -rf docs && jsdoc api/** -d docs -t ./node_modules/better-docs",
7
7
  "doc": "rm -rf doc && mkdir doc && jsdoc2md api/**/* api/* > doc/api.md",