@questwork/q-utilities 0.1.13 → 0.1.14

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.
@@ -58,6 +58,7 @@ __webpack_require__.d(__webpack_exports__, {
58
58
  TenantAwareEntity: () => (/* reexport */ TenantAwareEntity),
59
59
  TrackedEntity: () => (/* reexport */ TrackedEntity),
60
60
  UniqueKeyGenerator: () => (/* reexport */ UniqueKeyGenerator),
61
+ authorize: () => (/* reexport */ authorize),
61
62
  concatStringByArray: () => (/* reexport */ concatStringByArray),
62
63
  convertString: () => (/* reexport */ convertString),
63
64
  formatDate: () => (/* reexport */ formatDate),
@@ -73,9 +74,45 @@ __webpack_require__.d(__webpack_exports__, {
73
74
  pReduce: () => (/* reexport */ pReduce),
74
75
  padZeros: () => (/* reexport */ padZeros),
75
76
  stringFormatter: () => (/* reexport */ stringFormatter),
76
- stringHelper: () => (/* reexport */ stringHelper)
77
+ stringHelper: () => (/* reexport */ stringHelper),
78
+ trackingPlugin: () => (/* reexport */ trackingPlugin)
77
79
  });
78
80
 
81
+ ;// ./lib/helpers/authorize/authorize.js
82
+ function authorize({ allowOwner, query = {}, required, user }) {
83
+ if (!user) {
84
+ throw new Error('Require login.')
85
+ }
86
+ if (!user.permission) {
87
+ throw new Error('You do not have any permission.')
88
+ }
89
+ const scopes = user.permission.getScopes(required || {})
90
+ if (!scopes || scopes.length === 0) {
91
+ throw new Error('You are not allowed in this scope.')
92
+ }
93
+ if (!scopes.includes('*')) {
94
+ query.tenantCode = user.tenantCode
95
+ }
96
+ if (!scopes.includes('TENANT')) {
97
+ query.eventShortCode = user.eventShortCode
98
+ }
99
+ if (!scopes.includes('EVENT')) {
100
+ query.eventRegistrationCode = user.eventRegistrationCode
101
+ }
102
+ if (allowOwner) {
103
+ query.__ALLOW_OWNER = true
104
+ }
105
+ // not good, just use it as example
106
+ if (user.hasExcludedFields) {
107
+ query.__EXCLUDED_FIELDS = user.getExcludedFields(required)
108
+ }
109
+ query.__LOGIN_SUBJECT_CODE = user.loginSubjectCode
110
+ return query
111
+ }
112
+
113
+ ;// ./lib/helpers/authorize/index.js
114
+
115
+
79
116
  ;// ./lib/helpers/getValidation/getValidation.js
80
117
  function getValidation(rule, data, getDataByKey, KeyValueObject) {
81
118
  if (!rule) {
@@ -337,6 +374,7 @@ const _FN_NAMES = [
337
374
  'lte',
338
375
  'map',
339
376
  'neq',
377
+ 'removeHtml',
340
378
  'toLowerCase',
341
379
  'toUpperCase',
342
380
  ]
@@ -925,6 +963,66 @@ function _neq(data, args) {
925
963
 
926
964
 
927
965
 
966
+ ;// ./lib/models/templateCompiler/helpers/_removeHtml.js
967
+
968
+
969
+ function _removeHtml(html, args) {
970
+ if (html === null || html === undefined) {
971
+ return null
972
+ }
973
+ if (!Array.isArray(args)) {
974
+ throw new TemplateCompilerException(`_removeHtml: ${TEMPLATE_COMPILER_EXCEPTION_TYPE.argumentFormatException}: args parts must be array`)
975
+ }
976
+
977
+ return _htmlToPlainText(html, args[0])
978
+ }
979
+
980
+ function _htmlToPlainText(html, delimiter = '\n') {
981
+ if (typeof delimiter !== 'string') {
982
+ delimiter = '\n'; // Fallback to default if not a string
983
+ }
984
+
985
+ // First decode HTML entities and normalize whitespace
986
+ const decodedHtml = html
987
+ .replace(/ /g, ' ')
988
+ .replace(/\s+/g, ' '); // Collapse all whitespace to single spaces
989
+
990
+ // Process HTML tags
991
+ let text = decodedHtml
992
+ // Replace block tags with temporary marker (~~~)
993
+ .replace(/<\/?(p|div|h[1-6]|ul|ol|li|pre|section|article|table|tr|td|th)(\s[^>]*)?>/gi, '~~~')
994
+ // Replace <br> tags with temporary marker (~~~)
995
+ .replace(/<br\s*\/?>/gi, '~~~')
996
+ // Remove all other tags
997
+ .replace(/<[^>]+>/g, '')
998
+ // Convert markers to specified delimiter
999
+ .replace(/~~~+/g, delimiter)
1000
+ // Trim and clean whitespace
1001
+ .trim();
1002
+
1003
+ // Special handling for empty delimiter
1004
+ if (delimiter === '') {
1005
+ // Collapse all whitespace to single space
1006
+ text = text.replace(/\s+/g, ' ');
1007
+ } else {
1008
+
1009
+
1010
+ // Collapse multiple delimiters to single
1011
+ text = text.replace(new RegExp(`${escapeRegExp(delimiter)}+`, 'g'), delimiter);
1012
+ // Remove leading/trailing delimiters
1013
+ text = text.replace(new RegExp(`^${escapeRegExp(delimiter)}|${escapeRegExp(delimiter)}$`, 'g'), '');
1014
+ }
1015
+
1016
+ return text;
1017
+ }
1018
+
1019
+
1020
+ function escapeRegExp(string) {
1021
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1022
+ }
1023
+
1024
+
1025
+
928
1026
  ;// ./lib/models/templateCompiler/helpers/_toLowerCase.js
929
1027
 
930
1028
 
@@ -979,6 +1077,7 @@ function _toUpperCase(data, args) {
979
1077
 
980
1078
 
981
1079
 
1080
+
982
1081
 
983
1082
 
984
1083
  ;// ./lib/models/templateCompiler/templateCompiler.js
@@ -1049,6 +1148,9 @@ class TemplateCompiler {
1049
1148
  static neq(data, args) {
1050
1149
  return _neq(data, args)
1051
1150
  }
1151
+ static removeHtml(data, args) {
1152
+ return _removeHtml(data, args)
1153
+ }
1052
1154
  static toLowerCase(data, args) {
1053
1155
  return _toLowerCase(data, args)
1054
1156
  }
@@ -1058,6 +1160,9 @@ class TemplateCompiler {
1058
1160
  static parseFunction(expression) {
1059
1161
  return _parseFunction(expression, _FN_NAMES)
1060
1162
  }
1163
+ static parseParams(parameters) {
1164
+ return _parseParams(parameters)
1165
+ }
1061
1166
 
1062
1167
  pipe(expression = '') {
1063
1168
  this.delimiters = expression.substring(0, 2) === '{{' ? TAGS_HANDLEBAR : TAGS_EJS
@@ -1237,6 +1342,8 @@ function _callFunction(data, functionName, parameters) {
1237
1342
  return _map(data, parameters)
1238
1343
  case 'neq':
1239
1344
  return _neq(data, parameters)
1345
+ case 'removeHtml':
1346
+ return _removeHtml(data, parameters)
1240
1347
  case 'toLowerCase':
1241
1348
  return _toLowerCase(data)
1242
1349
  case 'toUpperCase':
@@ -1385,42 +1492,59 @@ const objectHelper = {
1385
1492
  },
1386
1493
  merge,
1387
1494
  set(obj, path, value) {
1388
- const parts = path.split('.')
1389
- let current = obj
1495
+ const parts = path.split('.');
1496
+ let current = obj;
1497
+
1498
+ // 处理所有中间部分
1390
1499
  for (let i = 0; i < parts.length - 1; i++) {
1391
- const part = parts[i]
1392
- if (part.endsWith('[]')) {
1393
- // 处理数组遍历
1394
- const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
1395
- if (Array.isArray(current[key])) {
1396
- current[key].forEach((item) => set(item, parts.slice(i + 1).join('.'), value))
1500
+ const part = parts[i];
1501
+ let key, index;
1502
+
1503
+ // 检查是否是数组索引格式,如key[0]
1504
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
1505
+ if (arrayMatch) {
1506
+ key = arrayMatch[1];
1507
+ index = parseInt(arrayMatch[2], 10);
1508
+ // 确保当前层级的数组存在
1509
+ if (!current[key] || !Array.isArray(current[key])) {
1510
+ current[key] = [];
1397
1511
  }
1398
- return // 处理完数组后直接返回
1399
- }
1400
- if (part.includes('[') && part.includes(']')) {
1401
- // 处理数组索引
1402
- const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
1403
- if (arrayMatch) {
1404
- const key = arrayMatch[1]
1405
- const index = arrayMatch[2]
1406
- if (Array.isArray(current[key]) && current[key][index]) {
1407
- current = current[key][index]
1408
- } else {
1409
- return // 如果数组或索引不存在,直接返回
1410
- }
1512
+ // 扩展数组到足够大
1513
+ while (current[key].length <= index) {
1514
+ current[key].push(undefined);
1411
1515
  }
1516
+ // 如果当前位置未定义或为null,初始化为对象
1517
+ if (current[key][index] == null) {
1518
+ current[key][index] = {};
1519
+ }
1520
+ current = current[key][index];
1412
1521
  } else {
1413
1522
  // 处理普通属性
1414
1523
  if (!current[part]) {
1415
- current[part] = {} // 如果属性不存在,创建一个空对象
1524
+ current[part] = {};
1416
1525
  }
1417
- current = current[part]
1526
+ current = current[part];
1527
+ }
1528
+ }
1529
+
1530
+ // 处理最后一部分
1531
+ const lastPart = parts[parts.length - 1];
1532
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/);
1533
+ if (arrayMatch) {
1534
+ const key = arrayMatch[1];
1535
+ const index = parseInt(arrayMatch[2], 10);
1536
+ // 确保数组存在
1537
+ if (!current[key] || !Array.isArray(current[key])) {
1538
+ current[key] = [];
1539
+ }
1540
+ // 扩展数组到所需索引
1541
+ while (current[key].length <= index) {
1542
+ current[key].push(undefined);
1418
1543
  }
1544
+ current[key][index] = value;
1545
+ } else {
1546
+ current[lastPart] = value;
1419
1547
  }
1420
-
1421
- // 设置最终属性值
1422
- const lastPart = parts[parts.length - 1]
1423
- current[lastPart] = value
1424
1548
  }
1425
1549
  }
1426
1550
 
@@ -1489,6 +1613,8 @@ async function pReduce(iterable, reducer, initialValue) {
1489
1613
 
1490
1614
 
1491
1615
  ;// ./lib/models/repo/repo.js
1616
+
1617
+
1492
1618
  class Repo {
1493
1619
  constructor(options) {
1494
1620
  options = options || {}
@@ -1501,11 +1627,7 @@ class Repo {
1501
1627
  : null
1502
1628
  }
1503
1629
  static init(options = {}) {
1504
- if (options instanceof this) {
1505
- return options
1506
- }
1507
- const instance = new this(options)
1508
- return instance.isValid ? instance : null
1630
+ return init(this, options)
1509
1631
  }
1510
1632
  static get _classname() {
1511
1633
  return 'Repo'
@@ -1697,6 +1819,11 @@ function _makeLog({ systemLog, label, message: message1, input } = {}) {
1697
1819
 
1698
1820
 
1699
1821
 
1822
+ ;// ./lib/models/repo/index.js
1823
+
1824
+
1825
+
1826
+
1700
1827
  ;// ./lib/models/apiResponse/apiResponse.js
1701
1828
  class ApiResponse {
1702
1829
  constructor(options = {}) {
@@ -1841,6 +1968,11 @@ function makeService({ repo }) {
1841
1968
 
1842
1969
 
1843
1970
 
1971
+ ;// ./lib/models/service/index.js
1972
+
1973
+
1974
+
1975
+
1844
1976
  ;// ./lib/helpers/generalPost/generalPost.js
1845
1977
 
1846
1978
 
@@ -1865,7 +1997,13 @@ async function generalPost({ body = {}, GeneralModel, UniqueKeyGenerator, resour
1865
1997
  function _attachShared(data, globalShared = {}, shared = {}) {
1866
1998
  Object.keys(shared).forEach((key) => {
1867
1999
  const _data = data[key]
1868
- data[key] = objectHelper.merge({}, _data, globalShared, shared[key] || {})
2000
+ if (Array.isArray(_data)) {
2001
+ data[key] = _data.map((_dataItem) => {
2002
+ return objectHelper.merge({}, _dataItem, globalShared, shared[key] || {})
2003
+ })
2004
+ } else {
2005
+ data[key] = objectHelper.merge({}, _data, globalShared, shared[key] || {})
2006
+ }
1869
2007
  })
1870
2008
  }
1871
2009
 
@@ -1900,7 +2038,7 @@ function init(_class, options) {
1900
2038
  }
1901
2039
  try {
1902
2040
  const instance = new _class(options)
1903
- return instance.isValid ? instance : null
2041
+ return instance.isValid !== false ? instance : null
1904
2042
  } catch (e) {
1905
2043
  console.log(`init failed for class: ${_class._classname || 'no _classname'}`, e)
1906
2044
  return null
@@ -2092,6 +2230,36 @@ const stringHelper = {
2092
2230
 
2093
2231
 
2094
2232
 
2233
+ ;// ./lib/helpers/trackingPlugin/trackingPlugin.js
2234
+ function trackingPlugin(schema, options) {
2235
+ // Add meta fields
2236
+ schema.add({
2237
+ meta: {
2238
+ active: { type: Boolean, default: true },
2239
+ created: { type: Number },
2240
+ creator: { type: String },
2241
+ deleted: { type: Boolean, default: false },
2242
+ modified: { type: Number },
2243
+ owner: { type: String },
2244
+ }
2245
+ })
2246
+
2247
+ // Auto-update hook
2248
+ schema.pre('save', function(next) {
2249
+ this.meta.modified = Date.now()
2250
+ next()
2251
+ })
2252
+
2253
+ // Optional: Add helper methods
2254
+ // schema.methods.touch = function(userId) {
2255
+ // this.meta.updatedAt = new Date()
2256
+ // this.meta.updatedBy = userId
2257
+ // }
2258
+ }
2259
+
2260
+ ;// ./lib/helpers/trackingPlugin/index.js
2261
+
2262
+
2095
2263
  ;// ./lib/helpers/index.js
2096
2264
 
2097
2265
 
@@ -2108,6 +2276,8 @@ const stringHelper = {
2108
2276
 
2109
2277
 
2110
2278
 
2279
+
2280
+
2111
2281
  ;// ./lib/models/apiResponse/index.js
2112
2282
 
2113
2283
 
@@ -2448,21 +2618,12 @@ class QMeta {
2448
2618
 
2449
2619
 
2450
2620
 
2451
- ;// ./lib/models/repo/index.js
2452
-
2453
-
2454
-
2455
-
2456
- ;// ./lib/models/service/index.js
2457
-
2458
-
2459
-
2460
-
2461
2621
  ;// ./lib/models/trackedEntity/trackedEntity.js
2462
2622
 
2463
2623
 
2464
2624
  class TrackedEntity {
2465
- constructor(options = {}, { trackFlat = false } = {}) {
2625
+ constructor(options = {}) {
2626
+ options = options || {}
2466
2627
  const timestamp = Date.now()
2467
2628
  const _tracking = {
2468
2629
  active: options.active ?? true,
@@ -2473,11 +2634,13 @@ class TrackedEntity {
2473
2634
  owner: options.owner ?? '',
2474
2635
  }
2475
2636
 
2476
- if (trackFlat) {
2477
- Object.assign(this, _tracking)
2478
- } else {
2479
- this.meta = { ..._tracking, ...options.meta }
2480
- }
2637
+ this.meta = { ..._tracking, ...options.meta }
2638
+
2639
+ // if (trackFlat) {
2640
+ // Object.assign(this, _tracking)
2641
+ // } else {
2642
+ // this.meta = { ..._tracking, ...options.meta }
2643
+ // }
2481
2644
  }
2482
2645
 
2483
2646
  // Class methods
@@ -2497,16 +2660,52 @@ class TrackedEntity {
2497
2660
  static initOnlyValidFromArray(arr = []) {
2498
2661
  return initOnlyValidFromArray(this, arr)
2499
2662
  }
2500
- static nest(entity) {
2501
- const { active, created, creator, deleted, modified, owner, ...rest } = entity
2502
- return { ...rest, meta: { active, created, creator, deleted, modified, owner } }
2503
- }
2663
+ // static nest(entity) {
2664
+ // const { active, created, creator, deleted, modified, owner, ...rest } = entity
2665
+ // return { ...rest, meta: { active, created, creator, deleted, modified, owner } }
2666
+ // }
2504
2667
 
2505
2668
  // getters
2506
2669
  get isValid() {
2507
2670
  return !!this
2508
2671
  }
2509
-
2672
+ get active() {
2673
+ return this.meta?.active ?? this.active
2674
+ }
2675
+ get created() {
2676
+ return this.meta?.created ?? this.created
2677
+ }
2678
+ get creator() {
2679
+ return this.meta?.creator ?? this.creator
2680
+ }
2681
+ get deleted() {
2682
+ return this.meta?.deleted ?? this.deleted
2683
+ }
2684
+ get modified() {
2685
+ return this.meta?.modified ?? this.modified
2686
+ }
2687
+ get owner() {
2688
+ return this.meta?.owner ?? this.owner
2689
+ }
2690
+ delete() {
2691
+ return this.setDeleted()
2692
+ }
2693
+ setActive() {
2694
+ if (this.meta) {
2695
+ this.meta.active = true
2696
+ } else {
2697
+ this.active = true
2698
+ }
2699
+ return this
2700
+ }
2701
+ setDeleted() {
2702
+ if (this.meta) {
2703
+ this.meta.deleted = true
2704
+ } else {
2705
+ this.deleted = true
2706
+ }
2707
+ return this
2708
+ }
2510
2709
  setModified() {
2511
2710
  const timestamp = Date.now()
2512
2711
  if (this.meta) {
@@ -2514,6 +2713,41 @@ class TrackedEntity {
2514
2713
  } else {
2515
2714
  this.modified = timestamp
2516
2715
  }
2716
+ return this
2717
+ }
2718
+ setOwner(owner) {
2719
+ if (!owner) {
2720
+ return this
2721
+ }
2722
+ if (this.meta) {
2723
+ this.meta.owner = owner
2724
+ } else {
2725
+ this.owner = owner
2726
+ }
2727
+ return this
2728
+ }
2729
+ unsetActive() {
2730
+ if (this.meta) {
2731
+ this.meta.active = false
2732
+ } else {
2733
+ this.active = false
2734
+ }
2735
+ return this
2736
+ }
2737
+ unsetDeleted() {
2738
+ if (this.meta) {
2739
+ this.meta.deleted = false
2740
+ } else {
2741
+ this.deleted = false
2742
+ }
2743
+ return this
2744
+ }
2745
+
2746
+ update(update) {
2747
+ if (update.meta) {
2748
+ this.meta = { ...this.meta, ...update.meta }
2749
+ }
2750
+ return this.setModified()
2517
2751
  }
2518
2752
  }
2519
2753
 
@@ -2524,6 +2758,8 @@ class TrackedEntity {
2524
2758
  ;// ./lib/models/tenantAwareEntity/tenantAwareEntity.js
2525
2759
 
2526
2760
 
2761
+
2762
+
2527
2763
  class TenantAwareEntity extends TrackedEntity {
2528
2764
  constructor(options = {}) {
2529
2765
  options = options || {}
@@ -2538,6 +2774,9 @@ class TenantAwareEntity extends TrackedEntity {
2538
2774
  super(options)
2539
2775
 
2540
2776
  this._tenant = options._tenant
2777
+
2778
+ this.metadata = Metadata.initOnlyValidFromArray(options.metadata)
2779
+ this.remarks = KeyValueObject.initOnlyValidFromArray(options.remarks)
2541
2780
  this.tenantCode = options.tenantCode // Required for multi-tenancy
2542
2781
  }
2543
2782
 
@@ -2553,6 +2792,41 @@ class TenantAwareEntity extends TrackedEntity {
2553
2792
  get isValid() {
2554
2793
  return super.isValid && !!this.tenantCode // Required for multi-tenancy
2555
2794
  }
2795
+
2796
+ // instance methods
2797
+ getMetadata() {
2798
+ return this.metadata
2799
+ }
2800
+ getMetadataByKey(key) {
2801
+ return Metadata.foundByKey(this.metadata, key)
2802
+ }
2803
+ getMetadataValueByKey(key) {
2804
+ const found = this.getMetadataByKey(key)
2805
+ return found ? found.value : null
2806
+ }
2807
+ getRemarks() {
2808
+ return this.remarks
2809
+ }
2810
+ getRemarkByKey(key) {
2811
+ return KeyValueObject.foundByKey(this.remarks, key)
2812
+ }
2813
+ getRemarksValueByKey(key) {
2814
+ const found = this.getRemarkByKey(key)
2815
+ return found ? found.value : null
2816
+ }
2817
+ getTenantCode() {
2818
+ return this.tenantCode
2819
+ }
2820
+
2821
+ update(update) {
2822
+ if (update.metadata && Array.isArray(update.metadata)) {
2823
+ this.metadata = Metadata.initOnlyValidFromArray(update.metadata)
2824
+ }
2825
+ if (update.remarks && Array.isArray(update.remarks)) {
2826
+ this.remarks = KeyValueObject.initOnlyValidFromArray(update.remarks)
2827
+ }
2828
+ return super.update(update)
2829
+ }
2556
2830
  }
2557
2831
 
2558
2832
  ;// ./lib/models/tenantAwareEntity/index.js
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "@questwork/q-utilities",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Questwork QUtilities",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
8
  "require": "./dist/index.min.cjs",
9
+ "import": "./dist/q-utilities.esm.js",
9
10
  "default": "./dist/q-utilities.min.js"
10
11
  }
11
12
  },
13
+ "scripts": {
14
+ "build": "cross-env NODE_ENV=production minimize=false gulp",
15
+ "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
16
+ "lint": "eslint .",
17
+ "test:models": "NODE_ENV=test mocha --exit 'lib/models/test.setup.js' 'lib/models/**/*.spec.js'"
18
+ },
12
19
  "author": {
13
20
  "name": "Questwork Consulting Limited",
14
21
  "email": "info@questwork.com",
@@ -23,6 +30,7 @@
23
30
  "chai": "^4.5.0",
24
31
  "clean-webpack-plugin": "^4.0.0",
25
32
  "cross-env": "^7.0.3",
33
+ "del": "^8.0.0",
26
34
  "eslint": "^9.26.0",
27
35
  "gulp": "^4.0.2",
28
36
  "gulp-rename": "^2.0.0",
@@ -33,11 +41,5 @@
33
41
  },
34
42
  "engines": {
35
43
  "node": ">=10.0.0"
36
- },
37
- "scripts": {
38
- "build": "cross-env NODE_ENV=production minimize=false gulp",
39
- "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
40
- "lint": "eslint .",
41
- "test:models": "NODE_ENV=test mocha --exit 'lib/models/test.setup.js' 'lib/models/**/*.spec.js'"
42
44
  }
43
- }
45
+ }