w-web-perm 1.1.11 → 1.1.13
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 +158 -117
- package/dist/css/app.f09c7433.css +1 -0
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.8af87052.js +1 -0
- package/dist/js/app.8af87052.js +1 -0
- package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
- package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.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 +84 -4
- package/docs/global.html +24747 -832
- package/docs/index.html +2 -2
- package/docs/server_WWebPerm.mjs.html +245 -32
- package/docs/server_cascadeWrite.mjs.html +225 -0
- package/docs/server_dataChannel.mjs.html +2 -2
- package/docs/server_keyCheck.mjs.html +353 -0
- package/docs/server_lockSave.mjs.html +2 -2
- package/docs/server_maskLog.mjs.html +2 -2
- package/docs/server_procCore.mjs.html +180 -41
- package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
- package/docs/server_syncKeep.mjs.html +311 -0
- package/docs/server_tools_authRefsAudit.mjs.html +247 -0
- package/docs/server_tools_authRefsClean.mjs.html +335 -0
- package/docs/server_tools_fromTools.mjs.html +797 -0
- package/docs/server_tools_isMain.mjs.html +103 -0
- package/docs/server_tools_nameDupReport.mjs.html +280 -0
- package/docs/server_validateRows.mjs.html +15 -4
- package/docs/server_wrapInjected.mjs.html +3 -3
- package/docs/src_fetchJson.mjs.html +2 -2
- package/docs/src_js_cvKpToArr.mjs.html +125 -0
- package/docs/src_js_cvKpToObj.mjs.html +210 -0
- package/docs/src_js_draftSync.mjs.html +142 -0
- package/docs/src_js_eachOwn.mjs.html +3 -3
- package/docs/src_js_editorRefRows.mjs.html +356 -0
- package/docs/src_js_externalRows.mjs.html +338 -0
- package/docs/src_js_externalUi.mjs.html +230 -0
- package/docs/src_js_findRefRows.mjs.html +107 -0
- package/docs/src_js_findTouchedDup.mjs.html +150 -0
- package/docs/src_js_fmtLossyMsg.mjs.html +95 -0
- package/docs/src_js_fromAudit.mjs.html +489 -0
- package/docs/src_js_getAddImpact.mjs.html +269 -0
- package/docs/src_js_getAuthWarn.mjs.html +463 -0
- package/docs/src_js_getCascadePreview.mjs.html +258 -0
- package/docs/src_js_getDelRefCount.mjs.html +210 -0
- package/docs/src_js_getEffMode.mjs.html +92 -0
- package/docs/src_js_getKpLossy.mjs.html +155 -0
- package/docs/src_js_getNameErrs.mjs.html +266 -0
- package/docs/src_js_getOwn.mjs.html +3 -3
- package/docs/src_js_getPropKey.mjs.html +109 -0
- package/docs/src_js_getRuleEnable.mjs.html +2 -2
- package/docs/src_js_isAuthMode.mjs.html +92 -0
- package/docs/src_js_joinNames.mjs.html +96 -0
- package/docs/src_js_legendFit.mjs.html +2 -2
- package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
- package/docs/src_js_refAudit.mjs.html +359 -0
- package/docs/src_js_refCascade.mjs.html +572 -0
- package/docs/src_js_relDialogSend.mjs.html +199 -0
- package/docs/src_js_rowDigest.mjs.html +265 -0
- package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
- package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
- package/docs/src_plugins_submitGuard.mjs.html +2 -2
- package/g_authRefsClean.mjs +79 -0
- package/g_fixFromType.mjs +92 -0
- package/g_initialData.mjs +3 -3
- package/g_purgeFrom.mjs +127 -0
- package/package.json +1 -1
- package/server/WWebPerm.mjs +243 -30
- package/server/cascadeWrite.mjs +153 -0
- package/server/keyCheck.mjs +281 -0
- package/server/procCore.mjs +178 -39
- package/server/procLang.mjs +271 -0
- package/server/syncKeep.mjs +239 -0
- package/server/tools/authRefsAudit.mjs +175 -0
- package/server/tools/authRefsClean.mjs +263 -0
- package/server/tools/fromTools.mjs +725 -0
- package/server/tools/isMain.mjs +31 -0
- package/server/tools/nameDupReport.mjs +208 -0
- package/server/validateRows.mjs +13 -2
- package/server/wrapInjected.mjs +1 -1
- package/src/components/LayoutContentGrups.vue +399 -77
- package/src/components/LayoutContentPemis.vue +400 -78
- package/src/components/LayoutContentTargets.vue +261 -25
- package/src/components/LayoutContentUsers.vue +336 -98
- package/src/components/VeCgrups.vue +119 -115
- package/src/components/VeCpemis.vue +117 -115
- package/src/components/VeCrules.vue +18 -2
- package/src/components/VeGrupBlngUsers.vue +240 -73
- package/src/components/VePemiBlngGrups.vue +247 -83
- package/src/js/cvKpToArr.mjs +53 -0
- package/src/js/cvKpToObj.mjs +138 -0
- package/src/js/draftSync.mjs +70 -0
- package/src/js/eachOwn.mjs +1 -1
- package/src/js/editorRefRows.mjs +284 -0
- package/src/js/externalRows.mjs +266 -0
- package/src/js/externalUi.mjs +158 -0
- package/src/js/findRefRows.mjs +35 -0
- package/src/js/findTouchedDup.mjs +78 -0
- package/src/js/fmtLossyMsg.mjs +23 -0
- package/src/js/fromAudit.mjs +417 -0
- package/src/js/getAddImpact.mjs +197 -0
- package/src/js/getAuthWarn.mjs +391 -0
- package/src/js/getCascadePreview.mjs +186 -0
- package/src/js/getDelRefCount.mjs +138 -0
- package/src/js/getEffMode.mjs +20 -0
- package/src/js/getKpLossy.mjs +83 -0
- package/src/js/getNameErrs.mjs +194 -0
- package/src/js/getOwn.mjs +1 -1
- package/src/js/getPropKey.mjs +37 -0
- package/src/js/isAuthMode.mjs +20 -0
- package/src/js/joinNames.mjs +24 -0
- package/src/js/pairRowsToSnapshot.mjs +84 -0
- package/src/js/refAudit.mjs +287 -0
- package/src/js/refCascade.mjs +500 -0
- package/src/js/relDialogSend.mjs +127 -0
- package/src/js/rowDigest.mjs +193 -0
- package/src/plugins/dataReloadGuard.mjs +37 -0
- package/src/plugins/mShare.mjs +147 -174
- package/src/plugins/mUI.mjs +3 -8
- package/src/schema/tables/users.mjs +152 -152
- package/dist/css/app.6918ec45.css +0 -1
- package/dist/js/app-legacy.963e800c.js +0 -1
- package/dist/js/app.963e800c.js +0 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>server/syncKeep.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#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">server/syncKeep.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>//外部同步(WWebPerm.mjs:syncAndReplaceTabs)之三個純函數(F-23; R-PRINCIPLE-00 原則 3、5; 規則帳本 R-EXT-OWN-01; ADR-037), 不依賴 ORM/Vue, 供單元測試:
|
|
49
|
+
// keepAssignOnMissing: users 列「payload 缺 cgrups 鍵」時保留庫內指派(指派欄屬後台, Lv1; 含該鍵則以外部為準)——修正前 ltdtmapping 缺鍵補 ''、delAll+insert 即清掉後台已指派之群組(run06 已知限制④);
|
|
50
|
+
// keepAssignByEntity: grups 列缺 cpemis 鍵、pemis 列缺 crules 鍵時同樣保留(run08; 同為後台可寫之指派欄)——群組/權限無登入對應鍵, 實體身分依 id(無 id 者依名稱), 見該函數說明;
|
|
51
|
+
// countIdRenumber: 同一 from 同一批內出現「同名異 id」(疑似外部 id 重編)之筆數, 供 warn log 與頁面提示(外部 id 不穩定時, id 消失=刪除會每次清理引用而抹掉後台指派, 不加開關, 以提示處理)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
let isStr = (v) => typeof v === 'string'
|
|
55
|
+
let isObj = (v) => v !== null && typeof v === 'object'
|
|
56
|
+
let hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
//toId: 列之 id 取字串形式(非空字串; 有限數字取其字串), 否則 null(同 refCascade.fillIdlessIds)
|
|
60
|
+
let toId = (v) => {
|
|
61
|
+
let id = isObj(v) ? v.id : undefined
|
|
62
|
+
if (isStr(id) && id !== '') {
|
|
63
|
+
return id
|
|
64
|
+
}
|
|
65
|
+
return (typeof id === 'number' && Number.isFinite(id)) ? String(id) : null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 記錄 payload 各列是否自帶指定鍵(須於 ltdtmapping 補鍵之前呼叫; ltdtmapping 為 1:1 同序, 故可依索引對應)
|
|
71
|
+
*
|
|
72
|
+
* @param {Array} rowsRaw 輸入 payload 原始列(皆為物件)
|
|
73
|
+
* @param {String} key 輸入鍵名(如 'cgrups')
|
|
74
|
+
* @returns {Array} 回傳布林陣列, 第 k 項為第 k 列是否自帶該鍵(自有屬性)
|
|
75
|
+
*/
|
|
76
|
+
function markHasKey(rowsRaw, key) {
|
|
77
|
+
return rowsRaw.map((v) => isObj(v) && hasOwn(v, key))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* users 列缺 cgrups 鍵者, 以庫內同一 from 之對應列之值取代(對應: 先依 id; 無 id 者在 mappingBy 為 email/name 時依該欄位, 須恰命中 1 筆; 對不到則不保留, 即新使用者/無從對應者維持 '')
|
|
83
|
+
*
|
|
84
|
+
* @param {Object} p 輸入參數物件
|
|
85
|
+
* @param {Array} p.rows 輸入已 ltdtmapping 並帶 from 之本批列
|
|
86
|
+
* @param {Array} p.hasKey 輸入 markHasKey 之結果(與 rows 同序同長)
|
|
87
|
+
* @param {Array} p.rowsOldSameFrom 輸入庫內同一 from 之既有 users 列
|
|
88
|
+
* @param {String} [p.mappingBy='id'] 輸入使用者對應鍵('id'|'email'|'name')
|
|
89
|
+
* @param {String} [p.key='cgrups'] 輸入指派欄名稱
|
|
90
|
+
* @param {Function} [p.isKeepable] 輸入判斷庫內值是否可保留之函數(收庫內值, 回布林), 預設恆可; 不可保留者(如無法原樣通過寫入前驗證)不保留並計入 nSkip
|
|
91
|
+
* @returns {Object} 回傳 { rows, nKeep, nSkip, nConflict }: rows 為新陣列(被保留值之列為複本, 其餘同一參考), nKeep 為保留筆數, nSkip 為因 isKeepable 為假而未保留之筆數,
|
|
92
|
+
* nConflict 為因身分矛盾而未保留之筆數;
|
|
93
|
+
* mappingBy 為 email/name 時, 以 id 對到之舊列其對應鍵值須與本批列相同、且庫內同一來源不得另有別列持有同值(舊登入擁有者矛盾), 否則不保留(同來源 id 重用/重編之矛盾)
|
|
94
|
+
*/
|
|
95
|
+
function keepAssignOnMissing({ rows, hasKey, rowsOldSameFrom, mappingBy = 'id', key = 'cgrups', isKeepable = () => true }) {
|
|
96
|
+
let oldById = new Map()
|
|
97
|
+
let oldByMap = new Map() //mappingBy 欄位值 → 同值之既有列陣列
|
|
98
|
+
rowsOldSameFrom.forEach((o) => {
|
|
99
|
+
if (!isObj(o)) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
if (isStr(o.id)) {
|
|
103
|
+
oldById.set(o.id, o)
|
|
104
|
+
}
|
|
105
|
+
if ((mappingBy === 'email' || mappingBy === 'name') && isStr(o[mappingBy]) && o[mappingBy] !== '') {
|
|
106
|
+
if (!oldByMap.has(o[mappingBy])) {
|
|
107
|
+
oldByMap.set(o[mappingBy], [])
|
|
108
|
+
}
|
|
109
|
+
oldByMap.get(o[mappingBy]).push(o)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
let byMap = mappingBy === 'email' || mappingBy === 'name'
|
|
113
|
+
let nKeep = 0
|
|
114
|
+
let nSkip = 0
|
|
115
|
+
let nConflict = 0
|
|
116
|
+
let out = rows.map((v, k) => {
|
|
117
|
+
if (hasKey[k] === true || !isObj(v)) {
|
|
118
|
+
return v
|
|
119
|
+
}
|
|
120
|
+
let id = toId(v)
|
|
121
|
+
let o = null
|
|
122
|
+
if (id !== null) {
|
|
123
|
+
o = oldById.has(id) ? oldById.get(id) : null
|
|
124
|
+
|
|
125
|
+
//對應鍵矛盾(外審 sol R3): mappingBy 為 email/name 時, 登入以對應鍵找使用者; 本批列以 id 對到之舊列, 其對應鍵值須與本批列相同, 否則為同來源 id 重用/重編(id 指向 A、對應鍵卻是 B),
|
|
126
|
+
//自動把 A 之後台指派移轉給 B 屬靜默擇一(R-PRINCIPLE-00 原則 3), 一律不保留(cgrups 維持空字串, 收嚴); 不保留者計入 nConflict 供呼叫端提示(原則 3: 收嚴須附提示, 增量外審 N2)
|
|
127
|
+
if (o !== null && byMap && (v[mappingBy] === undefined ? '' : v[mappingBy]) !== (o[mappingBy] === undefined ? '' : o[mappingBy])) {
|
|
128
|
+
o = null
|
|
129
|
+
nConflict++
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//舊登入擁有者矛盾(增量外審 N1): 對應鍵值非空時, 庫內同一來源須只有該列自己持有此值; 若另有別列(如舊 active 擁有者為另一個 id, 本列為停用列被本批改為啟用)持有同值,
|
|
133
|
+
//登入查找(以對應鍵+isActive)原指向別列, 保留本列之指派會使該登入值改取得本列之授權(移轉), 同樣不保留
|
|
134
|
+
if (o !== null && byMap && isStr(v[mappingBy]) && v[mappingBy] !== '') {
|
|
135
|
+
let cands = oldByMap.get(v[mappingBy])
|
|
136
|
+
if (cands !== undefined && cands.some((c) => c !== o)) {
|
|
137
|
+
o = null
|
|
138
|
+
nConflict++
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (byMap && isStr(v[mappingBy]) && v[mappingBy] !== '') {
|
|
143
|
+
let cands = oldByMap.get(v[mappingBy])
|
|
144
|
+
o = (cands !== undefined && cands.length === 1) ? cands[0] : null
|
|
145
|
+
|
|
146
|
+
//對應鍵命中多筆(如同值之啟用與停用列)無從判定歸屬, 不取首筆而不保留; 須附提示, 計入 nConflict(收嚴+提示, run08 外審 sol #3; 與 keepAssignByEntity 之無 id 同名多筆一致)
|
|
147
|
+
if (cands !== undefined && cands.length > 1) {
|
|
148
|
+
nConflict++
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (o === null || !hasOwn(o, key)) {
|
|
152
|
+
return v
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//保留值須能通過與外部寫入同一套寫入前驗證(外審 sol R4): 保留之歷史值若含會被 clearXSS 改寫之標記或非純量, 入庫時被改寫而使引用改指他名(增權), 一律不保留(視為空, 收嚴)
|
|
156
|
+
if (!isKeepable(o[key])) {
|
|
157
|
+
nSkip++
|
|
158
|
+
return v
|
|
159
|
+
}
|
|
160
|
+
nKeep++
|
|
161
|
+
return { ...v, [key]: o[key] }
|
|
162
|
+
})
|
|
163
|
+
return { rows: out, nKeep, nSkip, nConflict }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* grups 列缺 cpemis 鍵、pemis 列缺 crules 鍵者, 以庫內同一 from 之同一實體之值取代(run08; 與 keepAssignOnMissing 同一精神: 指派欄屬後台, 同步不得靜默清掉; 含該鍵則以外部為準)
|
|
169
|
+
*
|
|
170
|
+
* 實體身分(群組/權限無登入對應鍵, 不適用 mappingBy 之矛盾檢查): 有 id 者依 id 對到庫內同一 from 之列; 無 id 者(寫入時 ORM 補隨機主鍵, 身分不穩定)依名稱, 與引用連帶(refCascade.fillIdlessIds)同一口徑:
|
|
171
|
+
* 對庫內同一 from 中 id 未被本批帶用、名稱相同之列, 須恰 1 筆(同名多筆為歷史同名, 不取首筆); 對不到者視為新實體, 維持缺鍵之預設('').
|
|
172
|
+
* 收嚴(原則 3, 不保留並計入 nConflict, 供呼叫端記 warn): ①無 id 而同名命中多筆; ②同 id 但名稱不同(改名)且 cascadeRename 為假——引用不跟著改時名稱即授權身分,
|
|
173
|
+
* 把舊名之指派移轉給新名, 會使原引用新名之使用者(懸空)取得該授權; ③同 id 改名而新舊名稱有一方非字串(歷史資料)——級聯無從處理(缺欄視 '', 同 refCascade.entName), 保留同樣會增權(run08 外審 sol #1).
|
|
174
|
+
*
|
|
175
|
+
* @param {Object} p 輸入參數物件
|
|
176
|
+
* @param {Array} p.rows 輸入已 ltdtmapping 並帶 from 之本批列
|
|
177
|
+
* @param {Array} p.hasKey 輸入 markHasKey 之結果(與 rows 同序同長)
|
|
178
|
+
* @param {Array} p.rowsOldSameFrom 輸入庫內同一 from 之既有列
|
|
179
|
+
* @param {String} p.key 輸入指派欄名稱('cpemis'|'crules')
|
|
180
|
+
* @param {Boolean} [p.cascadeRename=true] 輸入改名是否連帶更新引用(opt.cascadeRename), 預設 true
|
|
181
|
+
* @param {Function} [p.isKeepable] 輸入判斷庫內值是否可保留之函數(收庫內值, 回布林), 預設恆可; 不可保留者不保留並計入 nSkip
|
|
182
|
+
* @returns {Object} 回傳 { rows, nKeep, nSkip, nConflict }: rows 為新陣列(被保留值之列為複本, 其餘同一參考), nKeep 為保留筆數, nSkip 為因 isKeepable 為假而未保留之筆數, nConflict 為因身分不確定而未保留之筆數
|
|
183
|
+
*/
|
|
184
|
+
function keepAssignByEntity({ rows, hasKey, rowsOldSameFrom, key, cascadeRename = true, isKeepable = () => true }) {
|
|
185
|
+
let nameOf = (r) => (r.name === undefined ? '' : r.name) //同 refCascade.entName: 缺欄視 ''
|
|
186
|
+
let idsBatch = new Set()
|
|
187
|
+
rows.forEach((v) => {
|
|
188
|
+
let id = toId(v)
|
|
189
|
+
if (id !== null) {
|
|
190
|
+
idsBatch.add(id)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
let oldById = new Map()
|
|
194
|
+
let oldByName = new Map() //名稱 → 本批未帶用其 id 之既有列陣列(無 id 者之對應池)
|
|
195
|
+
rowsOldSameFrom.forEach((o) => {
|
|
196
|
+
if (!isObj(o) || !isStr(o.id)) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
oldById.set(o.id, o)
|
|
200
|
+
if (isStr(o.name) && !idsBatch.has(o.id)) {
|
|
201
|
+
if (!oldByName.has(o.name)) {
|
|
202
|
+
oldByName.set(o.name, [])
|
|
203
|
+
}
|
|
204
|
+
oldByName.get(o.name).push(o)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
let nKeep = 0
|
|
208
|
+
let nSkip = 0
|
|
209
|
+
let nConflict = 0
|
|
210
|
+
let out = rows.map((v, k) => {
|
|
211
|
+
if (hasKey[k] === true || !isObj(v)) {
|
|
212
|
+
return v
|
|
213
|
+
}
|
|
214
|
+
let id = toId(v)
|
|
215
|
+
let o = null
|
|
216
|
+
if (id !== null) {
|
|
217
|
+
o = oldById.has(id) ? oldById.get(id) : null
|
|
218
|
+
|
|
219
|
+
//改名(同 id 異名)須「引用跟著改」才算同一實體: cascadeRename 為假時引用不跟著改; 為真時級聯只處理新舊名稱皆為字串之改名(refCascade.diffRefTargets: 缺欄視 '', 非字串不成實體),
|
|
220
|
+
//舊名稱為歷史非字串者無從級聯, 新名稱撞懸空名之檢查(renameCascadeNameInUse)亦不會觸發, 保留會把舊列之指派帶給原指向新名之懸空引用者, 同樣不保留(run08 外審 sol #1)
|
|
221
|
+
if (o !== null && nameOf(o) !== nameOf(v) && (cascadeRename === false || !isStr(nameOf(o)) || !isStr(nameOf(v)))) {
|
|
222
|
+
o = null
|
|
223
|
+
nConflict++
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (isStr(v.name) && oldByName.has(v.name)) {
|
|
227
|
+
let cands = oldByName.get(v.name)
|
|
228
|
+
if (cands.length === 1) {
|
|
229
|
+
o = cands[0]
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
nConflict++
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (o === null || !hasOwn(o, key)) {
|
|
236
|
+
return v
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
//保留值須能通過與外部寫入同一套寫入前驗證(同 keepAssignOnMissing): 不通過者不保留(視為空, 收嚴)
|
|
240
|
+
if (!isKeepable(o[key])) {
|
|
241
|
+
nSkip++
|
|
242
|
+
return v
|
|
243
|
+
}
|
|
244
|
+
nKeep++
|
|
245
|
+
return { ...v, [key]: o[key] }
|
|
246
|
+
})
|
|
247
|
+
return { rows: out, nKeep, nSkip, nConflict }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 同一 from 同一批內「同名異 id」之筆數(疑似外部 id 重編): 本批列帶 id 與其名稱, 而庫內同一 from 有另一列同名、不同 id、且該舊 id 不在本批
|
|
253
|
+
*
|
|
254
|
+
* @param {Array} rowsOldSameFrom 輸入庫內同一 from 之既有列(grups/pemis; users 之 name 可重複故不適用)
|
|
255
|
+
* @param {Array} rows 輸入本批列(帶 id 者才計)
|
|
256
|
+
* @returns {Number} 回傳筆數
|
|
257
|
+
*/
|
|
258
|
+
function countIdRenumber(rowsOldSameFrom, rows) {
|
|
259
|
+
let idsBatch = new Set()
|
|
260
|
+
rows.forEach((v) => {
|
|
261
|
+
let id = toId(v)
|
|
262
|
+
if (id !== null) {
|
|
263
|
+
idsBatch.add(id)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
let oldByName = new Map() //名稱 → 本批未沿用之舊 id 集合
|
|
267
|
+
rowsOldSameFrom.forEach((o) => {
|
|
268
|
+
if (isObj(o) && isStr(o.id) && isStr(o.name) && !idsBatch.has(o.id)) {
|
|
269
|
+
if (!oldByName.has(o.name)) {
|
|
270
|
+
oldByName.set(o.name, new Set())
|
|
271
|
+
}
|
|
272
|
+
oldByName.get(o.name).add(o.id)
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
let n = 0
|
|
276
|
+
rows.forEach((v) => {
|
|
277
|
+
let id = toId(v)
|
|
278
|
+
if (id !== null && isObj(v) && isStr(v.name) && oldByName.has(v.name)) {
|
|
279
|
+
n++
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
return n
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
export { markHasKey, keepAssignOnMissing, keepAssignByEntity, countIdRenumber }
|
|
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 Fri Oct 23 2026 10:56:39 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>
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>server/tools/authRefsAudit.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#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">server/tools/authRefsAudit.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>//懸空引用之離線稽核工具(適用表 n 上線後之升版前盤點; 規則帳本 R-REF-CASCADE-01 之配套; 不屬測試, 只讀匯出檔、不寫庫).
|
|
49
|
+
//
|
|
50
|
+
//用法(自專案根; 套件安裝後此檔位於 node_modules/w-web-perm/server/tools/, 以該路徑執行即可, 匯出檔路徑相對於目前工作目錄):
|
|
51
|
+
// node server/tools/authRefsAudit.mjs <匯出檔.json> [--json] [--clean-out <檔>]
|
|
52
|
+
//
|
|
53
|
+
// 匯出檔內容 { users, grups, pemis, targets }(各為列陣列, 如以資料通道 select 之結果或 g_backupDb.mjs 之備份整理而得)。
|
|
54
|
+
// --json 輸出結構化結果 { items, counts, changes, badJson, unnamed, unsafe, noId, widen }(供其他工具), 省略則輸出文字報告.
|
|
55
|
+
// --clean-out 把「清理後之引用端列」(僅變更者之主鍵 id 與新字串)寫成 json 供審閱: { users: [{ id, cgrups }], grups: [{ id, cpemis }], pemis: [{ id, crules }] }.
|
|
56
|
+
//
|
|
57
|
+
//報告內容:
|
|
58
|
+
// 1. 懸空引用清單(users.cgrups 引用不存在之群組名、grups.cpemis 引用不存在之權限名、pemis.crules 引用不存在之管控對象 id): 逐引用端列出被引用之名稱、isActive、mode 與對授權之影響.
|
|
59
|
+
// 2. 清理後授權可能變多之使用者/群組數(啟用之 AND 懸空項使結果為空, 清理後限制解除; 含「唯一 OR 項」與經群組傳導之情形).
|
|
60
|
+
// 3. 壞 JSON(解析失敗, 視為無引用, 不處理)清單、名稱非字串之引用(不處理, 須人工)清單.
|
|
61
|
+
// 4. 建議處置步驟.
|
|
62
|
+
// 不回顯 email/name 等個資(只列主鍵 id 與被引用之名稱).
|
|
63
|
+
//
|
|
64
|
+
//清理由 g_authRefsClean.mjs 執行(預設只列不改, 加 --apply 才寫入; 寫入前備份); 解析與改寫邏輯同 src/js/refAudit.mjs(改寫經 src/js/refCascade.mjs 之 rewriteRefs).
|
|
65
|
+
|
|
66
|
+
import fs from 'fs'
|
|
67
|
+
import path from 'path'
|
|
68
|
+
import isMain from './isMain.mjs'
|
|
69
|
+
import auditRefs, { EFFECT } from '../../src/js/refAudit.mjs'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
const TABLE_LABEL = { users: 'users.cgrups(引用群組名)', grups: 'grups.cpemis(引用權限名)', pemis: 'pemis.crules(引用管控對象 id)' }
|
|
73
|
+
const TARGET_LABEL = { users: '群組', grups: '權限', pemis: '管控對象' }
|
|
74
|
+
const BAD_TXT = { parse: 'JSON5 解析失敗', notContainer: '解析結果非物件/陣列', decode: '解碼失敗' }
|
|
75
|
+
const MAX_HOLDERS = 500 //文字報告每表最多列出之引用端列數(完整請用 --json)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
let arr = (v) => (Array.isArray(v) ? v : [])
|
|
79
|
+
let j = (v) => JSON.stringify(v)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 建立稽核結果(純函數, 不讀寫檔案): auditRefs 之結果加上各表筆數
|
|
84
|
+
*
|
|
85
|
+
* @param {Object} data { users, grups, pemis, targets }
|
|
86
|
+
* @returns {Object} 稽核結果
|
|
87
|
+
*/
|
|
88
|
+
function buildReport(data) {
|
|
89
|
+
let d = (data !== null && typeof data === 'object') ? data : {}
|
|
90
|
+
let rp = auditRefs(d)
|
|
91
|
+
return {
|
|
92
|
+
rows: { users: arr(d.users).length, grups: arr(d.grups).length, pemis: arr(d.pemis).length, targets: arr(d.targets).length },
|
|
93
|
+
...rp,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 清理後之引用端列(供 --clean-out): 僅變更者之主鍵 id 與新字串, 即 ORM save 之輸入形狀
|
|
100
|
+
*
|
|
101
|
+
* @param {Array} changes auditRefs 之 changes
|
|
102
|
+
* @returns {Object} { users: [{ id, cgrups }], grups: [{ id, cpemis }], pemis: [{ id, crules }] }
|
|
103
|
+
*/
|
|
104
|
+
function cleanOutOf(changes) {
|
|
105
|
+
let out = { users: [], grups: [], pemis: [] }
|
|
106
|
+
for (let c of arr(changes)) {
|
|
107
|
+
out[c.table].push({ id: c.id, [c.field]: c.after })
|
|
108
|
+
}
|
|
109
|
+
return out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
//formatReport: 結果轉文字
|
|
114
|
+
function formatReport(rp, opt = {}) {
|
|
115
|
+
let maxHolders = Number.isInteger(opt.maxHolders) ? opt.maxHolders : MAX_HOLDERS
|
|
116
|
+
let c = rp.counts
|
|
117
|
+
let L = []
|
|
118
|
+
L.push(`資料筆數: users ${rp.rows.users}、grups ${rp.rows.grups}、pemis ${rp.rows.pemis}、targets ${rp.rows.targets}`)
|
|
119
|
+
L.push('')
|
|
120
|
+
L.push(`== 懸空引用(引用之名稱查無實體): 共 ${c.nItems} 項, 涉及引用端 ${c.byTable.users.nHolders + c.byTable.grups.nHolders + c.byTable.pemis.nHolders} 列; 清理後會改寫 ${c.nChanges} 列 ==`)
|
|
121
|
+
for (let t of ['users', 'grups', 'pemis']) {
|
|
122
|
+
let its = rp.items.filter((x) => x.table === t)
|
|
123
|
+
let bt = c.byTable[t]
|
|
124
|
+
L.push(`-- ${TABLE_LABEL[t]}: ${bt.nItems} 項, ${bt.nHolders} 列`)
|
|
125
|
+
let byHolder = new Map()
|
|
126
|
+
for (let x of its) {
|
|
127
|
+
if (!byHolder.has(x.holderId)) {
|
|
128
|
+
byHolder.set(x.holderId, [])
|
|
129
|
+
}
|
|
130
|
+
byHolder.get(x.holderId).push(x)
|
|
131
|
+
}
|
|
132
|
+
let n = 0
|
|
133
|
+
for (let [id, xs] of byHolder) {
|
|
134
|
+
if (n++ >= maxHolders) {
|
|
135
|
+
L.push(` …(其餘 ${byHolder.size - maxHolders} 列從略, 完整清單請加 --json)`)
|
|
136
|
+
break
|
|
137
|
+
}
|
|
138
|
+
let txt = xs.map((x) => `${j(x.name)}${t === 'pemis' ? '' : `(${x.isActive === 'y' ? '啟用' : '停用'}, mode=${j(x.mode)})`}`).join('、')
|
|
139
|
+
L.push(` ${t} id=${id}: ${txt}`)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
L.push('')
|
|
143
|
+
L.push(`== 對授權之影響(ref 類之 cgrups/cpemis; crules 懸空項不影響授權) ==`)
|
|
144
|
+
L.push(`- 啟用之懸空項 ${c.nActive} 項(其中有效模式為 AND 者 ${c.nAndActive} 項、OR 者 ${c.nOrActive} 項), 停用之懸空項 ${c.nInactive} 項`)
|
|
145
|
+
L.push(`- 擁有「啟用 AND 懸空項」之使用者 ${c.byTable.users.nAndActiveHolders} 位、群組 ${c.byTable.grups.nAndActiveHolders} 個: 現況其授權(該層合併結果)被收嚴為空; 清理後 AND 限制解除, 授權可能變多`)
|
|
146
|
+
L.push(`- 清理後授權可能變多(含唯一 OR 懸空項而同層有合法 AND 項、經群組傳導者): 使用者 ${c.nWidenUsers} 位、群組 ${c.nWidenGrups} 個`)
|
|
147
|
+
for (let w of rp.widen.slice(0, maxHolders)) {
|
|
148
|
+
L.push(` ${w.table} id=${w.holderId}${w.via === 'grups' ? '(經其啟用引用之群組傳導)' : ''}`)
|
|
149
|
+
}
|
|
150
|
+
if (rp.widen.length > maxHolders) {
|
|
151
|
+
L.push(` …(其餘 ${rp.widen.length - maxHolders} 筆從略, 完整清單請加 --json)`)
|
|
152
|
+
}
|
|
153
|
+
L.push('- 影響說明:')
|
|
154
|
+
L.push(` AND: ${EFFECT.and}`)
|
|
155
|
+
L.push(` OR: ${EFFECT.or}`)
|
|
156
|
+
L.push(` 停用: ${EFFECT.inactive}`)
|
|
157
|
+
L.push(` crules: ${EFFECT.rule}`)
|
|
158
|
+
L.push('')
|
|
159
|
+
L.push(`== 授權字串壞 JSON(解析失敗等, 視為無引用, 清理時不處理): ${rp.badJson.length} 列 ==`)
|
|
160
|
+
rp.badJson.forEach((b) => L.push(`- ${b.table} id=${b.id} 欄位 ${b.field}: ${BAD_TXT[b.reason] || b.reason}`))
|
|
161
|
+
L.push('')
|
|
162
|
+
L.push(`== 名稱非字串之引用(陣列元素無 name 等, 不對應任何${TARGET_LABEL.users}/${TARGET_LABEL.grups}名, 清理時不處理, 須人工於後台修正): ${rp.unnamed.length} 項 ==`)
|
|
163
|
+
rp.unnamed.slice(0, maxHolders).forEach((x) => L.push(`- ${x.table} id=${x.holderId} 欄位 ${x.field} isActive=${j(x.isActive)} mode=${j(x.mode)}`))
|
|
164
|
+
if (rp.unsafe && rp.unsafe.length > 0) {
|
|
165
|
+
L.push(`== 含無法安全重新編碼之內容(JSON5 Infinity/NaN 數值)而不清理之授權欄位, 須人工處理: ${rp.unsafe.length} 項 ==`)
|
|
166
|
+
rp.unsafe.slice(0, maxHolders).forEach((x) => L.push(`- ${x.table} id=${x.id} 欄位 ${x.field}`))
|
|
167
|
+
}
|
|
168
|
+
if (rp.noId.length > 0) {
|
|
169
|
+
L.push('')
|
|
170
|
+
L.push(`== 缺字串主鍵而無從寫回之引用端列: ${rp.noId.length} 列(不清理) ==`)
|
|
171
|
+
rp.noId.forEach((x) => L.push(`- ${x.table} id=${j(x.holderId)} 欄位 ${x.field}: ${x.nItems} 項懸空`))
|
|
172
|
+
}
|
|
173
|
+
L.push('')
|
|
174
|
+
L.push('== 建議處置步驟 ==')
|
|
175
|
+
L.push('1. 先確認本報告「對授權之影響」: 啟用 AND 懸空項若本意為限制(例如曾刪除群組/權限而期望該使用者從此無授權), 清理會使其授權變多; 此類請先於後台改正名稱(或停用該引用)再清理.')
|
|
176
|
+
L.push('2. 預設只列不改: node g_authRefsClean.mjs(連專案資料庫, 與本報告同一判準); 確認清單後再加 --apply 寫入(寫入前自動備份資料庫與將改寫之三張引用端表, 任一備份失敗即中止).')
|
|
177
|
+
L.push('3. 建議於停服(或無人編輯)時執行 --apply, 執行後重啟服務(避免伺服端快取沿用舊字串), 並以 getPerm/getPermUserInfor 抽查數位使用者之授權.')
|
|
178
|
+
L.push('4. 寫入後工具會重新稽核, 應為 0 懸空; 壞 JSON 與名稱非字串者不受清理影響, 須另行處理.')
|
|
179
|
+
return L.join('\n')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
//CLI
|
|
184
|
+
if (isMain(import.meta.url)) {
|
|
185
|
+
let args = process.argv.slice(2)
|
|
186
|
+
let iOut = args.indexOf('--clean-out')
|
|
187
|
+
let cleanOut = (iOut >= 0) ? args[iOut + 1] : undefined
|
|
188
|
+
let fp = args.find((a, k) => !a.startsWith('--') && !(iOut >= 0 && k === iOut + 1))
|
|
189
|
+
if (!fp || (iOut >= 0 && (cleanOut === undefined || cleanOut.startsWith('--')))) {
|
|
190
|
+
console.log('用法: node server/tools/authRefsAudit.mjs <匯出檔.json> [--json] [--clean-out <檔>]')
|
|
191
|
+
process.exit(2)
|
|
192
|
+
}
|
|
193
|
+
//輸出不得覆寫輸入(同一檔; 以解析後之絕對路徑與已存在檔之 inode 判定): --clean-out 只含變更列之 id 與引用欄位, 覆寫匯出檔會使其遺失其餘列與欄位
|
|
194
|
+
if (cleanOut !== undefined) {
|
|
195
|
+
let same = path.resolve(fp).toLowerCase() === path.resolve(cleanOut).toLowerCase()
|
|
196
|
+
if (!same && fs.existsSync(cleanOut) && fs.existsSync(fp)) {
|
|
197
|
+
let a = fs.statSync(fp)
|
|
198
|
+
let b = fs.statSync(cleanOut)
|
|
199
|
+
same = a.ino !== 0 && a.ino === b.ino && a.dev === b.dev
|
|
200
|
+
}
|
|
201
|
+
if (same) {
|
|
202
|
+
console.error('失敗: --clean-out 不得與輸入匯出檔為同一檔(會覆寫原匯出檔)')
|
|
203
|
+
process.exit(2)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
let data = JSON.parse(fs.readFileSync(fp, 'utf8'))
|
|
208
|
+
let rp = buildReport(data)
|
|
209
|
+
console.log(args.includes('--json') ? JSON.stringify(rp, null, 1) : formatReport(rp))
|
|
210
|
+
if (cleanOut !== undefined) {
|
|
211
|
+
fs.writeFileSync(cleanOut, JSON.stringify(cleanOutOf(rp.changes), null, 1), 'utf8')
|
|
212
|
+
console.error(`已寫入清理後之引用端列(僅變更者): ${cleanOut}`) //stderr: 不污染 --json 之 stdout
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
console.error(`失敗: ${err && err.message ? err.message : err}`)
|
|
217
|
+
process.exitCode = 1
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
export { buildReport, formatReport, cleanOutOf }
|
|
223
|
+
</code></pre>
|
|
224
|
+
</article>
|
|
225
|
+
</section>
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<br class="clear">
|
|
235
|
+
|
|
236
|
+
<footer>
|
|
237
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
238
|
+
</footer>
|
|
239
|
+
|
|
240
|
+
<script>prettyPrint();</script>
|
|
241
|
+
<script src="scripts/polyfill.js"></script>
|
|
242
|
+
<script src="scripts/linenumber.js"></script>
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
</body>
|
|
247
|
+
</html>
|