w-web-perm 1.0.78 → 1.0.80

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.
Files changed (37) hide show
  1. package/dist/index.tmp +1 -1
  2. package/dist/js/app-legacy.85fce10c.js +1 -0
  3. package/dist/js/app.85fce10c.js +1 -0
  4. package/dist/js/{chunk-vendors-legacy.884ef407.js → chunk-vendors-legacy.b57e1cce.js} +2 -2
  5. package/dist/js/{chunk-vendors.884ef407.js → chunk-vendors.b57e1cce.js} +2 -2
  6. package/dist/w-web-perm.umd.js +2 -2
  7. package/dist/w-web-perm.umd.js.map +1 -1
  8. package/docs/WWebPerm.html +4 -4
  9. package/docs/global.html +610 -0
  10. package/docs/index.html +2 -2
  11. package/docs/{WWebPerm.mjs.html → server_WWebPerm.mjs.html} +84 -94
  12. package/docs/server_procCore.mjs.html +311 -0
  13. package/docs/src_fetchJson.mjs.html +113 -0
  14. package/package.json +18 -19
  15. package/server/WWebPerm.mjs +80 -90
  16. package/server/procCore.mjs +56 -4
  17. package/server/procLang.mjs +3 -3
  18. package/server/provideTabs.mjs +8 -9
  19. package/src/components/LayoutContentGrups.vue +5 -1
  20. package/src/components/LayoutContentPemis.vue +5 -1
  21. package/src/components/LayoutContentTargets.vue +5 -1
  22. package/src/components/LayoutContentUsers.vue +21 -8
  23. package/src/components/VeCgrups.vue +3 -3
  24. package/src/components/VeCpemis.vue +2 -2
  25. package/src/components/VeGrupBlngUsers.vue +2 -2
  26. package/src/components/VePemiBlngGrups.vue +2 -2
  27. package/src/fetchJson.mjs +41 -0
  28. package/src/getPerm.mjs +7 -7
  29. package/src/getPermUserInfor.mjs +7 -7
  30. package/src/main.js +1 -1
  31. package/src/perm.mjs +5 -4
  32. package/src/plugins/mUI.mjs +17 -0
  33. package/srv.mjs +8 -0
  34. package/tableTags-web-perm.json +1 -0
  35. package/dist/js/app-legacy.71fa8c3b.js +0 -1
  36. package/dist/js/app.71fa8c3b.js +0 -1
  37. package/tableTags.json +0 -1
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
 
5
5
  <meta charset="utf-8">
6
- <title>WWebPerm.mjs - Documentation</title>
6
+ <title>server/WWebPerm.mjs - Documentation</title>
7
7
 
8
8
 
9
9
  <script src="scripts/prettify/prettify.js"></script>
@@ -29,13 +29,13 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
36
36
  <div id="main">
37
37
 
38
- <h1 class="page-title">WWebPerm.mjs</h1>
38
+ <h1 class="page-title">server/WWebPerm.mjs</h1>
39
39
 
40
40
 
41
41
 
@@ -88,7 +88,7 @@ import procStaInfor from './procStaInfor.mjs'
88
88
  * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
89
89
  * @param {String} db 輸入資料庫名稱字串
90
90
  * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫
91
- * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
91
+ * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件(perm users 表之列)與來源字串from,通過此函數識別後回傳布林值(可為Promise),允許使用者回傳true,反之回傳false。from 值域: 'getUserByToken'(登入)、'getPerm'(api/getPerm)、以及資料通道之 apiType 'main'(RPC 與 ORM 執行)、'upload-controller'、'upload-slice'、'download-get-filename'、'download-get-file'、'download';資料通道與登入採同一授權強度(反查 perm users 且 isActive='y' 後再經本函數)
92
92
  * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
93
93
  * @param {Object} [opt={}] 輸入設定物件,預設{}
94
94
  * @param {Integer} [opt.serverPort=11006] 輸入伺服器通訊port,預設11006
@@ -396,34 +396,6 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
396
396
  }
397
397
 
398
398
 
399
- // //getTargetsList
400
- // let getTargetsList = async (userId) => {
401
- // let rs = await woItems.targets.select()
402
- // return rs
403
- // }
404
-
405
-
406
- // //getPemisList
407
- // let getPemisList = async (userId) => {
408
- // let rs = await woItems.pemis.select()
409
- // return rs
410
- // }
411
-
412
-
413
- // //getGrupsList
414
- // let getGrupsList = async (userId) => {
415
- // let rs = await woItems.grups.select()
416
- // return rs
417
- // }
418
-
419
-
420
- // //getUsersList
421
- // let getUsersList = async (userId) => {
422
- // let rs = await woItems.users.select()
423
- // return rs
424
- // }
425
-
426
-
427
399
  //checkUser
428
400
  let checkUser = async(user) => {
429
401
  let id = get(user, 'id', '')
@@ -513,22 +485,6 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
513
485
  }
514
486
 
515
487
 
516
- //p
517
- let p = {
518
- checkToken: async(token) => {
519
-
520
- //getUserByToken
521
- let u = await getUserByToken(token)
522
- // console.log('u', u)
523
-
524
- //checkUser
525
- await checkUser(u)
526
-
527
- return true
528
- },
529
- }
530
-
531
-
532
488
  //getAndVerifyClientUser
533
489
  let getAndVerifyClientUser = async (token, from = '') => {
534
490
  //基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
@@ -591,6 +547,25 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
591
547
  }
592
548
 
593
549
 
550
+ //p
551
+ let p = {
552
+ //checkToken, 資料通道(/api/main 之 RPC 與 ORM 執行、上傳、下載)之連線驗證, 與 api/getUserByToken(登入)採同一授權強度:
553
+ //外部 getUserByToken → 欄位齊備 → 反查 perm users(mappingBy)且 isActive='y' → verifyClientUser(user, from)
554
+ //why: 2026-09-07 前僅驗外部 token 欄位齊備(checkUser), 不查 perm users 與 isActive, 亦不經 verifyClientUser,
555
+ //致不在 perm users 表之外部使用者、被停用(isActive='n')之使用者仍可經資料通道讀寫全部權限資料(實測 A3/A4, 見 tmp/probe-report.json);
556
+ //改為與登入入口對稱, 使 UI 之「是否有效」與部署層 verifyClientUser 政策對資料通道亦生效.
557
+ //from 為 w-converhp 之 apiType('main' / 'upload-controller' / 'upload-slice' / 'download-get-filename' / 'download-get-file' / 'download'),
558
+ //供部署層 verifyClientUser(user, from) 區分入口.
559
+ checkToken: async(token, from = '') => {
560
+
561
+ //getAndVerifyClientUser, reject 時 key: cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等
562
+ await getAndVerifyClientUser(token, from)
563
+
564
+ return true
565
+ },
566
+ }
567
+
568
+
594
569
  //parsePayload
595
570
  let parsePayload = async (req) => {
596
571
 
@@ -645,49 +620,49 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
645
620
  //序列化同表之並行整表 delAll+insert: 防 lost update (同 keyTable 序列化, 不同表並行)
646
621
  return await kmx('syncAndReplaceTabs:' + keyTable, async () => {
647
622
 
648
- //from
649
- let from = get(inp, 'from', '')
650
- // console.log('from', from)
623
+ //from
624
+ let from = get(inp, 'from', '')
625
+ // console.log('from', from)
651
626
 
652
- //check
653
- if (!isestr(from)) {
654
- console.log('inp', inp)
655
- srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
656
- return Promise.reject(`invalidFrom`)
657
- }
627
+ //check
628
+ if (!isestr(from)) {
629
+ console.log('inp', inp)
630
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
631
+ return Promise.reject(`invalidFrom`)
632
+ }
658
633
 
659
- //rows
660
- let rows = get(inp, 'rows', [])
661
- // console.log(keyTable, rows)
634
+ //rows
635
+ let rows = get(inp, 'rows', [])
636
+ // console.log(keyTable, rows)
662
637
 
663
- //check
664
- if (!isearr(rows)) {
665
- console.log('inp', inp)
666
- console.log('keyTable', keyTable)
667
- srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
668
- return Promise.reject(`invalidRows`)
669
- }
638
+ //check
639
+ if (!isearr(rows)) {
640
+ console.log('inp', inp)
641
+ console.log('keyTable', keyTable)
642
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
643
+ return Promise.reject(`invalidRows`)
644
+ }
670
645
 
671
- //save from
672
- rows = map(rows, (v, k) => {
646
+ //save from
647
+ rows = map(rows, (v, k) => {
673
648
  // v.order = k + 1 //order大部分由外部給予, 不能複寫
674
- v.from = from
675
- return v
676
- })
649
+ v.from = from
650
+ return v
651
+ })
677
652
 
678
- //ltdtmapping
679
- rows = ltdtmapping(rows, ds[keyTable].keys)
680
- // console.log('ltdtmapping rows', rows)
653
+ //ltdtmapping
654
+ rows = ltdtmapping(rows, ds[keyTable].keys)
655
+ // console.log('ltdtmapping rows', rows)
681
656
 
682
- //delAll from
683
- await woItems[keyTable].delAll({ from })
657
+ //delAll from
658
+ await woItems[keyTable].delAll({ from })
684
659
 
685
- //insert
686
- // let r = await woItems[keyTable].insert(rows)
687
- let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
688
- // console.log('r', r)
660
+ //insert
661
+ // let r = await woItems[keyTable].insert(rows)
662
+ let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
663
+ // console.log('r', r)
689
664
 
690
- return r
665
+ return r
691
666
 
692
667
  })
693
668
  }
@@ -1129,8 +1104,16 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1129
1104
  return false
1130
1105
  }
1131
1106
 
1132
- //checkToken
1133
- let b = await p.checkToken(token)
1107
+ //checkToken, 失敗一律回 false(傳輸層回 permission denied); reject 之 err key 記於 verifyConn log 之 msg
1108
+ let b = false
1109
+ let msg = ''
1110
+ await p.checkToken(token, apiType)
1111
+ .then(() => {
1112
+ b = true
1113
+ })
1114
+ .catch((err) => {
1115
+ msg = (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || 'checkTokenFail')
1116
+ })
1134
1117
  // console.log('b', b)
1135
1118
 
1136
1119
  //ip, 對齊sso慣例x-forwarded-for取第1個, fallback到req remoteAddress, 取不到則略過
@@ -1145,8 +1128,13 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1145
1128
  //referer
1146
1129
  let referer = get(headers, 'referer', '')
1147
1130
 
1148
- //info
1149
- srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
1131
+ //info, 失敗時 msg 為 err key(cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等), 供資安稽核與統計
1132
+ if (b) {
1133
+ srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
1134
+ }
1135
+ else {
1136
+ srLog.error({ event: 'verifyConn-error', ok: b, apiType, ip, origin, referer, msg })
1137
+ }
1150
1138
 
1151
1139
  return b
1152
1140
  },
@@ -1173,15 +1161,14 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1173
1161
 
1174
1162
  getWebInfor,
1175
1163
 
1176
- // getTargetsList,
1177
- // getPemisList,
1178
- // getGrupsList,
1179
- // getUsersList,
1180
-
1181
1164
  updateTargets: pc.updateTargets,
1182
1165
  updatePemis: pc.updatePemis,
1183
1166
  updateGrups: pc.updateGrups,
1184
- updateUsers: pc.updateUsers,
1167
+ //updateUsers, modeEditUsers='for:grups' 時 API 與 UI 一致只採納既有使用者之 cgrups 變更, 身分欄/新增/刪除/順序以資料庫為準
1168
+ updateUsers: (userId, rows) => {
1169
+ let opt = (modeEditUsers === 'for:grups') ? { pickKeysOnly: ['cgrups'] } : {}
1170
+ return pc.updateUsers(userId, rows, opt)
1171
+ },
1185
1172
 
1186
1173
  getStaEvent: async (userId, timeLength, timeInterval) => {
1187
1174
  try {
@@ -1208,7 +1195,10 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1208
1195
  },
1209
1196
 
1210
1197
  },
1211
- fnTableTags: 'tableTags-web-perm.json',
1198
+ //fpTableTags: 各資料表時間戳檔案(供前端判定是否需重新同步)。
1199
+ //w-serv-hapi 1.0.87 起之正式名稱; 舊名 fnTableTags 於該版之前傳下去等同未給(w-serv-webdata 只讀 fpTableTags),
1200
+ //故 1.0.86 及之前實際寫的是預設之 ./tableTags.json, 升版後改用本檔名。
1201
+ fpTableTags: 'tableTags-web-perm.json',
1212
1202
  })
1213
1203
 
1214
1204
 
@@ -1231,7 +1221,7 @@ export default WWebPerm
1231
1221
  <br class="clear">
1232
1222
 
1233
1223
  <footer>
1234
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 06 2026 01:04:48 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1224
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 15:35:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1235
1225
  </footer>
1236
1226
 
1237
1227
  <script>prettyPrint();</script>
@@ -0,0 +1,311 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>server/procCore.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">server/procCore.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import get from 'lodash-es/get.js'
49
+ import each from 'lodash-es/each.js'
50
+ import map from 'lodash-es/map.js'
51
+ import size from 'lodash-es/size.js'
52
+ import sortBy from 'lodash-es/sortBy.js'
53
+ import isestr from 'wsemi/src/isestr.mjs'
54
+ import isearr from 'wsemi/src/isearr.mjs'
55
+ import haskey from 'wsemi/src/haskey.mjs'
56
+ import arrHas from 'wsemi/src/arrHas.mjs'
57
+ import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
58
+ import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
59
+ import ds from '../src/schema/index.mjs'
60
+
61
+
62
+ /**
63
+ * 僅採納既有列之指定欄位變更, 其餘欄位、新增列、缺列(刪除)與順序一律以資料庫現值為準
64
+ *
65
+ * 用於 modeEditUsers='for:grups': 使用者身分由外部單一登入系統同步, 本系統只擁有群組指派(cgrups), 故 API 層須與 UI 一致只接受 cgrups 變更
66
+ *
67
+ * @param {Array} ltdtDb 資料庫現有列(select 結果, 依 order 排序後為基底)
68
+ * @param {Array} rowsIn 前端/外部送入之整表列
69
+ * @param {String} keyDetect 對應列之鍵欄位(如 'id')
70
+ * @param {Array} pickKeys 允許採納之欄位名陣列(如 ['cgrups'])
71
+ * @returns {Array} 以資料庫列為基底、僅覆蓋 pickKeys 之新列陣列(順序同資料庫)
72
+ */
73
+ function mergePickKeysOnly(ltdtDb, rowsIn, keyDetect, pickKeys) {
74
+ let kpIn = {}
75
+ each(rowsIn, (r) => {
76
+ let v = get(r, keyDetect, '')
77
+ if (isestr(v)) {
78
+ kpIn[v] = r
79
+ }
80
+ })
81
+ let ltdt = sortBy(ltdtDb, 'order')
82
+ ltdt = map(ltdt, (u) => {
83
+ let r = { ...u }
84
+ let v = get(u, keyDetect, '')
85
+ let rin = get(kpIn, v, null)
86
+ if (rin !== null) {
87
+ each(pickKeys, (k) => {
88
+ if (haskey(rin, k)) {
89
+ r[k] = rin[k]
90
+ }
91
+ })
92
+ }
93
+ return r
94
+ })
95
+ return ltdt
96
+ }
97
+
98
+
99
+ function proc(woItems, procOrm, { srLog, kmx }) {
100
+
101
+
102
+ //updateTabItems
103
+ let updateTabItems = async (keyTable, userId, rows, keyDetect, opt = {}) => {
104
+ // console.log('updateTabItems', keyTable, userId, rows.length, keyDetect)
105
+
106
+ //pickKeysOnly, 僅採納既有列之指定欄位變更(見 mergePickKeysOnly), 未給則整表 diff 寫入
107
+ let pickKeysOnly = get(opt, 'pickKeysOnly', null)
108
+
109
+ //序列化同表之並行整表批次寫入: 防 lost update (同 keyTable 序列化, 不同表並行)
110
+ return await kmx('updateTabItems:' + keyTable, async () => {
111
+
112
+ //pickKeysOnly, 須於 mutex 內讀取資料庫為基底, 避免與並行寫入 race
113
+ if (isearr(pickKeysOnly)) {
114
+ let ltdtDb = await woItems[keyTable].select()
115
+ rows = mergePickKeysOnly(ltdtDb, rows, keyDetect, pickKeysOnly)
116
+ srLog.info({ event: 'updateTabItems-pickKeysOnly', keyTable, userId, pickKeysOnly })
117
+ }
118
+
119
+ //ltdtmapping
120
+ rows = ltdtmapping(rows, ds[keyTable].keys)
121
+ // console.log('ltdtmapping rows', rows)
122
+
123
+ //重給order
124
+ rows = map(rows, (r, k) => {
125
+ r.order = k + 1
126
+ return r
127
+ })
128
+
129
+ //ckKey, 回傳 err key (saveRowFieldInvalid / saveRowFieldDuplicate) 或 null; 動態細節 (row index/field/value) 改記 srLog, 不進使用者訊息
130
+ let ckKey = (rows, key) => {
131
+ let err = null
132
+
133
+ //check
134
+ let kp = {}
135
+ each(rows, (v, k) => {
136
+
137
+ //value
138
+ let value = get(v, key, '')
139
+
140
+ //check
141
+ if (!isestr(value)) {
142
+ srLog.error({ event: 'updateTabItems-ckKey-error', keyTable, key, msg: 'saveRowFieldInvalid', row: k })
143
+ err = `saveRowFieldInvalid`
144
+ return false //跳出
145
+ }
146
+
147
+ //check
148
+ if (haskey(kp, value)) {
149
+ srLog.error({ event: 'updateTabItems-ckKey-error', keyTable, key, msg: 'saveRowFieldDuplicate', row: k, value })
150
+ err = `saveRowFieldDuplicate`
151
+ return false //跳出
152
+ }
153
+
154
+ //kp
155
+ kp[value] = true
156
+
157
+ })
158
+
159
+ return err
160
+ }
161
+
162
+ //偵測未給予或重複
163
+ let err = null
164
+ if (true) {
165
+ if (arrHas(keyTable, ['targets'])) {
166
+ err = ckKey(rows, 'id')
167
+ if (err !== null) {
168
+ return Promise.reject(err)
169
+ }
170
+ }
171
+ if (arrHas(keyTable, ['pemis', 'grups'])) { //users可重複name故不列入
172
+ err = ckKey(rows, 'name')
173
+ if (err !== null) {
174
+ return Promise.reject(err)
175
+ }
176
+ }
177
+ if (arrHas(keyTable, ['users'])) {
178
+ err = ckKey(rows, 'email')
179
+ if (err !== null) {
180
+ return Promise.reject(err)
181
+ }
182
+ }
183
+ }
184
+
185
+ //ltdtDiffByKey
186
+ let ltdtOld = await woItems[keyTable].select()
187
+ let ltdtNew = rows
188
+ let r = ltdtDiffByKey(ltdtOld, ltdtNew, keyDetect)
189
+ // console.log('ltdtDiffByKey r', r)
190
+
191
+ //del
192
+ if (size(r.del) > 0) {
193
+ await procOrm(userId, keyTable, 'del', r.del) //須使用procOrm才有辦法自動給予相關欄位
194
+ // .catch((err) => {
195
+ // console.log('woItems[keyTable].del err', err)
196
+ // })
197
+ }
198
+
199
+ //add
200
+ if (size(r.add) > 0) {
201
+ await procOrm(userId, keyTable, 'insert', r.add) //須使用procOrm才有辦法自動給予相關欄位
202
+ // .catch((err) => {
203
+ // console.log('woItems[keyTable].insert err', err)
204
+ // })
205
+ }
206
+
207
+ //diff
208
+ if (size(r.diff) > 0) {
209
+ await procOrm(userId, keyTable, 'save', r.diff) //須使用procOrm才有辦法自動給予相關欄位
210
+ // .catch((err) => {
211
+ // console.log('woItems[keyTable].save err', err)
212
+ // })
213
+ }
214
+
215
+ return ltdtNew
216
+
217
+ })
218
+ }
219
+
220
+
221
+ //errLog: 失敗統一記 err key (updateTabItems reject 為 key 字串如 saveRowFieldInvalid; procOrm 失敗為 Error)
222
+ let errLog = (event, userId, err) => {
223
+ srLog.error({ event, userId, msg: (typeof err === 'string') ? err : ((err &amp;&amp; err.message) || event) })
224
+ }
225
+
226
+
227
+ //updateTargets
228
+ let updateTargets = async (userId, rows) => {
229
+ try {
230
+ await updateTabItems('targets', userId, rows, 'id')
231
+ }
232
+ catch (err) { errLog('updateTargets-error', userId, err); throw err }
233
+ srLog.info({ event: 'updateTargets-success', userId })
234
+ return 'saveTabItemsSuccess' //成功 msg key
235
+ }
236
+
237
+
238
+ //updatePemis
239
+ let updatePemis = async (userId, rows) => {
240
+ try {
241
+ await updateTabItems('pemis', userId, rows, 'name')
242
+ }
243
+ catch (err) { errLog('updatePemis-error', userId, err); throw err }
244
+ srLog.info({ event: 'updatePemis-success', userId })
245
+ return 'saveTabItemsSuccess' //成功 msg key
246
+ }
247
+
248
+
249
+ //updateGrups
250
+ let updateGrups = async (userId, rows) => {
251
+ try {
252
+ await updateTabItems('grups', userId, rows, 'name')
253
+ }
254
+ catch (err) { errLog('updateGrups-error', userId, err); throw err }
255
+ srLog.info({ event: 'updateGrups-success', userId })
256
+ return 'saveTabItemsSuccess' //成功 msg key
257
+ }
258
+
259
+
260
+ //updateUsers, opt.pickKeysOnly 給 ['cgrups'] 時僅採納既有使用者之群組指派變更(modeEditUsers='for:grups')
261
+ let updateUsers = async (userId, rows, opt = {}) => {
262
+ try {
263
+ await updateTabItems('users', userId, rows, 'id', opt)
264
+ }
265
+ catch (err) { errLog('updateUsers-error', userId, err); throw err }
266
+ srLog.info({ event: 'updateUsers-success', userId })
267
+ return 'saveTabItemsSuccess' //成功 msg key
268
+ }
269
+
270
+
271
+ let p = {
272
+ updateTargets,
273
+ updatePemis,
274
+ updateGrups,
275
+ updateUsers,
276
+ }
277
+
278
+
279
+ return p
280
+ }
281
+
282
+
283
+ export { mergePickKeysOnly }
284
+
285
+
286
+ export default proc
287
+ </code></pre>
288
+ </article>
289
+ </section>
290
+
291
+
292
+
293
+
294
+
295
+
296
+ </div>
297
+
298
+ <br class="clear">
299
+
300
+ <footer>
301
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 15:35:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
302
+ </footer>
303
+
304
+ <script>prettyPrint();</script>
305
+ <script src="scripts/polyfill.js"></script>
306
+ <script src="scripts/linenumber.js"></script>
307
+
308
+
309
+
310
+ </body>
311
+ </html>