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