w-web-perm 1.1.13 → 1.1.15
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 +98 -7
- package/dist/css/{app.f09c7433.css → app.f24c5e50.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.307bef4f.js +1 -0
- package/dist/js/app.307bef4f.js +1 -0
- package/dist/js/{chunk-vendors.ece5a361.js → chunk-vendors-legacy.6cc4d11e.js} +1 -1
- package/dist/js/{chunk-vendors-legacy.ece5a361.js → chunk-vendors.6cc4d11e.js} +1 -1
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +15 -7
- package/docs/global.html +11733 -5078
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +154 -345
- package/docs/server_adminCheck.mjs.html +92 -0
- package/docs/server_cascadeWrite.mjs.html +5 -26
- package/docs/server_dataChannel.mjs.html +4 -4
- package/docs/server_keyCheck.mjs.html +150 -23
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +261 -65
- package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
- package/docs/server_syncCore.mjs.html +533 -0
- package/docs/server_syncKeep.mjs.html +27 -20
- package/docs/server_tools_authRefsAudit.mjs.html +30 -10
- package/docs/server_tools_authRefsClean.mjs.html +2 -2
- package/docs/server_tools_fromTools.mjs.html +2 -2
- package/docs/server_tools_isMain.mjs.html +2 -2
- package/docs/server_tools_nameDupReport.mjs.html +40 -7
- package/docs/server_validateRows.mjs.html +2 -2
- package/docs/server_wrapInjected.mjs.html +2 -2
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_getPermUserInfor.mjs.html +175 -0
- package/docs/src_js_applySaved.mjs.html +128 -0
- package/docs/src_js_cellEditing.mjs.html +163 -0
- package/docs/src_js_cvKpToArr.mjs.html +2 -2
- package/docs/src_js_cvKpToObj.mjs.html +2 -2
- package/docs/src_js_draftSync.mjs.html +83 -7
- package/docs/src_js_eachOwn.mjs.html +2 -2
- package/docs/src_js_editorRefRows.mjs.html +2 -2
- package/docs/src_js_externalRows.mjs.html +34 -10
- package/docs/src_js_externalUi.mjs.html +4 -8
- package/docs/src_js_findRefRows.mjs.html +2 -2
- package/docs/src_js_findTouchedDup.mjs.html +2 -2
- package/docs/src_js_fmtLossyMsg.mjs.html +2 -2
- package/docs/src_js_fromAudit.mjs.html +2 -2
- package/docs/src_js_getAddImpact.mjs.html +2 -2
- package/docs/src_js_getAuthWarn.mjs.html +2 -2
- package/docs/src_js_getCascadePreview.mjs.html +2 -2
- package/docs/src_js_getDelRefCount.mjs.html +2 -2
- package/docs/src_js_getEffMode.mjs.html +2 -2
- package/docs/src_js_getKpLossy.mjs.html +2 -2
- package/docs/src_js_getNameErrs.mjs.html +2 -2
- package/docs/src_js_getOwn.mjs.html +2 -2
- package/docs/src_js_getPropKey.mjs.html +2 -2
- package/docs/src_js_getRuleEnable.mjs.html +2 -2
- package/docs/src_js_isAuthMode.mjs.html +2 -2
- package/docs/src_js_joinNames.mjs.html +2 -2
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_js_pairRowsToSnapshot.mjs.html +2 -2
- package/docs/src_js_refAudit.mjs.html +2 -2
- package/docs/src_js_refCascade.mjs.html +2 -2
- package/docs/src_js_refIdentity.mjs.html +500 -0
- package/docs/src_js_relDialogSend.mjs.html +2 -2
- package/docs/src_js_rowDigest.mjs.html +75 -145
- package/docs/src_plugins_dataReloadGuard.mjs.html +2 -2
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/g_provideGrups.mjs +1 -1
- package/g_providePemis.mjs +1 -1
- package/g_provideTargets.mjs +1 -1
- package/g_provideUsers.mjs +1 -1
- package/package.json +2 -2
- package/server/WWebPerm.mjs +152 -343
- package/server/adminCheck.mjs +20 -0
- package/server/cascadeWrite.mjs +3 -24
- package/server/dataChannel.mjs +2 -2
- package/server/keyCheck.mjs +148 -21
- package/server/procCore.mjs +259 -63
- package/server/procLang.mjs +78 -4
- package/server/provideTabs.mjs +6 -4
- package/server/syncCore.mjs +461 -0
- package/server/syncKeep.mjs +25 -18
- package/server/tools/authRefsAudit.mjs +28 -8
- package/server/tools/nameDupReport.mjs +38 -5
- package/src/App.vue +6 -1
- package/src/components/LayoutContentGrups.vue +57 -8
- package/src/components/LayoutContentPemis.vue +57 -8
- package/src/components/LayoutContentTargets.vue +38 -7
- package/src/components/LayoutContentUsers.vue +57 -8
- package/src/components/LayoutState.vue +12 -0
- package/src/components/VeGrupBlngUsers.vue +128 -20
- package/src/components/VePemiBlngGrups.vue +128 -20
- package/src/getPermUserInfor.mjs +10 -0
- package/src/js/applySaved.mjs +56 -0
- package/src/js/cellEditing.mjs +91 -0
- package/src/js/draftSync.mjs +81 -5
- package/src/js/externalRows.mjs +32 -8
- package/src/js/externalUi.mjs +2 -6
- package/src/js/refIdentity.mjs +428 -0
- package/src/js/rowDigest.mjs +73 -143
- package/src/plugins/mUI.mjs +5 -0
- package/src/schema/contracts/grups.mjs +8 -0
- package/src/schema/contracts/index.mjs +17 -0
- package/src/schema/contracts/pemis.mjs +8 -0
- package/src/schema/contracts/targets.mjs +8 -0
- package/src/schema/contracts/users.mjs +13 -0
- package/src/schema/tables/grups.mjs +4 -0
- package/src/schema/tables/pemis.mjs +4 -0
- package/src/schema/tables/targets.mjs +4 -0
- package/src/schema/tables/users.mjs +4 -0
- package/src/store/mutations.mjs +1 -1
- package/srv.mjs +43 -0
- package/dist/js/app-legacy.8af87052.js +0 -1
- package/dist/js/app.8af87052.js +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 是否為系統管理員: perm users 表之列 isActive 為 'y' 且 isAdmin 為 'y'(自有屬性、嚴格相等, 其餘值一律視為否)
|
|
3
|
+
*
|
|
4
|
+
* 判準單一擁有者(R-ADMIN-ONLY-01): 資料通道連線閘(checkToken)、資料通道執行身分(getUserIdByToken)、後台登入檢查(/api/getUserByToken)與寫入函數之角色檢查(requireAdmin)都只呼叫本函數.
|
|
5
|
+
* perm 以自身 users 表為權威, 不採外部 token(getUserByToken)宣稱之 isAdmin; 呼叫端須傳入 perm users 表之列(getTokenUser 反查所得, 或 requireAdmin 新鮮讀取之列), 不得傳 token 使用者物件.
|
|
6
|
+
* 只認自有屬性: 原型鏈上繼承而來之 isAdmin/isActive 不算(防禦性, 資料庫列恆為自有屬性).
|
|
7
|
+
*
|
|
8
|
+
* @param {*} row 輸入 perm users 表之列(任何值)
|
|
9
|
+
* @returns {Boolean} 回傳是否為有效之系統管理員
|
|
10
|
+
*/
|
|
11
|
+
function isAdminRow(row) {
|
|
12
|
+
if (row === null || typeof row !== 'object' || Array.isArray(row)) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
let has = (k) => Object.prototype.hasOwnProperty.call(row, k)
|
|
16
|
+
return has('isActive') && has('isAdmin') && row.isActive === 'y' && row.isAdmin === 'y'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export default isAdminRow
|
package/server/cascadeWrite.mjs
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import truncate from 'lodash-es/truncate.js'
|
|
2
|
-
import ds from '../src/schema/index.mjs'
|
|
3
|
-
import { fillIdlessIds } from '../src/js/refCascade.mjs'
|
|
4
2
|
import validateRowsBeforeWrite from './validateRows.mjs'
|
|
5
3
|
|
|
6
4
|
|
|
@@ -13,7 +11,7 @@ import validateRowsBeforeWrite from './validateRows.mjs'
|
|
|
13
11
|
// 使資料回到寫入前之狀態再回報原錯誤, 重新操作不依賴「冪等重存」; 主體部分寫入之舊限制不變(ADR-013: 整表寫入為 del → insert → save 非交易, 其部分寫入之處置維持「重新整理後確認」, 本檔不補償主體);
|
|
14
12
|
// ③ 補償本身失敗(雙重故障)時回 saveCascadePartial 並記 srLog(event refCascade-restore-error, 帶引用端表名與列數, 不記個資值), 由稽核工具(server/tools/authRefsAudit.mjs)列出懸空引用後人工處置;
|
|
15
13
|
// ④ 行程於引用端寫入後、主體寫入前崩潰者不在補償範圍(記憶體內無日誌), 同③以稽核工具處置.
|
|
16
|
-
//鎖: 呼叫端須已以 withTabLocks 取得被引用表與引用端表之鎖(server/keyCheck.mjs); 本檔不取鎖.
|
|
14
|
+
//鎖: 呼叫端須已以 withTabLocks 取得被引用表與引用端表之鎖(server/keyCheck.mjs, 其底層即 w-serv-orm 實例之 withTabLocks); 本檔不取鎖.
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
/**
|
|
@@ -90,7 +88,7 @@ async function writeWithCascade({ plan, holdersOld, woItems, procOrm, userId, sr
|
|
|
90
88
|
let rm = null
|
|
91
89
|
let stage = 'holder'
|
|
92
90
|
try {
|
|
93
|
-
await procOrm(userId, plan.holder, 'save', post) //須使用procOrm
|
|
91
|
+
await procOrm(userId, plan.holder, 'save', post, { mustExist: true }) //須使用procOrm才有辦法自動給予更新者與更新時間; 只改不增: 引用端列於持鎖後新鮮讀取, 理論上必在, 不在即 rowNotFound 零寫入(已刪之列不復活, 規格第 6 節)
|
|
94
92
|
stage = 'main'
|
|
95
93
|
rm = await writeMain()
|
|
96
94
|
}
|
|
@@ -131,23 +129,4 @@ async function writeWithCascade({ plan, holdersOld, woItems, procOrm, userId, sr
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
* 外部同步(delAll({ from }) + insert)之「寫入後整表」, 供 planRefChanges 判定同一 from 內之改名與刪除(Q6: 依同一來源內之穩定 id 判定)
|
|
136
|
-
*
|
|
137
|
-
* 他 from 之既有列原樣; 本批各列帶 id 者以其 id(有限數字取字串形式)為身分(同 id 異名=改名、舊 id 不在本批=刪除);
|
|
138
|
-
* 本批未帶 id 之列(grups/pemis, 寫入時由 ORM 補隨機主鍵, 每次同步皆不同)無穩定 id 可依, 改以「名稱」對應同一 from 之既有列(同名即同一實體, 不算刪除再新增; 名稱不同即視為舊列刪除、新列新增), 僅供規劃, 不改寫入之列.
|
|
139
|
-
*
|
|
140
|
-
* @param {String} keyTable 輸入同步之表名
|
|
141
|
-
* @param {Array} rowsOld 輸入該表全部既有列(select 結果)
|
|
142
|
-
* @param {Array} rows 輸入本批列(已 ltdtmapping 並帶 from)
|
|
143
|
-
* @param {String} from 輸入本批之 from
|
|
144
|
-
* @returns {Array} 回傳寫入後之整表(僅供規劃, 不寫入)
|
|
145
|
-
*/
|
|
146
|
-
function buildSyncRowsNew(keyTable, rowsOld, rows, from) {
|
|
147
|
-
let others = rowsOld.filter((r) => r !== null && typeof r === 'object' && r.from !== from)
|
|
148
|
-
let sameFrom = rowsOld.filter((r) => r !== null && typeof r === 'object' && r.from === from)
|
|
149
|
-
return [...others, ...fillIdlessIds(keyTable, sameFrom, rows, others)] //未帶 id 之列依名稱對應同一 from 之既有列(見 fillIdlessIds); 規劃 id 須避開他 from 列之主鍵
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
export { checkCascadePlan, writeWithCascade, buildSyncRowsNew }
|
|
132
|
+
export { checkCascadePlan, writeWithCascade }
|
package/server/dataChannel.mjs
CHANGED
|
@@ -4,9 +4,9 @@ import isestr from 'wsemi/src/isestr.mjs'
|
|
|
4
4
|
//資料通道(w-serv-webdata, useDbOrm:true)之表操作授權與 ORM 呼叫包裝(規則帳本 R-WSD-01、R-ORM-02; spec/設計要點與取捨.md ADR-026、ADR-015 Update)
|
|
5
5
|
//
|
|
6
6
|
//政策: 資料通道只開 select(methodsExec:['select'] 為第一道, 本授權函數只放行 select 為第二道).
|
|
7
|
-
//why: 裸 insert/save/del/delAll 繞過 procCore 之 lockSave、
|
|
7
|
+
//why: 裸 insert/save/del/delAll 繞過 procCore 之 lockSave、表鎖(w-serv-orm withTabLocks/guardWrite)、ckKey、寫入前驗證與整表比對(ADR-009/012/025/026 之不變式只在 kpFunExt 成立),
|
|
8
8
|
//同表兩條寫入路徑不對稱; 前端與測試皆只用 kpFunExt 寫入、同步只用 select(2026-10-14 Grep `\$fapi\.`、`callRpc(` 實證).
|
|
9
|
-
//連線者之身分授權仍在 verifyConn(
|
|
9
|
+
//連線者之身分授權仍在 verifyConn(getAndVerifyAdminUser: 完整驗證+限系統管理員), 本函數不重做.
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
/**
|
package/server/keyCheck.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import findTouchedDup from '../src/js/findTouchedDup.mjs' //判準單一擁有
|
|
|
4
4
|
|
|
5
5
|
//名稱唯一(F-02)與使用者對應鍵(F-14)之判準, 單一擁有者(規則帳本 R-NAME-UNIQ-01/02、R-MAPKEY-01/02/03, ADR-034):
|
|
6
6
|
// 外部同步(WWebPerm.mjs:syncAndReplaceTabs)、後台整表寫入(procCore.mjs:updateTabItems)、登入查找(WWebPerm.mjs:getTokenUser/getGenUserByKV)只呼叫本檔, 不複製判準.
|
|
7
|
+
//同表寫入互斥(外審 sol R10): 外部同步與後台整表寫入對同一張表須共用同一把鎖(否則兩者各自「先檢查名稱/對應鍵、後寫入」可交錯而略過 F-02/F-14);
|
|
8
|
+
//perm-run01 起該鎖即 w-serv-orm 實例之 withTabLocks(每表一鍵), 由下方 withTabLocks 以階層巢狀取得, 不再有套內自建之鎖鍵(tabWriteKey 已移除).
|
|
7
9
|
//比對一律精確 ===(與伺服端 arrFinder 與 getTokenUser 之 select 一致; 不做大小寫/空白正規化: 正規化後比對會放行伺服端視為不同之名稱, 或拒絕伺服端視為不同之鍵).
|
|
8
10
|
//鍵之值非字串者不參與重複比對(空/非字串另由呼叫端之 isestr 檢查負責). 以 Map/Set 計, 免原型鍵名(constructor、__proto__)誤判(規則帳本 R-KEY-01).
|
|
9
11
|
|
|
@@ -97,37 +99,26 @@ function findKeyOtherFrom(rows, rowsExist, from, key, opt = {}) {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
* @param {String} keyTable 表名
|
|
106
|
-
* @returns {String} 回傳 kmx 鍵
|
|
107
|
-
*/
|
|
108
|
-
function tabWriteKey(keyTable) {
|
|
109
|
-
return 'tabWrite:' + keyTable
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//取鎖階層(規則帳本 R-WRITE-LOCK-02, ADR-036): 引用鏈序「被引用者先、引用端後」targets → pemis → grups → users(引用端之階層恆高於被引用者); 所有需持多把表鎖之路徑(後台整表寫入、外部同步、日後之外部列保護/清源)只能經 withTabLocks 取鎖,
|
|
114
|
-
//同一把鎖不巢狀(pmKeyMutex 非可重入, 巢狀同鍵與相反次序皆死結, D1 設計稿 probe-mutex 實測); 持鎖後才讀取、預檢與寫入, 使預檢與寫入涵蓋同一版本(外審 A.3)
|
|
102
|
+
//取鎖階層(規則帳本 R-WRITE-LOCK-02, ADR-036; perm-run01 起鎖管理器改為 w-serv-orm 實例之 withTabLocks, R-LOCK-CORE-01): 引用鏈序「被引用者先、引用端後」targets → pemis → grups → users(引用端之階層恆高於被引用者);
|
|
103
|
+
//所有寫入入口(後台整表寫入、外部同步、引用連帶之引用端寫入)取得的是**同一個**鎖管理器(WServOrm 回傳之 withTabLocks, 每表一鍵、不可重入), 與 w-serv-orm 之 guardWrite 共鎖.
|
|
104
|
+
//多把表鎖一律由外而內「依階層」逐表巢狀取鎖(每層一次只宣告一張表), 不使用核心之多表一次宣告(其碼元序與階層序不同, 混用即成環);
|
|
105
|
+
//同一把鎖不巢狀, 持鎖後才讀取、預檢與寫入, 使預檢與寫入涵蓋同一版本(外審 A.3)
|
|
115
106
|
let TAB_LOCK_RANK = { targets: 0, pemis: 1, grups: 2, users: 3 }
|
|
116
107
|
|
|
117
108
|
|
|
118
109
|
/**
|
|
119
|
-
* 依取鎖階層取齊指定表鎖後執行 fn(單一取鎖函數): 表名去重、依 targets → pemis → grups → users 排序(階層外之表名排最後、彼此依字典序), 由外而內巢狀取
|
|
110
|
+
* 依取鎖階層取齊指定表鎖後執行 fn(單一取鎖函數): 表名去重、依 targets → pemis → grups → users 排序(階層外之表名排最後、彼此依字典序), 由外而內巢狀取 lockTabs([表]);
|
|
120
111
|
* 各路徑取鎖次序一致故不成環.
|
|
121
112
|
* 分段取鎖: 先取被引用表之鎖、依該表之新舊資料判定須改寫引用端後, 才在內層以 held 指明已持之表再取引用端表之鎖(寫入引用端表不涉改名/刪除者免取, 免無謂之互等);
|
|
122
113
|
* held 非空時, keyTables 之階層須全數高於 held(否則拋錯, 即違反階層, 不嘗試取鎖).
|
|
123
114
|
*
|
|
124
|
-
* @param {Function}
|
|
115
|
+
* @param {Function} lockTabs 輸入 w-serv-orm 實例之 withTabLocks(tableNames, fn)
|
|
125
116
|
* @param {Array} keyTables 輸入須持鎖之表名陣列
|
|
126
117
|
* @param {Function} fn 輸入持鎖後執行之 async 函數
|
|
127
118
|
* @param {Array} [held=[]] 輸入呼叫端已持有鎖之表名陣列(內層分段取鎖用)
|
|
128
119
|
* @returns {Promise} 回傳 fn 之結果
|
|
129
120
|
*/
|
|
130
|
-
function withTabLocks(
|
|
121
|
+
function withTabLocks(lockTabs, keyTables, fn, held = []) {
|
|
131
122
|
let rank = (t) => (Object.prototype.hasOwnProperty.call(TAB_LOCK_RANK, t) ? TAB_LOCK_RANK[t] : Infinity)
|
|
132
123
|
let ts = [...new Set(keyTables)].sort((a, b) => (rank(a) - rank(b)) || (a < b ? -1 : (a > b ? 1 : 0)))
|
|
133
124
|
if (held.length > 0) {
|
|
@@ -139,8 +130,8 @@ function withTabLocks(kmx, keyTables, fn, held = []) {
|
|
|
139
130
|
let run = fn
|
|
140
131
|
for (let k = ts.length - 1; k >= 0; k--) {
|
|
141
132
|
let inner = run
|
|
142
|
-
let
|
|
143
|
-
run = () =>
|
|
133
|
+
let t = ts[k]
|
|
134
|
+
run = () => lockTabs([t], inner)
|
|
144
135
|
}
|
|
145
136
|
return run()
|
|
146
137
|
}
|
|
@@ -278,4 +269,140 @@ function pickUniqueUser(rs) {
|
|
|
278
269
|
}
|
|
279
270
|
|
|
280
271
|
|
|
281
|
-
|
|
272
|
+
//users 登入對應 mappingBy='id' 之 O1 規則(perm-run04, 規則帳本 R-MAPKEY-04; 規格 dspec/外部匯入之定義與機制.md 第 9 節末, 主控 2026-10-24 定案):
|
|
273
|
+
// 外部列(from 非空)以外部 token 之使用者 id 對 srcKey(其 id 是本系統配發之內部主鍵, 不是登入識別), 後台列(from 空)對 id.
|
|
274
|
+
// perm-run05(主控 2026-10-24 收件裁示): 登入識別值須跨全表唯一, 「不論 isActive」「含他來源」: 不同外部系統之使用者 id 碰撞常見而非同一人, 且停用列可被重新啟用,
|
|
275
|
+
// 故「只有一列有效」不得放行(否則已停用者之 token 會對到他來源之有效列). 撞值之組合: 同一登入識別值出現在兩列, 且該對不是「兩後台列」(同一主鍵)、「同一來源皆有 srcKey 之兩外部列」(srcKeyDuplicate/srcKeyConflict 於同步端另拒)與「同一來源皆無 srcKey 之兩舊外部列」(同一主鍵); 詳見 findLoginIdClashDetail.
|
|
276
|
+
// 匯入批次、後台存檔、登入三處皆拒(原則 3 收嚴+提示、原則 4 所有入口一致); 登入時命中多筆(不論有效與否)即拒絕, 恰 1 筆且停用視同查無.
|
|
277
|
+
// 判準(loginIdOf/findLoginUsers/findLoginIdClashDetail)單一擁有者在本檔; 外部同步(syncCore.mjs)、後台整表寫入(procCore.mjs)、登入查找與 getPermUserInfor(WWebPerm.mjs)只呼叫本檔.
|
|
278
|
+
let isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
279
|
+
let isEStr = (v) => isStr(v) && v.length > 0
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* users 列在 mappingBy='id' 下的登入識別值: 後台列(from 空)為 id; 外部列(from 非空)為 srcKey;
|
|
284
|
+
* 遷移前之舊外部列(升版前同步寫入、尚無 srcKey)暫以 id(與升版前行為相同, 首次匯入依規格補上 srcKey 後即改以 srcKey).
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} row users 列
|
|
287
|
+
* @returns {String} 登入識別值; 無可用鍵(非物件、id 空或非字串)為 ''
|
|
288
|
+
*/
|
|
289
|
+
function loginIdOf(row) {
|
|
290
|
+
if (!isObj(row)) {
|
|
291
|
+
return ''
|
|
292
|
+
}
|
|
293
|
+
if (isEStr(row.from) && isEStr(row.srcKey)) {
|
|
294
|
+
return row.srcKey
|
|
295
|
+
}
|
|
296
|
+
return isEStr(row.id) ? row.id : ''
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 登入查找(getTokenUser/getPermUserInfor): 回 { user, count }(僅恰 1 筆有效列時 user 為該列, 多筆不取首筆).
|
|
302
|
+
* 'email'/'name': 以該欄位查 perm users 中有效(isActive='y')之列(不變, 同 pickUniqueUser).
|
|
303
|
+
* 其餘(含預設 'id'): 後台列對 id、外部列對 srcKey(見上方 O1 規則), 「不論 isActive」計筆(perm-run05): 兩次帶條件之 select(id、srcKey 各一; 不帶 isActive, 不做全表讀取),
|
|
304
|
+
* 合併去重後以 loginIdOf 複判; 多於 1 筆 → count 為筆數、user 為 null(不以「只有一列有效」放行); 恰 1 筆且有效 → 該列; 恰 1 筆但停用 → count 0(視同查無).
|
|
305
|
+
* ORM 讀取失敗原樣拋出(不吞成 0 筆).
|
|
306
|
+
*
|
|
307
|
+
* @param {Object} tabUsers 輸入 users 表之 ORM 物件(select)
|
|
308
|
+
* @param {String} mappingBy 'id'|'email'|'name'(其他值視同 'id')
|
|
309
|
+
* @param {String} vSelf 外部 token 之使用者對應值(須為非空字串)
|
|
310
|
+
* @returns {Promise<Object>} { user, count }
|
|
311
|
+
*/
|
|
312
|
+
async function findLoginUsers(tabUsers, mappingBy, vSelf) {
|
|
313
|
+
let key = mapKeyOf(mappingBy)
|
|
314
|
+
if (key !== null) {
|
|
315
|
+
return pickUniqueUser(await tabUsers.select({ [key]: vSelf, isActive: 'y' }))
|
|
316
|
+
}
|
|
317
|
+
let rsId = await tabUsers.select({ id: vSelf })
|
|
318
|
+
let rsSrc = await tabUsers.select({ srcKey: vSelf })
|
|
319
|
+
let kp = new Map() //以 id 去重(srcKey 等於 id 之遷移後外部列會被兩次查詢各命中一次)
|
|
320
|
+
for (let r of [...(Array.isArray(rsId) ? rsId : []), ...(Array.isArray(rsSrc) ? rsSrc : [])]) {
|
|
321
|
+
if (isObj(r) && isEStr(r.id)) {
|
|
322
|
+
kp.set(r.id, r)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
let hits = [...kp.values()].filter((r) => loginIdOf(r) === vSelf)
|
|
326
|
+
if (hits.length === 1 && hits[0].isActive !== 'y') {
|
|
327
|
+
return { user: null, count: 0 }
|
|
328
|
+
}
|
|
329
|
+
return pickUniqueUser(hits)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* 登入識別撞值(mappingBy='id'): 回傳寫入後之表 rowsAfter 中第一個「被本次觸及」之列, 其登入識別值(loginIdOf)與另一列相同且屬撞值組合者之細節, 無則 null.
|
|
335
|
+
* 「不論 isActive」「含他來源」(perm-run05): 撞值組合=同一登入識別值出現在兩列, 且該對不是下列三種不屬登入識別撞值者:
|
|
336
|
+
* - 兩後台列(登入識別值=主鍵 id, 同一主鍵於表中不可並存);
|
|
337
|
+
* - 同一來源(from 相同)之兩外部列且皆有 srcKey(srcKeyDuplicate/srcKeyConflict 於同步端另拒);
|
|
338
|
+
* - 同一來源之兩舊外部列(皆無 srcKey, 登入識別值=id, 即同一主鍵).
|
|
339
|
+
* 撞值之種類:
|
|
340
|
+
* - adminId: 一為後台列(from 空)、一為外部列(其 srcKey; 舊外部列無 srcKey 者為其 id)相同;
|
|
341
|
+
* - otherFrom: 兩外部列來源不同而登入識別值相同;
|
|
342
|
+
* - sameFrom: 同一來源之兩外部列, 一有 srcKey、一為舊形(無 srcKey, 以其 id 為登入識別)而該 id 等於對方 srcKey——即同步端之 claimTwice(srcKeyConflict), 登入亦命中兩筆
|
|
343
|
+
* (後台新增外部形列只在 lockExternalRows 為假時可能; 外審 sol 第一輪#1).
|
|
344
|
+
* 歷史既有(未被觸及)之撞值不擋(否則已有歷史資料之庫無法在後台修復; 同 R-MAPKEY-03). 外部列之內部 id(有 srcKey 者)不是登入識別, 不參與.
|
|
345
|
+
*
|
|
346
|
+
* @param {Array} rowsAfter 寫入後之 users 全表(後台整表寫入為送入之整表; 外部匯入為「他 from 之列+本批」)
|
|
347
|
+
* @param {Function} isTouched (row, index) → Boolean, 該列是否為本次造成或觸及
|
|
348
|
+
* @returns {Object|null} { k, kOther, kind: 'adminId'|'otherFrom'|'sameFrom' }(k 為第一個被觸及且撞值之列索引, kOther 為與之撞值的另一列中索引最小者), 無則 null
|
|
349
|
+
*/
|
|
350
|
+
function findLoginIdClashDetail(rowsAfter, isTouched) {
|
|
351
|
+
if (!Array.isArray(rowsAfter)) {
|
|
352
|
+
return null
|
|
353
|
+
}
|
|
354
|
+
let groups = new Map() //登入識別值 → 列索引陣列(以 Map 計, 免原型鍵名誤判)
|
|
355
|
+
rowsAfter.forEach((r, k) => {
|
|
356
|
+
let v = loginIdOf(r)
|
|
357
|
+
if (v === '') {
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
if (!groups.has(v)) {
|
|
361
|
+
groups.set(v, [])
|
|
362
|
+
}
|
|
363
|
+
groups.get(v).push(k)
|
|
364
|
+
})
|
|
365
|
+
for (let k = 0; k < rowsAfter.length; k++) {
|
|
366
|
+
let r = rowsAfter[k]
|
|
367
|
+
let v = loginIdOf(r)
|
|
368
|
+
if (v === '' || !isTouched(r, k)) {
|
|
369
|
+
continue
|
|
370
|
+
}
|
|
371
|
+
let rx = isEStr(r.from)
|
|
372
|
+
for (let kOther of groups.get(v)) {
|
|
373
|
+
if (kOther === k) {
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
let o = rowsAfter[kOther]
|
|
377
|
+
let ox = isEStr(o.from)
|
|
378
|
+
if (!rx && !ox) {
|
|
379
|
+
continue
|
|
380
|
+
}
|
|
381
|
+
if (rx && ox && r.from === o.from) {
|
|
382
|
+
//同一來源: 皆有 srcKey 或皆無 srcKey 者不在此判; 一有一無(claimTwice 形)才撞
|
|
383
|
+
if (isEStr(r.srcKey) === isEStr(o.srcKey)) {
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
return { k, kOther, kind: 'sameFrom' }
|
|
387
|
+
}
|
|
388
|
+
return { k, kOther, kind: (rx && ox) ? 'otherFrom' : 'adminId' }
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return null
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* 登入識別撞值(mappingBy='id'): findLoginIdClashDetail 之索引版, 回第一個被觸及且撞值之列索引, 無則 -1
|
|
397
|
+
*
|
|
398
|
+
* @param {Array} rowsAfter 寫入後之 users 全表
|
|
399
|
+
* @param {Function} isTouched (row, index) → Boolean, 該列是否為本次造成或觸及
|
|
400
|
+
* @returns {Number} 撞值列之索引, 無為 -1
|
|
401
|
+
*/
|
|
402
|
+
function findLoginIdClash(rowsAfter, isTouched) {
|
|
403
|
+
let d = findLoginIdClashDetail(rowsAfter, isTouched)
|
|
404
|
+
return d === null ? -1 : d.k
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
export { findDupIndex, hasDupKey, findKeyOtherFrom, findTouchedDup, withTabLocks, mapKeyOf, checkMapKeyBatch, checkUsersWrite, pickUniqueUser, loginIdOf, findLoginUsers, findLoginIdClash, findLoginIdClashDetail }
|