w-web-api 1.0.75 → 1.0.76
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 +99 -99
- package/dist/css/{app.e140ee15.css → app.ae472c4d.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.2aa145e5.js +1 -0
- package/dist/js/app.2aa145e5.js +1 -0
- package/dist/js/{chunk-vendors.cc9f5247.js → chunk-vendors-legacy.47cf6375.js} +1 -1
- package/dist/js/{chunk-vendors-legacy.cc9f5247.js → chunk-vendors.47cf6375.js} +1 -1
- package/dist/w-web-api.umd.js +1 -1
- package/docs/WWebApi.html +76 -2
- package/docs/index.html +1 -1
- package/docs/server_WWebApi.mjs.html +931 -931
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/{g.backupDb.mjs → g_backupDb.mjs} +10 -10
- package/{g.initialTestData.mjs → g_initialTestData.mjs} +21 -21
- package/{g.mOrm.mjs → g_mOrm.mjs} +24 -24
- package/{g.provideApis.mjs → g_provideApis.mjs} +53 -53
- package/logs/2026-08-25T20.log +0 -0
- package/logs/2026-08-25T21.log +1891 -0
- package/package.json +12 -12
- package/server/WWebApi.mjs +930 -930
- package/settings.json +1 -1
- package/src/components/LayoutContent.vue +637 -637
- package/src/components/LayoutContentStats.vue +99 -27
- package/srv.mjs +108 -108
- package/tableTags.json +1 -1
- package/dist/js/app-legacy.955c81f0.js +0 -1
- package/dist/js/app.955c81f0.js +0 -1
- /package/{g.getSettings.mjs → g_getSettings.mjs} +0 -0
|
@@ -45,936 +45,936 @@
|
|
|
45
45
|
|
|
46
46
|
<section>
|
|
47
47
|
<article>
|
|
48
|
-
<pre class="prettyprint source linenums"><code>import path from 'path'
|
|
49
|
-
import fs from 'fs'
|
|
50
|
-
import get from 'lodash-es/get.js'
|
|
51
|
-
import each from 'lodash-es/each.js'
|
|
52
|
-
import map from 'lodash-es/map.js'
|
|
53
|
-
import keys from 'lodash-es/keys.js'
|
|
54
|
-
import j2o from 'wsemi/src/j2o.mjs'
|
|
55
|
-
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
56
|
-
import isestr from 'wsemi/src/isestr.mjs'
|
|
57
|
-
import isearr from 'wsemi/src/isearr.mjs'
|
|
58
|
-
import ispint from 'wsemi/src/ispint.mjs'
|
|
59
|
-
import isErr from 'wsemi/src/isErr.mjs'
|
|
60
|
-
import isfun from 'wsemi/src/isfun.mjs'
|
|
61
|
-
import ispm from 'wsemi/src/ispm.mjs'
|
|
62
|
-
import cint from 'wsemi/src/cint.mjs'
|
|
63
|
-
import strleft from 'wsemi/src/strleft.mjs'
|
|
64
|
-
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
65
|
-
import strright from 'wsemi/src/strright.mjs'
|
|
66
|
-
import strdelright from 'wsemi/src/strdelright.mjs'
|
|
67
|
-
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
|
|
68
|
-
import b642str from 'wsemi/src/b642str.mjs'
|
|
69
|
-
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
70
|
-
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
71
|
-
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
|
|
72
|
-
import replace from 'wsemi/src/replace.mjs'
|
|
73
|
-
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
74
|
-
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
75
|
-
import procLang from './procLang.mjs'
|
|
76
|
-
import procProxy from './procProxy.mjs'
|
|
77
|
-
import procApis from './procApis.mjs'
|
|
78
|
-
import staEvent from './staLogs/staEvent.mjs'
|
|
79
|
-
import srLogInit from './srLog.mjs'
|
|
80
|
-
import ds from '../src/schema/index.mjs'
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* API伺服器
|
|
85
|
-
*
|
|
86
|
-
* @class
|
|
87
|
-
* @param {Function} WOrm 輸入資料庫ORM函數
|
|
88
|
-
* @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
|
|
89
|
-
* @param {String} db 輸入資料庫名稱字串
|
|
90
|
-
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
|
|
91
|
-
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
|
|
92
|
-
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
|
|
93
|
-
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
94
|
-
* @param {Integer} [opt.serverPort=11005] 輸入伺服器通訊port,預設11005
|
|
95
|
-
* @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
|
|
96
|
-
* @param {Function} [opt.getUserById=null] 輸入當useCheckUser=true時依照使用者ID取得使用者資訊物件函數,預設null
|
|
97
|
-
* @param {Boolean} [opt.useExcludeWhenNotAdmin=false] 輸入使用ORM的select方法時是否自動刪除數據內isActive欄位之布林值,預設false
|
|
98
|
-
* @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
99
|
-
* @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
100
|
-
* @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
|
|
101
|
-
* @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
|
|
102
|
-
* @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
|
|
103
|
-
* @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
|
|
104
|
-
* @example
|
|
105
|
-
*
|
|
106
|
-
* import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
|
|
107
|
-
* import WWebApi from './server/WWebApi.mjs'
|
|
108
|
-
* import getSettings from './
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* //st
|
|
112
|
-
* let st = getSettings()
|
|
113
|
-
*
|
|
114
|
-
* let url = st.dbUrl
|
|
115
|
-
* let db = st.dbName
|
|
116
|
-
* let opt = {
|
|
117
|
-
*
|
|
118
|
-
* useCheckUser: false,
|
|
119
|
-
* getUserById: null,
|
|
120
|
-
* useExcludeWhenNotAdmin: false,
|
|
121
|
-
*
|
|
122
|
-
* serverPort: 11005,
|
|
123
|
-
* subfolder: '', //mapi
|
|
124
|
-
* urlRedirect: 'https://www.google.com/', //本機測試時得先編譯, 再瀏覽: http://localhost:11005/
|
|
125
|
-
*
|
|
126
|
-
* webName: {
|
|
127
|
-
* 'eng': 'API Service',
|
|
128
|
-
* 'cht': 'API管理系統',
|
|
129
|
-
* },
|
|
130
|
-
* webDescription: {
|
|
131
|
-
* 'eng': 'A web service package as methods to send requests to and receive responses from an API.',
|
|
132
|
-
* 'cht': 'A web service package as methods to send requests to and receive responses from an API.',
|
|
133
|
-
* },
|
|
134
|
-
* webLogo: 'data:image/svg+xml;base64,...',
|
|
135
|
-
*
|
|
136
|
-
* }
|
|
137
|
-
*
|
|
138
|
-
* let getUserByToken = async (token) => {
|
|
139
|
-
* // return {} //測試無法登入
|
|
140
|
-
* if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
|
|
141
|
-
* return {
|
|
142
|
-
* id: 'id-for-application',
|
|
143
|
-
* name: 'application',
|
|
144
|
-
* email: 'application@example.com',
|
|
145
|
-
* isAdmin: 'y',
|
|
146
|
-
* }
|
|
147
|
-
* }
|
|
148
|
-
* if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
|
|
149
|
-
* return {
|
|
150
|
-
* id: 'id-for-admin',
|
|
151
|
-
* name: '測試者',
|
|
152
|
-
* email: 'admin@example.com',
|
|
153
|
-
* isAdmin: 'y',
|
|
154
|
-
* }
|
|
155
|
-
* }
|
|
156
|
-
* console.log('invalid token', token)
|
|
157
|
-
* console.log('於生產環境時得加入SSO等驗證token機制')
|
|
158
|
-
* return {}
|
|
159
|
-
* }
|
|
160
|
-
*
|
|
161
|
-
* let verifyClientUser = (user, from) => {
|
|
162
|
-
* console.log('verifyClientUser/user', user)
|
|
163
|
-
* // return false //測試無法登入
|
|
164
|
-
* console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
|
|
165
|
-
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
166
|
-
* }
|
|
167
|
-
*
|
|
168
|
-
* let verifyAppUser = (user, from) => {
|
|
169
|
-
* console.log('verifyAppUser/user', user)
|
|
170
|
-
* // return false //測試無法登入
|
|
171
|
-
* console.log('於生產環境時得加入限制應用程式使用者身份機制')
|
|
172
|
-
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
173
|
-
* }
|
|
174
|
-
*
|
|
175
|
-
* //WWebApi
|
|
176
|
-
* let instWWebApi = WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt)
|
|
177
|
-
*
|
|
178
|
-
* instWWebApi.on('error', (err) => {
|
|
179
|
-
* console.log(err)
|
|
180
|
-
* })
|
|
181
|
-
*
|
|
182
|
-
*/
|
|
183
|
-
function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt = {}) {
|
|
184
|
-
let instWServHapiServer = null
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//check WOrm
|
|
188
|
-
if (!isfun(WOrm)) {
|
|
189
|
-
console.log('invalid WOrm', WOrm)
|
|
190
|
-
throw new Error('invalid WOrm')
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//check url
|
|
195
|
-
if (!isestr(url)) {
|
|
196
|
-
console.log('invalid url', url)
|
|
197
|
-
throw new Error('invalid url')
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//check db
|
|
202
|
-
if (!isestr(db)) {
|
|
203
|
-
console.log('invalid db', db)
|
|
204
|
-
throw new Error('invalid db')
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
//check getUserByToken
|
|
209
|
-
if (!isfun(getUserByToken)) {
|
|
210
|
-
console.log('invalid getUserByToken', getUserByToken)
|
|
211
|
-
throw new Error('invalid getUserByToken')
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//check verifyClientUser
|
|
216
|
-
if (!isfun(verifyClientUser)) {
|
|
217
|
-
console.log('invalid verifyClientUser', verifyClientUser)
|
|
218
|
-
throw new Error('invalid verifyClientUser')
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//check verifyAppUser
|
|
223
|
-
if (!isfun(verifyAppUser)) {
|
|
224
|
-
console.log('invalid verifyAppUser', verifyAppUser)
|
|
225
|
-
throw new Error('invalid verifyAppUser')
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//serverPort
|
|
230
|
-
let serverPort = get(opt, 'serverPort')
|
|
231
|
-
if (!ispint(serverPort)) {
|
|
232
|
-
serverPort = 11005
|
|
233
|
-
}
|
|
234
|
-
serverPort = cint(serverPort)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//useCheckUser
|
|
238
|
-
let useCheckUser = get(opt, 'useCheckUser', false)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//getUserById
|
|
242
|
-
let getUserById = get(opt, 'getUserById', null)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
//useExcludeWhenNotAdmin
|
|
246
|
-
let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin', false)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
//webName
|
|
250
|
-
let webName = get(opt, 'webName', {})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
//webDescription
|
|
254
|
-
let webDescription = get(opt, 'webDescription', {})
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
//webLogo
|
|
258
|
-
let webLogo = get(opt, 'webLogo', '')
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
//subfolder
|
|
262
|
-
let subfolder = get(opt, 'subfolder', '')
|
|
263
|
-
if (isestr(subfolder)) {
|
|
264
|
-
if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
|
|
265
|
-
subfolder = strdelright(subfolder, 1)
|
|
266
|
-
}
|
|
267
|
-
if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
|
|
268
|
-
subfolder = `/${subfolder}`
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
//urlRedirect
|
|
274
|
-
let urlRedirect = get(opt, 'urlRedirect', '')
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
//params
|
|
278
|
-
let showLanguage = get(opt, 'showLanguage', 'y')
|
|
279
|
-
let language = get(opt, 'language', 'eng')
|
|
280
|
-
let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
//kpLangExt
|
|
284
|
-
let kpLangExt = get(opt, 'kpLangExt', {})
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
//kpLang
|
|
288
|
-
let kpLang = procLang({ kpLangExt, webName, webDescription })
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
//WServOrm
|
|
292
|
-
let optWServOrm = {
|
|
293
|
-
useCheckUser,
|
|
294
|
-
getUserById,
|
|
295
|
-
useExcludeWhenNotAdmin,
|
|
296
|
-
}
|
|
297
|
-
let wp = {}
|
|
298
|
-
try {
|
|
299
|
-
wp = WServOrm(ds, WOrm, url, db, optWServOrm)
|
|
300
|
-
}
|
|
301
|
-
catch (err) {
|
|
302
|
-
//ORM 為必要依賴:初始化失敗即 fail-fast,不以 wp={} 帶 undefined woItems/procOrm 殘缺啟動(與建構子其餘必要依賴一致)
|
|
303
|
-
console.log('WServOrm init failed', err)
|
|
304
|
-
throw err
|
|
305
|
-
}
|
|
306
|
-
let { woItems, procOrm } = wp
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
//srLog(後端 log;錯誤一律 log err-key 供識別,對齊 w-web-sso)
|
|
310
|
-
let srLog = srLogInit(opt)
|
|
311
|
-
|
|
312
|
-
//maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
|
|
313
|
-
let maskTok = (t) => isestr(t) ? (strleft(t, 4) + '***') : t
|
|
314
|
-
|
|
315
|
-
//kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
|
|
316
|
-
//原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
|
|
317
|
-
let wrapErrLog = (event, fn) => {
|
|
318
|
-
let ev = 'kpfun-' + event
|
|
319
|
-
return async (...args) => {
|
|
320
|
-
try {
|
|
321
|
-
let result = await fn(...args)
|
|
322
|
-
srLog.info({ event: ev }) //成功也記 info
|
|
323
|
-
return result
|
|
324
|
-
}
|
|
325
|
-
catch (err) {
|
|
326
|
-
srLog.error({ event: ev, err })
|
|
327
|
-
return Promise.reject(err)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
//procProxy
|
|
334
|
-
let { proxyRequest } = procProxy({ srLog })
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
//procApis
|
|
338
|
-
let { getApisList, saveApi, deleteApi } = procApis({ woItems, procOrm, ds })
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
//getWebInfor
|
|
342
|
-
let getWebInfor = (userId) => {
|
|
343
|
-
return {
|
|
344
|
-
|
|
345
|
-
// webName, //已併入kpLang
|
|
346
|
-
// webDescription, //已併入kpLang
|
|
347
|
-
webLogo,
|
|
348
|
-
|
|
349
|
-
showLanguage,
|
|
350
|
-
language,
|
|
351
|
-
kpLang,
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
//checkUser
|
|
358
|
-
let checkUser = async(user) => {
|
|
359
|
-
let id = get(user, 'id', '')
|
|
360
|
-
if (!isestr(id)) {
|
|
361
|
-
console.log('user', user)
|
|
362
|
-
console.log('can not get the userId')
|
|
363
|
-
return Promise.reject('errUserIdMissing')
|
|
364
|
-
}
|
|
365
|
-
let email = get(user, 'email', '')
|
|
366
|
-
if (!isestr(email)) {
|
|
367
|
-
console.log('user', user)
|
|
368
|
-
console.log('can not get the email of user')
|
|
369
|
-
return Promise.reject('errUserEmailMissing')
|
|
370
|
-
}
|
|
371
|
-
let name = get(user, 'name', '')
|
|
372
|
-
if (!isestr(name)) {
|
|
373
|
-
console.log('user', user)
|
|
374
|
-
console.log('can not get userName')
|
|
375
|
-
return Promise.reject('errUserNameMissing')
|
|
376
|
-
}
|
|
377
|
-
let isAdmin = get(user, 'isAdmin', '')
|
|
378
|
-
if (isAdmin !== 'y' && isAdmin !== 'n') {
|
|
379
|
-
console.log('user', user)
|
|
380
|
-
console.log('user.isAdmin is not y or n', user.isAdmin)
|
|
381
|
-
console.log('can not get the role of user')
|
|
382
|
-
return Promise.reject('errUserRoleMissing')
|
|
383
|
-
}
|
|
384
|
-
return true
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
//getTokenUser
|
|
389
|
-
let getTokenUser = async (token) => {
|
|
390
|
-
|
|
391
|
-
//getUserByToken
|
|
392
|
-
let userSelf = getUserByToken(token)
|
|
393
|
-
if (ispm(userSelf)) {
|
|
394
|
-
userSelf = await userSelf
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
//check
|
|
398
|
-
await checkUser(userSelf)
|
|
399
|
-
|
|
400
|
-
return userSelf
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
//p
|
|
405
|
-
let p = {
|
|
406
|
-
checkToken: async(token) => {
|
|
407
|
-
|
|
408
|
-
//getUserByToken
|
|
409
|
-
let u = await getUserByToken(token)
|
|
410
|
-
// console.log('u', u)
|
|
411
|
-
|
|
412
|
-
//checkUser
|
|
413
|
-
await checkUser(u)
|
|
414
|
-
|
|
415
|
-
return true
|
|
416
|
-
},
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
//getAndVerifyClientUser
|
|
421
|
-
let getAndVerifyClientUser = async (token, from = '') => {
|
|
422
|
-
//基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
|
|
423
|
-
|
|
424
|
-
//getTokenUser
|
|
425
|
-
let user = await getTokenUser(token)
|
|
426
|
-
|
|
427
|
-
//verifyClientUser
|
|
428
|
-
let b = verifyClientUser(user, from)
|
|
429
|
-
if (ispm(b)) {
|
|
430
|
-
b = await b
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
//check
|
|
434
|
-
if (!b) {
|
|
435
|
-
console.log('user', user)
|
|
436
|
-
console.log(`user does not have permission`)
|
|
437
|
-
return Promise.reject('errUserNoPermission')
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
srLog.info({ event: 'verifyClientUser', from, userId: get(user, 'id', '') }) //瀏覽使用者驗證通過
|
|
441
|
-
return user
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
//getAndVerifyAppUser
|
|
446
|
-
let getAndVerifyAppUser = async (token, from = '') => {
|
|
447
|
-
//基於token查找app使用者, 會基於外部getUserByToken進行查找, 故只要getUserByToken提供使用者即可, 可支援getUserByToken給予針對應用程式所產生之虛擬使用者
|
|
448
|
-
|
|
449
|
-
//user
|
|
450
|
-
let user = null
|
|
451
|
-
if (isestr(token)) {
|
|
452
|
-
user = getUserByToken(token)
|
|
453
|
-
if (ispm(user)) {
|
|
454
|
-
user = await user
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
// console.log('user', user)
|
|
458
|
-
|
|
459
|
-
//check
|
|
460
|
-
if (!iseobj(user)) {
|
|
461
|
-
console.log(`token`, token)
|
|
462
|
-
console.log(`can not find the user from token`)
|
|
463
|
-
return Promise.reject('errUserNotFound')
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
//verifyAppUser
|
|
467
|
-
let b = verifyAppUser(user, from)
|
|
468
|
-
if (ispm(b)) {
|
|
469
|
-
b = await b
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
//check
|
|
473
|
-
if (!b) {
|
|
474
|
-
console.log('user', user)
|
|
475
|
-
console.log(`user does not have permission`)
|
|
476
|
-
return Promise.reject('errUserNoPermission')
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
srLog.info({ event: 'verifyAppUser', from, userId: get(user, 'id', '') }) //應用程式使用者驗證通過
|
|
480
|
-
return user
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
// //getAPIsList
|
|
485
|
-
// let getAPIsList = async (query = {}) => {
|
|
486
|
-
// let rs = await woItems.apis.select(query)
|
|
487
|
-
// return rs
|
|
488
|
-
// }
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
//parsePayload
|
|
492
|
-
let parsePayload = async (req) => {
|
|
493
|
-
|
|
494
|
-
//payload
|
|
495
|
-
let payload = get(req, 'payload')
|
|
496
|
-
|
|
497
|
-
//to obj
|
|
498
|
-
if (isestr(payload)) {
|
|
499
|
-
payload = j2o(payload)
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
//check
|
|
503
|
-
if (!iseobj(payload)) {
|
|
504
|
-
console.log('payload', payload)
|
|
505
|
-
console.log(`invalid payload in req`)
|
|
506
|
-
return Promise.reject('errPayloadInvalid')
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
//group
|
|
510
|
-
let group = get(payload, 'group', '')
|
|
511
|
-
|
|
512
|
-
//check
|
|
513
|
-
if (!isestr(group)) {
|
|
514
|
-
console.log('payload', payload)
|
|
515
|
-
console.log(`invalid group in payload`)
|
|
516
|
-
return Promise.reject('errGroupInvalid')
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
//rows
|
|
520
|
-
let rows = get(payload, 'rows', [])
|
|
521
|
-
|
|
522
|
-
//check
|
|
523
|
-
if (!isearr(rows)) {
|
|
524
|
-
console.log('payload', payload)
|
|
525
|
-
console.log(`invalid rows in payload`)
|
|
526
|
-
return Promise.reject('errRowsInvalid')
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
//r
|
|
530
|
-
let r = {
|
|
531
|
-
group,
|
|
532
|
-
rows,
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return r
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
// //updateAPIs
|
|
540
|
-
// let updateAPIs = async (inp = {}) => {
|
|
541
|
-
|
|
542
|
-
// //spread inp
|
|
543
|
-
// let { apis: rsApis } = inp
|
|
544
|
-
// // console.log('group', group)
|
|
545
|
-
// // console.log('rsApis', rsApis)
|
|
546
|
-
|
|
547
|
-
// //save
|
|
548
|
-
// let r = await woItems.apis.save(rsApis)
|
|
549
|
-
|
|
550
|
-
// return r
|
|
551
|
-
// }
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
//syncAndReplaceTabs
|
|
555
|
-
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
556
|
-
|
|
557
|
-
//group
|
|
558
|
-
let group = get(inp, 'group', '')
|
|
559
|
-
// console.log('group', group)
|
|
560
|
-
|
|
561
|
-
//check
|
|
562
|
-
if (!isestr(group)) {
|
|
563
|
-
console.log('inp', inp)
|
|
564
|
-
console.log('group', group)
|
|
565
|
-
console.log(`invalid group`)
|
|
566
|
-
return Promise.reject('errGroupInvalid')
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
//rows
|
|
570
|
-
let rows = get(inp, 'rows', [])
|
|
571
|
-
// console.log('rows', rows)
|
|
572
|
-
|
|
573
|
-
//check
|
|
574
|
-
if (!isearr(rows)) {
|
|
575
|
-
console.log('inp', inp)
|
|
576
|
-
console.log('keyTable', keyTable)
|
|
577
|
-
return Promise.reject('errRowsInvalid')
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
//save from
|
|
581
|
-
rows = map(rows, (v, k) => {
|
|
582
|
-
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
583
|
-
v.group = group
|
|
584
|
-
return v
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
//偵測b64自動恢復
|
|
588
|
-
if (keyTable === 'apis') {
|
|
589
|
-
each(rows, (row, krow) => {
|
|
590
|
-
// console.log(kapi, row.name)
|
|
591
|
-
each(row, (v, k) => {
|
|
592
|
-
// console.log(k, v)
|
|
593
|
-
if (isestr(v)) {
|
|
594
|
-
if (strleft(v, 7) === 'base64:') {
|
|
595
|
-
// console.log(k, 'get base64')
|
|
596
|
-
v = strdelleft(v, 7)
|
|
597
|
-
v = b642str(v)
|
|
598
|
-
// console.log(v)
|
|
599
|
-
rows[krow][k] = v
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
})
|
|
603
|
-
})
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
//ltdtmapping
|
|
607
|
-
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
608
|
-
// console.log('ltdtmapping rows', rows)
|
|
609
|
-
|
|
610
|
-
//delAll group
|
|
611
|
-
await woItems[keyTable].delAll({ group })
|
|
612
|
-
|
|
613
|
-
//insert
|
|
614
|
-
// let r = await woItems[keyTable].insert(rows)
|
|
615
|
-
let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
|
|
616
|
-
// console.log('r', r)
|
|
617
|
-
|
|
618
|
-
return r
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
//pathStaticFiles
|
|
623
|
-
let pathStaticFiles = 'dist'
|
|
624
|
-
let npmPathStaticFiles = './node_modules/w-web-api/dist'
|
|
625
|
-
if (fsIsFolder(npmPathStaticFiles)) {
|
|
626
|
-
pathStaticFiles = npmPathStaticFiles
|
|
627
|
-
}
|
|
628
|
-
// console.log('pathStaticFiles', pathStaticFiles)
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
//subfolder
|
|
632
|
-
let fnEntryIn = 'index.tmp'
|
|
633
|
-
let fnEntryOut = 'index.html'
|
|
634
|
-
try {
|
|
635
|
-
let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
|
|
636
|
-
if (!fsIsFile(fpEntryIn)) {
|
|
637
|
-
fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
|
|
638
|
-
}
|
|
639
|
-
if (!fsIsFile(fpEntryIn)) {
|
|
640
|
-
console.log('fpEntryIn', fpEntryIn)
|
|
641
|
-
throw new Error(`invalid fpEntryIn`)
|
|
642
|
-
}
|
|
643
|
-
let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
|
|
644
|
-
let c = fs.readFileSync(fpEntryIn, 'utf8')
|
|
645
|
-
c = replace(c, '/mapi/', '{sfd}/') //方法同genEntry
|
|
646
|
-
c = replace(c, '{sfd}', subfolder)
|
|
647
|
-
c = replace(c, '{urlRedirect}', urlRedirect)
|
|
648
|
-
c = replace(c, '{language}', language) //注入初始語系 → window.___pmwperm___.language(前端 getLang 之來源之一)
|
|
649
|
-
fs.writeFileSync(fpEntryOut, c, 'utf8')
|
|
650
|
-
}
|
|
651
|
-
catch (err) {
|
|
652
|
-
console.log(err)
|
|
653
|
-
console.log(`can not generate ${fnEntryOut}`)
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
//apis
|
|
658
|
-
let apis = [
|
|
659
|
-
|
|
660
|
-
{
|
|
661
|
-
method: 'GET',
|
|
662
|
-
path: '/api/getUserByToken', //未登入主頁時需先檢測token, getUserByToken為w-ui-loginout預設值, 若要更改兩邊須同時修改
|
|
663
|
-
handler: async function (req, res) {
|
|
664
|
-
// console.log('getUserByToken', req)
|
|
665
|
-
|
|
666
|
-
async function core() {
|
|
667
|
-
|
|
668
|
-
//token
|
|
669
|
-
let token = get(req, 'query.token', '')
|
|
670
|
-
|
|
671
|
-
//check
|
|
672
|
-
if (!isestr(token)) {
|
|
673
|
-
console.log('req.query', get(req, 'query'))
|
|
674
|
-
console.log('token', maskTok(token))
|
|
675
|
-
console.log('[API]getUserByToken/check token: invalid token')
|
|
676
|
-
return Promise.reject('errTokenNoPermission')
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
//getAndVerifyClientUser
|
|
680
|
-
let user = await getAndVerifyClientUser(token, 'getUserByToken')
|
|
681
|
-
|
|
682
|
-
//check
|
|
683
|
-
if (!iseobj(user)) {
|
|
684
|
-
console.log('token', maskTok(token))
|
|
685
|
-
console.log('[API]getUserByToken/check user: invalid user')
|
|
686
|
-
return Promise.reject('errTokenNoPermission')
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
srLog.info({ event: 'api/getUserByToken', userId: get(user, 'id', '') })
|
|
690
|
-
return user
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
//pm2resolve core
|
|
694
|
-
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
695
|
-
if (isErr(r.msg)) {
|
|
696
|
-
r.msg = r.msg.message
|
|
697
|
-
}
|
|
698
|
-
// console.log('getUserByToken', r)
|
|
699
|
-
|
|
700
|
-
return r
|
|
701
|
-
},
|
|
702
|
-
},
|
|
703
|
-
|
|
704
|
-
// {
|
|
705
|
-
// method: 'GET',
|
|
706
|
-
// path: '/getAPIsList',
|
|
707
|
-
// handler: async function (req, res) {
|
|
708
|
-
// // console.log('getAPIsList', req)
|
|
709
|
-
|
|
710
|
-
// async function core() {
|
|
711
|
-
|
|
712
|
-
// //token
|
|
713
|
-
// let token = get(req, 'query.token', '')
|
|
714
|
-
|
|
715
|
-
// //check
|
|
716
|
-
// if (!isestr(token)) {
|
|
717
|
-
// console.log('req.query', get(req, 'query'))
|
|
718
|
-
// console.log('token', token)
|
|
719
|
-
// console.log('[API]getAPIsList/check token: invalid token')
|
|
720
|
-
// return Promise.reject('errTokenNoPermission')
|
|
721
|
-
// }
|
|
722
|
-
|
|
723
|
-
// //getAndVerifyClientUser
|
|
724
|
-
// let user = await getAndVerifyClientUser(token, 'getAPIsList')
|
|
725
|
-
|
|
726
|
-
// //check
|
|
727
|
-
// if (!iseobj(user)) {
|
|
728
|
-
// console.log('token', token)
|
|
729
|
-
// console.log('[API]getAPIsList/check user: invalid user')
|
|
730
|
-
// return Promise.reject('errTokenNoPermission')
|
|
731
|
-
// }
|
|
732
|
-
|
|
733
|
-
// //getAPIsList
|
|
734
|
-
// let r = await getAPIsList({ isActive: 'y' })
|
|
735
|
-
|
|
736
|
-
// return r
|
|
737
|
-
// }
|
|
738
|
-
|
|
739
|
-
// //pm2resolve core
|
|
740
|
-
// let r = await pm2resolve(core)()
|
|
741
|
-
// if (isErr(r.msg)) {
|
|
742
|
-
// r.msg = r.msg.message
|
|
743
|
-
// }
|
|
744
|
-
// // console.log('getAPIsList', r)
|
|
745
|
-
|
|
746
|
-
// return r
|
|
747
|
-
// },
|
|
748
|
-
// },
|
|
749
|
-
|
|
750
|
-
// {
|
|
751
|
-
// method: 'POST',
|
|
752
|
-
// path: '/updateAPIs',
|
|
753
|
-
// handler: async function (req, res) {
|
|
754
|
-
// // console.log('updateAPIs', req)
|
|
755
|
-
// // console.log('payload', req.payload)
|
|
756
|
-
|
|
757
|
-
// async function core() {
|
|
758
|
-
|
|
759
|
-
// //token
|
|
760
|
-
// let token = get(req, 'query.token', '')
|
|
761
|
-
|
|
762
|
-
// //check
|
|
763
|
-
// if (!isestr(token)) {
|
|
764
|
-
// console.log('req.query', get(req, 'query'))
|
|
765
|
-
// console.log('token', token)
|
|
766
|
-
// console.log('[API]updateAPIs/check token: invalid token')
|
|
767
|
-
// return Promise.reject('errTokenNoPermission')
|
|
768
|
-
// }
|
|
769
|
-
|
|
770
|
-
// //getAndVerifyClientUser
|
|
771
|
-
// let user = await getAndVerifyClientUser(token, 'updateAPIs')
|
|
772
|
-
|
|
773
|
-
// //check
|
|
774
|
-
// if (!iseobj(user)) {
|
|
775
|
-
// console.log('token', token)
|
|
776
|
-
// console.log('[API]updateAPIs/check user: invalid user')
|
|
777
|
-
// return Promise.reject('errTokenNoPermission')
|
|
778
|
-
// }
|
|
779
|
-
|
|
780
|
-
// //parsePayload
|
|
781
|
-
// let inp = await parsePayload(req)
|
|
782
|
-
|
|
783
|
-
// //updateAPIs
|
|
784
|
-
// let r = await updateAPIs(inp)
|
|
785
|
-
|
|
786
|
-
// return r
|
|
787
|
-
// }
|
|
788
|
-
|
|
789
|
-
// //pm2resolve core
|
|
790
|
-
// let r = await pm2resolve(core)()
|
|
791
|
-
// if (isErr(r.msg)) {
|
|
792
|
-
// r.msg = r.msg.message
|
|
793
|
-
// }
|
|
794
|
-
// // console.log('updateAPIs', r)
|
|
795
|
-
|
|
796
|
-
// return r
|
|
797
|
-
// },
|
|
798
|
-
// },
|
|
799
|
-
|
|
800
|
-
{
|
|
801
|
-
method: 'POST',
|
|
802
|
-
path: '/syncAndReplaceTabs',
|
|
803
|
-
handler: async function (req, res) {
|
|
804
|
-
// console.log('syncAndReplaceTabs', req)
|
|
805
|
-
// console.log('payload', req.payload)
|
|
806
|
-
|
|
807
|
-
async function core() {
|
|
808
|
-
|
|
809
|
-
//token
|
|
810
|
-
let token = get(req, 'query.token', '')
|
|
811
|
-
|
|
812
|
-
//check
|
|
813
|
-
if (!isestr(token)) {
|
|
814
|
-
console.log('req.query', get(req, 'query'))
|
|
815
|
-
console.log('token', maskTok(token))
|
|
816
|
-
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
817
|
-
return Promise.reject('errTokenNoPermission')
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
//keyTable
|
|
821
|
-
let keyTable = get(req, 'query.keyTable', '')
|
|
822
|
-
|
|
823
|
-
//check
|
|
824
|
-
if (!isestr(keyTable)) {
|
|
825
|
-
console.log('req.query', get(req, 'query'))
|
|
826
|
-
console.log('keyTable', keyTable)
|
|
827
|
-
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
828
|
-
return Promise.reject('errKeyTableInvalid')
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
//check keyTable
|
|
832
|
-
let allowedTables = ['apis']
|
|
833
|
-
if (!allowedTables.includes(keyTable)) {
|
|
834
|
-
console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
|
|
835
|
-
return Promise.reject('errKeyTableInvalid')
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
//getAndVerifyAppUser
|
|
839
|
-
let user = await getAndVerifyAppUser(token, 'syncAndReplaceTabs')
|
|
840
|
-
|
|
841
|
-
//check
|
|
842
|
-
if (!iseobj(user)) {
|
|
843
|
-
console.log('token', maskTok(token))
|
|
844
|
-
console.log('[API]syncAndReplaceTabs/check user: invalid user')
|
|
845
|
-
return Promise.reject('errTokenNoPermission')
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
//userId
|
|
849
|
-
let userId = get(user, 'id', '')
|
|
850
|
-
|
|
851
|
-
//check userId
|
|
852
|
-
if (!isestr(userId)) {
|
|
853
|
-
console.log('token', maskTok(token))
|
|
854
|
-
console.log('user', user)
|
|
855
|
-
console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
|
|
856
|
-
return Promise.reject('errTokenNoPermission')
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
//parsePayload
|
|
860
|
-
let inp = await parsePayload(req)
|
|
861
|
-
|
|
862
|
-
//syncAndReplaceTabs
|
|
863
|
-
let r = await syncAndReplaceTabs(userId, inp, keyTable)
|
|
864
|
-
|
|
865
|
-
srLog.info({ event: 'api/syncAndReplaceTabs', userId, keyTable })
|
|
866
|
-
return r
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
//pm2resolve core
|
|
870
|
-
let r = await pm2resolve(core)()
|
|
871
|
-
if (isErr(r.msg)) {
|
|
872
|
-
r.msg = r.msg.message
|
|
873
|
-
}
|
|
874
|
-
// console.log('syncAndReplaceTabs', r)
|
|
875
|
-
|
|
876
|
-
return r
|
|
877
|
-
},
|
|
878
|
-
},
|
|
879
|
-
|
|
880
|
-
]
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
//tableNamesExec, tableNamesSync
|
|
884
|
-
let tableNamesExec = keys(ds)
|
|
885
|
-
// let tableNamesSync = filter(tableNamesExec, (v) => {
|
|
886
|
-
// return strright(v, 5) !== 'Items'//不同步數據
|
|
887
|
-
// })
|
|
888
|
-
let tableNamesSync = tableNamesExec
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
//WServHapiServer
|
|
892
|
-
instWServHapiServer = new WServHapiServer({
|
|
893
|
-
port: serverPort,
|
|
894
|
-
pathStaticFiles,
|
|
895
|
-
apiName: 'api',
|
|
896
|
-
apis,
|
|
897
|
-
tokenType: 'Bearer',
|
|
898
|
-
verifyConn: async ({ apiType, authorization, query, headers, req }) => {
|
|
899
|
-
// console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
|
|
900
|
-
|
|
901
|
-
//token
|
|
902
|
-
let token = strdelleft(authorization, 7) //刪除Bearer
|
|
903
|
-
// console.log('token', token)
|
|
904
|
-
|
|
905
|
-
//check
|
|
906
|
-
if (!isestr(token)) {
|
|
907
|
-
console.log('apiType', apiType)
|
|
908
|
-
console.log('authorization', maskTok(authorization))
|
|
909
|
-
console.log('invalid token')
|
|
910
|
-
return false
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
//checkToken:套件層連線閘僅做「token 認證」(解析 user + 驗欄位齊備)。**授權**(如限管理員)屬
|
|
914
|
-
//套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
|
|
915
|
-
//或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
|
|
916
|
-
//保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
|
|
917
|
-
let b = await p.checkToken(token)
|
|
918
|
-
|
|
919
|
-
srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過(checkToken 失敗會 throw 不到此)
|
|
920
|
-
return b
|
|
921
|
-
},
|
|
922
|
-
getUserIdByToken: async (token) => { //可使用async或sync函數
|
|
923
|
-
// console.log('getUserIdByToken', token)
|
|
924
|
-
if (!isestr(token)) {
|
|
925
|
-
return ''
|
|
926
|
-
}
|
|
927
|
-
let user = await getUserByToken(token)
|
|
928
|
-
let userId = get(user, 'id', '')
|
|
929
|
-
return userId
|
|
930
|
-
},
|
|
931
|
-
useDbOrm: true,
|
|
932
|
-
kpOrm: woItems,
|
|
933
|
-
operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
|
|
934
|
-
tableNamesExec,
|
|
935
|
-
methodsExec: ['select', 'insert', 'save', 'del'], //mix需於procOrm內註冊以提供
|
|
936
|
-
tableNamesSync,
|
|
937
|
-
kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數(皆經 wrapErrLog:錯誤 srLog err-key 後 re-reject)
|
|
938
|
-
|
|
939
|
-
getWebInfor: wrapErrLog('getWebInfor', getWebInfor),
|
|
940
|
-
|
|
941
|
-
proxyRequest: wrapErrLog('proxyRequest', (userId, reqSpec) => {
|
|
942
|
-
return proxyRequest(reqSpec)
|
|
943
|
-
}),
|
|
944
|
-
|
|
945
|
-
getApisList: wrapErrLog('getApisList', (userId) => {
|
|
946
|
-
return getApisList(userId)
|
|
947
|
-
}),
|
|
948
|
-
saveApi: wrapErrLog('saveApi', (userId, row) => {
|
|
949
|
-
return saveApi(userId, row)
|
|
950
|
-
}),
|
|
951
|
-
deleteApi: wrapErrLog('deleteApi', (userId, id) => {
|
|
952
|
-
return deleteApi(userId, id)
|
|
953
|
-
}),
|
|
954
|
-
|
|
955
|
-
//統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
|
|
956
|
-
//授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
|
|
957
|
-
getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
|
|
958
|
-
return staEvent(timeLength, timeInterval, { fdLog: logFd })
|
|
959
|
-
}),
|
|
960
|
-
|
|
961
|
-
// getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
|
|
962
|
-
// return getAPIsList(query)
|
|
963
|
-
// },
|
|
964
|
-
// updateApis: (userId, rows) => { //待提供api管理系統界面使用
|
|
965
|
-
// return updateApis(rows)
|
|
966
|
-
// },
|
|
967
|
-
|
|
968
|
-
},
|
|
969
|
-
fnTableTags: 'tableTags-web-api.json',
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
return instWServHapiServer
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
export default WWebApi
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import path from 'path'
|
|
49
|
+
import fs from 'fs'
|
|
50
|
+
import get from 'lodash-es/get.js'
|
|
51
|
+
import each from 'lodash-es/each.js'
|
|
52
|
+
import map from 'lodash-es/map.js'
|
|
53
|
+
import keys from 'lodash-es/keys.js'
|
|
54
|
+
import j2o from 'wsemi/src/j2o.mjs'
|
|
55
|
+
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
56
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
57
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
58
|
+
import ispint from 'wsemi/src/ispint.mjs'
|
|
59
|
+
import isErr from 'wsemi/src/isErr.mjs'
|
|
60
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
61
|
+
import ispm from 'wsemi/src/ispm.mjs'
|
|
62
|
+
import cint from 'wsemi/src/cint.mjs'
|
|
63
|
+
import strleft from 'wsemi/src/strleft.mjs'
|
|
64
|
+
import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
65
|
+
import strright from 'wsemi/src/strright.mjs'
|
|
66
|
+
import strdelright from 'wsemi/src/strdelright.mjs'
|
|
67
|
+
import pm2resolve from 'wsemi/src/pm2resolve.mjs'
|
|
68
|
+
import b642str from 'wsemi/src/b642str.mjs'
|
|
69
|
+
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
70
|
+
import fsIsFolder from 'wsemi/src/fsIsFolder.mjs'
|
|
71
|
+
import fsIsFile from 'wsemi/src/fsIsFile.mjs'
|
|
72
|
+
import replace from 'wsemi/src/replace.mjs'
|
|
73
|
+
import WServHapiServer from 'w-serv-hapi/src/WServHapiServer.mjs'
|
|
74
|
+
import WServOrm from 'w-serv-orm/src/WServOrm.mjs'
|
|
75
|
+
import procLang from './procLang.mjs'
|
|
76
|
+
import procProxy from './procProxy.mjs'
|
|
77
|
+
import procApis from './procApis.mjs'
|
|
78
|
+
import staEvent from './staLogs/staEvent.mjs'
|
|
79
|
+
import srLogInit from './srLog.mjs'
|
|
80
|
+
import ds from '../src/schema/index.mjs'
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* API伺服器
|
|
85
|
+
*
|
|
86
|
+
* @class
|
|
87
|
+
* @param {Function} WOrm 輸入資料庫ORM函數
|
|
88
|
+
* @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
|
|
89
|
+
* @param {String} db 輸入資料庫名稱字串
|
|
90
|
+
* @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
|
|
91
|
+
* @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
|
|
92
|
+
* @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
|
|
93
|
+
* @param {Object} [opt={}] 輸入設定物件,預設{}
|
|
94
|
+
* @param {Integer} [opt.serverPort=11005] 輸入伺服器通訊port,預設11005
|
|
95
|
+
* @param {Boolean} [opt.useCheckUser=false] 輸入是否檢查使用者資訊布林值,預設false
|
|
96
|
+
* @param {Function} [opt.getUserById=null] 輸入當useCheckUser=true時依照使用者ID取得使用者資訊物件函數,預設null
|
|
97
|
+
* @param {Boolean} [opt.useExcludeWhenNotAdmin=false] 輸入使用ORM的select方法時是否自動刪除數據內isActive欄位之布林值,預設false
|
|
98
|
+
* @param {Object} [opt.webName={}] 輸入站台名稱物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
99
|
+
* @param {Object} [opt.webDescription={}] 輸入站台描述物件,至少包含語系eng與cht鍵的名稱,預設{}
|
|
100
|
+
* @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
|
|
101
|
+
* @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
|
|
102
|
+
* @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
|
|
103
|
+
* @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
|
|
104
|
+
* @example
|
|
105
|
+
*
|
|
106
|
+
* import WOrm from 'w-orm-lmdb/src/WOrmLmdb.mjs'
|
|
107
|
+
* import WWebApi from './server/WWebApi.mjs'
|
|
108
|
+
* import getSettings from './g_getSettings.mjs'
|
|
109
|
+
*
|
|
110
|
+
*
|
|
111
|
+
* //st
|
|
112
|
+
* let st = getSettings()
|
|
113
|
+
*
|
|
114
|
+
* let url = st.dbUrl
|
|
115
|
+
* let db = st.dbName
|
|
116
|
+
* let opt = {
|
|
117
|
+
*
|
|
118
|
+
* useCheckUser: false,
|
|
119
|
+
* getUserById: null,
|
|
120
|
+
* useExcludeWhenNotAdmin: false,
|
|
121
|
+
*
|
|
122
|
+
* serverPort: 11005,
|
|
123
|
+
* subfolder: '', //mapi
|
|
124
|
+
* urlRedirect: 'https://www.google.com/', //本機測試時得先編譯, 再瀏覽: http://localhost:11005/
|
|
125
|
+
*
|
|
126
|
+
* webName: {
|
|
127
|
+
* 'eng': 'API Service',
|
|
128
|
+
* 'cht': 'API管理系統',
|
|
129
|
+
* },
|
|
130
|
+
* webDescription: {
|
|
131
|
+
* 'eng': 'A web service package as methods to send requests to and receive responses from an API.',
|
|
132
|
+
* 'cht': 'A web service package as methods to send requests to and receive responses from an API.',
|
|
133
|
+
* },
|
|
134
|
+
* webLogo: 'data:image/svg+xml;base64,...',
|
|
135
|
+
*
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* let getUserByToken = async (token) => {
|
|
139
|
+
* // return {} //測試無法登入
|
|
140
|
+
* if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
|
|
141
|
+
* return {
|
|
142
|
+
* id: 'id-for-application',
|
|
143
|
+
* name: 'application',
|
|
144
|
+
* email: 'application@example.com',
|
|
145
|
+
* isAdmin: 'y',
|
|
146
|
+
* }
|
|
147
|
+
* }
|
|
148
|
+
* if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
|
|
149
|
+
* return {
|
|
150
|
+
* id: 'id-for-admin',
|
|
151
|
+
* name: '測試者',
|
|
152
|
+
* email: 'admin@example.com',
|
|
153
|
+
* isAdmin: 'y',
|
|
154
|
+
* }
|
|
155
|
+
* }
|
|
156
|
+
* console.log('invalid token', token)
|
|
157
|
+
* console.log('於生產環境時得加入SSO等驗證token機制')
|
|
158
|
+
* return {}
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* let verifyClientUser = (user, from) => {
|
|
162
|
+
* console.log('verifyClientUser/user', user)
|
|
163
|
+
* // return false //測試無法登入
|
|
164
|
+
* console.log('於生產環境時得加入限制瀏覽器使用者身份機制')
|
|
165
|
+
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
166
|
+
* }
|
|
167
|
+
*
|
|
168
|
+
* let verifyAppUser = (user, from) => {
|
|
169
|
+
* console.log('verifyAppUser/user', user)
|
|
170
|
+
* // return false //測試無法登入
|
|
171
|
+
* console.log('於生產環境時得加入限制應用程式使用者身份機制')
|
|
172
|
+
* return user.isAdmin === 'y' //測試僅系統管理者使用
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* //WWebApi
|
|
176
|
+
* let instWWebApi = WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt)
|
|
177
|
+
*
|
|
178
|
+
* instWWebApi.on('error', (err) => {
|
|
179
|
+
* console.log(err)
|
|
180
|
+
* })
|
|
181
|
+
*
|
|
182
|
+
*/
|
|
183
|
+
function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser, opt = {}) {
|
|
184
|
+
let instWServHapiServer = null
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
//check WOrm
|
|
188
|
+
if (!isfun(WOrm)) {
|
|
189
|
+
console.log('invalid WOrm', WOrm)
|
|
190
|
+
throw new Error('invalid WOrm')
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
//check url
|
|
195
|
+
if (!isestr(url)) {
|
|
196
|
+
console.log('invalid url', url)
|
|
197
|
+
throw new Error('invalid url')
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
//check db
|
|
202
|
+
if (!isestr(db)) {
|
|
203
|
+
console.log('invalid db', db)
|
|
204
|
+
throw new Error('invalid db')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
//check getUserByToken
|
|
209
|
+
if (!isfun(getUserByToken)) {
|
|
210
|
+
console.log('invalid getUserByToken', getUserByToken)
|
|
211
|
+
throw new Error('invalid getUserByToken')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
//check verifyClientUser
|
|
216
|
+
if (!isfun(verifyClientUser)) {
|
|
217
|
+
console.log('invalid verifyClientUser', verifyClientUser)
|
|
218
|
+
throw new Error('invalid verifyClientUser')
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
//check verifyAppUser
|
|
223
|
+
if (!isfun(verifyAppUser)) {
|
|
224
|
+
console.log('invalid verifyAppUser', verifyAppUser)
|
|
225
|
+
throw new Error('invalid verifyAppUser')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
//serverPort
|
|
230
|
+
let serverPort = get(opt, 'serverPort')
|
|
231
|
+
if (!ispint(serverPort)) {
|
|
232
|
+
serverPort = 11005
|
|
233
|
+
}
|
|
234
|
+
serverPort = cint(serverPort)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
//useCheckUser
|
|
238
|
+
let useCheckUser = get(opt, 'useCheckUser', false)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
//getUserById
|
|
242
|
+
let getUserById = get(opt, 'getUserById', null)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
//useExcludeWhenNotAdmin
|
|
246
|
+
let useExcludeWhenNotAdmin = get(opt, 'useExcludeWhenNotAdmin', false)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
//webName
|
|
250
|
+
let webName = get(opt, 'webName', {})
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
//webDescription
|
|
254
|
+
let webDescription = get(opt, 'webDescription', {})
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
//webLogo
|
|
258
|
+
let webLogo = get(opt, 'webLogo', '')
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
//subfolder
|
|
262
|
+
let subfolder = get(opt, 'subfolder', '')
|
|
263
|
+
if (isestr(subfolder)) {
|
|
264
|
+
if (strright(subfolder, 1) === '/') { //右邊不需要給「/」
|
|
265
|
+
subfolder = strdelright(subfolder, 1)
|
|
266
|
+
}
|
|
267
|
+
if (strleft(subfolder, 1) !== '/') { //左邊需要給「/」
|
|
268
|
+
subfolder = `/${subfolder}`
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
//urlRedirect
|
|
274
|
+
let urlRedirect = get(opt, 'urlRedirect', '')
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
//params
|
|
278
|
+
let showLanguage = get(opt, 'showLanguage', 'y')
|
|
279
|
+
let language = get(opt, 'language', 'eng')
|
|
280
|
+
let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
//kpLangExt
|
|
284
|
+
let kpLangExt = get(opt, 'kpLangExt', {})
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
//kpLang
|
|
288
|
+
let kpLang = procLang({ kpLangExt, webName, webDescription })
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
//WServOrm
|
|
292
|
+
let optWServOrm = {
|
|
293
|
+
useCheckUser,
|
|
294
|
+
getUserById,
|
|
295
|
+
useExcludeWhenNotAdmin,
|
|
296
|
+
}
|
|
297
|
+
let wp = {}
|
|
298
|
+
try {
|
|
299
|
+
wp = WServOrm(ds, WOrm, url, db, optWServOrm)
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
//ORM 為必要依賴:初始化失敗即 fail-fast,不以 wp={} 帶 undefined woItems/procOrm 殘缺啟動(與建構子其餘必要依賴一致)
|
|
303
|
+
console.log('WServOrm init failed', err)
|
|
304
|
+
throw err
|
|
305
|
+
}
|
|
306
|
+
let { woItems, procOrm } = wp
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
//srLog(後端 log;錯誤一律 log err-key 供識別,對齊 w-web-sso)
|
|
310
|
+
let srLog = srLogInit(opt)
|
|
311
|
+
|
|
312
|
+
//maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
|
|
313
|
+
let maskTok = (t) => isestr(t) ? (strleft(t, 4) + '***') : t
|
|
314
|
+
|
|
315
|
+
//kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
|
|
316
|
+
//原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
|
|
317
|
+
let wrapErrLog = (event, fn) => {
|
|
318
|
+
let ev = 'kpfun-' + event
|
|
319
|
+
return async (...args) => {
|
|
320
|
+
try {
|
|
321
|
+
let result = await fn(...args)
|
|
322
|
+
srLog.info({ event: ev }) //成功也記 info
|
|
323
|
+
return result
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
srLog.error({ event: ev, err })
|
|
327
|
+
return Promise.reject(err)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
//procProxy
|
|
334
|
+
let { proxyRequest } = procProxy({ srLog })
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
//procApis
|
|
338
|
+
let { getApisList, saveApi, deleteApi } = procApis({ woItems, procOrm, ds })
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
//getWebInfor
|
|
342
|
+
let getWebInfor = (userId) => {
|
|
343
|
+
return {
|
|
344
|
+
|
|
345
|
+
// webName, //已併入kpLang
|
|
346
|
+
// webDescription, //已併入kpLang
|
|
347
|
+
webLogo,
|
|
348
|
+
|
|
349
|
+
showLanguage,
|
|
350
|
+
language,
|
|
351
|
+
kpLang,
|
|
352
|
+
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
//checkUser
|
|
358
|
+
let checkUser = async(user) => {
|
|
359
|
+
let id = get(user, 'id', '')
|
|
360
|
+
if (!isestr(id)) {
|
|
361
|
+
console.log('user', user)
|
|
362
|
+
console.log('can not get the userId')
|
|
363
|
+
return Promise.reject('errUserIdMissing')
|
|
364
|
+
}
|
|
365
|
+
let email = get(user, 'email', '')
|
|
366
|
+
if (!isestr(email)) {
|
|
367
|
+
console.log('user', user)
|
|
368
|
+
console.log('can not get the email of user')
|
|
369
|
+
return Promise.reject('errUserEmailMissing')
|
|
370
|
+
}
|
|
371
|
+
let name = get(user, 'name', '')
|
|
372
|
+
if (!isestr(name)) {
|
|
373
|
+
console.log('user', user)
|
|
374
|
+
console.log('can not get userName')
|
|
375
|
+
return Promise.reject('errUserNameMissing')
|
|
376
|
+
}
|
|
377
|
+
let isAdmin = get(user, 'isAdmin', '')
|
|
378
|
+
if (isAdmin !== 'y' && isAdmin !== 'n') {
|
|
379
|
+
console.log('user', user)
|
|
380
|
+
console.log('user.isAdmin is not y or n', user.isAdmin)
|
|
381
|
+
console.log('can not get the role of user')
|
|
382
|
+
return Promise.reject('errUserRoleMissing')
|
|
383
|
+
}
|
|
384
|
+
return true
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
//getTokenUser
|
|
389
|
+
let getTokenUser = async (token) => {
|
|
390
|
+
|
|
391
|
+
//getUserByToken
|
|
392
|
+
let userSelf = getUserByToken(token)
|
|
393
|
+
if (ispm(userSelf)) {
|
|
394
|
+
userSelf = await userSelf
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//check
|
|
398
|
+
await checkUser(userSelf)
|
|
399
|
+
|
|
400
|
+
return userSelf
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
//p
|
|
405
|
+
let p = {
|
|
406
|
+
checkToken: async(token) => {
|
|
407
|
+
|
|
408
|
+
//getUserByToken
|
|
409
|
+
let u = await getUserByToken(token)
|
|
410
|
+
// console.log('u', u)
|
|
411
|
+
|
|
412
|
+
//checkUser
|
|
413
|
+
await checkUser(u)
|
|
414
|
+
|
|
415
|
+
return true
|
|
416
|
+
},
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
//getAndVerifyClientUser
|
|
421
|
+
let getAndVerifyClientUser = async (token, from = '') => {
|
|
422
|
+
//基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
|
|
423
|
+
|
|
424
|
+
//getTokenUser
|
|
425
|
+
let user = await getTokenUser(token)
|
|
426
|
+
|
|
427
|
+
//verifyClientUser
|
|
428
|
+
let b = verifyClientUser(user, from)
|
|
429
|
+
if (ispm(b)) {
|
|
430
|
+
b = await b
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//check
|
|
434
|
+
if (!b) {
|
|
435
|
+
console.log('user', user)
|
|
436
|
+
console.log(`user does not have permission`)
|
|
437
|
+
return Promise.reject('errUserNoPermission')
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
srLog.info({ event: 'verifyClientUser', from, userId: get(user, 'id', '') }) //瀏覽使用者驗證通過
|
|
441
|
+
return user
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
//getAndVerifyAppUser
|
|
446
|
+
let getAndVerifyAppUser = async (token, from = '') => {
|
|
447
|
+
//基於token查找app使用者, 會基於外部getUserByToken進行查找, 故只要getUserByToken提供使用者即可, 可支援getUserByToken給予針對應用程式所產生之虛擬使用者
|
|
448
|
+
|
|
449
|
+
//user
|
|
450
|
+
let user = null
|
|
451
|
+
if (isestr(token)) {
|
|
452
|
+
user = getUserByToken(token)
|
|
453
|
+
if (ispm(user)) {
|
|
454
|
+
user = await user
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// console.log('user', user)
|
|
458
|
+
|
|
459
|
+
//check
|
|
460
|
+
if (!iseobj(user)) {
|
|
461
|
+
console.log(`token`, token)
|
|
462
|
+
console.log(`can not find the user from token`)
|
|
463
|
+
return Promise.reject('errUserNotFound')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
//verifyAppUser
|
|
467
|
+
let b = verifyAppUser(user, from)
|
|
468
|
+
if (ispm(b)) {
|
|
469
|
+
b = await b
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
//check
|
|
473
|
+
if (!b) {
|
|
474
|
+
console.log('user', user)
|
|
475
|
+
console.log(`user does not have permission`)
|
|
476
|
+
return Promise.reject('errUserNoPermission')
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
srLog.info({ event: 'verifyAppUser', from, userId: get(user, 'id', '') }) //應用程式使用者驗證通過
|
|
480
|
+
return user
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
// //getAPIsList
|
|
485
|
+
// let getAPIsList = async (query = {}) => {
|
|
486
|
+
// let rs = await woItems.apis.select(query)
|
|
487
|
+
// return rs
|
|
488
|
+
// }
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
//parsePayload
|
|
492
|
+
let parsePayload = async (req) => {
|
|
493
|
+
|
|
494
|
+
//payload
|
|
495
|
+
let payload = get(req, 'payload')
|
|
496
|
+
|
|
497
|
+
//to obj
|
|
498
|
+
if (isestr(payload)) {
|
|
499
|
+
payload = j2o(payload)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
//check
|
|
503
|
+
if (!iseobj(payload)) {
|
|
504
|
+
console.log('payload', payload)
|
|
505
|
+
console.log(`invalid payload in req`)
|
|
506
|
+
return Promise.reject('errPayloadInvalid')
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
//group
|
|
510
|
+
let group = get(payload, 'group', '')
|
|
511
|
+
|
|
512
|
+
//check
|
|
513
|
+
if (!isestr(group)) {
|
|
514
|
+
console.log('payload', payload)
|
|
515
|
+
console.log(`invalid group in payload`)
|
|
516
|
+
return Promise.reject('errGroupInvalid')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
//rows
|
|
520
|
+
let rows = get(payload, 'rows', [])
|
|
521
|
+
|
|
522
|
+
//check
|
|
523
|
+
if (!isearr(rows)) {
|
|
524
|
+
console.log('payload', payload)
|
|
525
|
+
console.log(`invalid rows in payload`)
|
|
526
|
+
return Promise.reject('errRowsInvalid')
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
//r
|
|
530
|
+
let r = {
|
|
531
|
+
group,
|
|
532
|
+
rows,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return r
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
// //updateAPIs
|
|
540
|
+
// let updateAPIs = async (inp = {}) => {
|
|
541
|
+
|
|
542
|
+
// //spread inp
|
|
543
|
+
// let { apis: rsApis } = inp
|
|
544
|
+
// // console.log('group', group)
|
|
545
|
+
// // console.log('rsApis', rsApis)
|
|
546
|
+
|
|
547
|
+
// //save
|
|
548
|
+
// let r = await woItems.apis.save(rsApis)
|
|
549
|
+
|
|
550
|
+
// return r
|
|
551
|
+
// }
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
//syncAndReplaceTabs
|
|
555
|
+
let syncAndReplaceTabs = async (userId, inp, keyTable) => {
|
|
556
|
+
|
|
557
|
+
//group
|
|
558
|
+
let group = get(inp, 'group', '')
|
|
559
|
+
// console.log('group', group)
|
|
560
|
+
|
|
561
|
+
//check
|
|
562
|
+
if (!isestr(group)) {
|
|
563
|
+
console.log('inp', inp)
|
|
564
|
+
console.log('group', group)
|
|
565
|
+
console.log(`invalid group`)
|
|
566
|
+
return Promise.reject('errGroupInvalid')
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
//rows
|
|
570
|
+
let rows = get(inp, 'rows', [])
|
|
571
|
+
// console.log('rows', rows)
|
|
572
|
+
|
|
573
|
+
//check
|
|
574
|
+
if (!isearr(rows)) {
|
|
575
|
+
console.log('inp', inp)
|
|
576
|
+
console.log('keyTable', keyTable)
|
|
577
|
+
return Promise.reject('errRowsInvalid')
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
//save from
|
|
581
|
+
rows = map(rows, (v, k) => {
|
|
582
|
+
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
583
|
+
v.group = group
|
|
584
|
+
return v
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
//偵測b64自動恢復
|
|
588
|
+
if (keyTable === 'apis') {
|
|
589
|
+
each(rows, (row, krow) => {
|
|
590
|
+
// console.log(kapi, row.name)
|
|
591
|
+
each(row, (v, k) => {
|
|
592
|
+
// console.log(k, v)
|
|
593
|
+
if (isestr(v)) {
|
|
594
|
+
if (strleft(v, 7) === 'base64:') {
|
|
595
|
+
// console.log(k, 'get base64')
|
|
596
|
+
v = strdelleft(v, 7)
|
|
597
|
+
v = b642str(v)
|
|
598
|
+
// console.log(v)
|
|
599
|
+
rows[krow][k] = v
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
})
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
//ltdtmapping
|
|
607
|
+
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
608
|
+
// console.log('ltdtmapping rows', rows)
|
|
609
|
+
|
|
610
|
+
//delAll group
|
|
611
|
+
await woItems[keyTable].delAll({ group })
|
|
612
|
+
|
|
613
|
+
//insert
|
|
614
|
+
// let r = await woItems[keyTable].insert(rows)
|
|
615
|
+
let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
|
|
616
|
+
// console.log('r', r)
|
|
617
|
+
|
|
618
|
+
return r
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
//pathStaticFiles
|
|
623
|
+
let pathStaticFiles = 'dist'
|
|
624
|
+
let npmPathStaticFiles = './node_modules/w-web-api/dist'
|
|
625
|
+
if (fsIsFolder(npmPathStaticFiles)) {
|
|
626
|
+
pathStaticFiles = npmPathStaticFiles
|
|
627
|
+
}
|
|
628
|
+
// console.log('pathStaticFiles', pathStaticFiles)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
//subfolder
|
|
632
|
+
let fnEntryIn = 'index.tmp'
|
|
633
|
+
let fnEntryOut = 'index.html'
|
|
634
|
+
try {
|
|
635
|
+
let fpEntryIn = path.resolve(pathStaticFiles, fnEntryIn)
|
|
636
|
+
if (!fsIsFile(fpEntryIn)) {
|
|
637
|
+
fpEntryIn = path.resolve(pathStaticFiles, fnEntryOut) //本機開發另使用html替代tmp
|
|
638
|
+
}
|
|
639
|
+
if (!fsIsFile(fpEntryIn)) {
|
|
640
|
+
console.log('fpEntryIn', fpEntryIn)
|
|
641
|
+
throw new Error(`invalid fpEntryIn`)
|
|
642
|
+
}
|
|
643
|
+
let fpEntryOut = path.resolve(pathStaticFiles, fnEntryOut)
|
|
644
|
+
let c = fs.readFileSync(fpEntryIn, 'utf8')
|
|
645
|
+
c = replace(c, '/mapi/', '{sfd}/') //方法同genEntry
|
|
646
|
+
c = replace(c, '{sfd}', subfolder)
|
|
647
|
+
c = replace(c, '{urlRedirect}', urlRedirect)
|
|
648
|
+
c = replace(c, '{language}', language) //注入初始語系 → window.___pmwperm___.language(前端 getLang 之來源之一)
|
|
649
|
+
fs.writeFileSync(fpEntryOut, c, 'utf8')
|
|
650
|
+
}
|
|
651
|
+
catch (err) {
|
|
652
|
+
console.log(err)
|
|
653
|
+
console.log(`can not generate ${fnEntryOut}`)
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
//apis
|
|
658
|
+
let apis = [
|
|
659
|
+
|
|
660
|
+
{
|
|
661
|
+
method: 'GET',
|
|
662
|
+
path: '/api/getUserByToken', //未登入主頁時需先檢測token, getUserByToken為w-ui-loginout預設值, 若要更改兩邊須同時修改
|
|
663
|
+
handler: async function (req, res) {
|
|
664
|
+
// console.log('getUserByToken', req)
|
|
665
|
+
|
|
666
|
+
async function core() {
|
|
667
|
+
|
|
668
|
+
//token
|
|
669
|
+
let token = get(req, 'query.token', '')
|
|
670
|
+
|
|
671
|
+
//check
|
|
672
|
+
if (!isestr(token)) {
|
|
673
|
+
console.log('req.query', get(req, 'query'))
|
|
674
|
+
console.log('token', maskTok(token))
|
|
675
|
+
console.log('[API]getUserByToken/check token: invalid token')
|
|
676
|
+
return Promise.reject('errTokenNoPermission')
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
//getAndVerifyClientUser
|
|
680
|
+
let user = await getAndVerifyClientUser(token, 'getUserByToken')
|
|
681
|
+
|
|
682
|
+
//check
|
|
683
|
+
if (!iseobj(user)) {
|
|
684
|
+
console.log('token', maskTok(token))
|
|
685
|
+
console.log('[API]getUserByToken/check user: invalid user')
|
|
686
|
+
return Promise.reject('errTokenNoPermission')
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
srLog.info({ event: 'api/getUserByToken', userId: get(user, 'id', '') })
|
|
690
|
+
return user
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
//pm2resolve core
|
|
694
|
+
let r = await pm2resolve(core)() //w-ui-loginout接收已預設格式用pm2resolve轉過, 會提取state進行判斷
|
|
695
|
+
if (isErr(r.msg)) {
|
|
696
|
+
r.msg = r.msg.message
|
|
697
|
+
}
|
|
698
|
+
// console.log('getUserByToken', r)
|
|
699
|
+
|
|
700
|
+
return r
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
// {
|
|
705
|
+
// method: 'GET',
|
|
706
|
+
// path: '/getAPIsList',
|
|
707
|
+
// handler: async function (req, res) {
|
|
708
|
+
// // console.log('getAPIsList', req)
|
|
709
|
+
|
|
710
|
+
// async function core() {
|
|
711
|
+
|
|
712
|
+
// //token
|
|
713
|
+
// let token = get(req, 'query.token', '')
|
|
714
|
+
|
|
715
|
+
// //check
|
|
716
|
+
// if (!isestr(token)) {
|
|
717
|
+
// console.log('req.query', get(req, 'query'))
|
|
718
|
+
// console.log('token', token)
|
|
719
|
+
// console.log('[API]getAPIsList/check token: invalid token')
|
|
720
|
+
// return Promise.reject('errTokenNoPermission')
|
|
721
|
+
// }
|
|
722
|
+
|
|
723
|
+
// //getAndVerifyClientUser
|
|
724
|
+
// let user = await getAndVerifyClientUser(token, 'getAPIsList')
|
|
725
|
+
|
|
726
|
+
// //check
|
|
727
|
+
// if (!iseobj(user)) {
|
|
728
|
+
// console.log('token', token)
|
|
729
|
+
// console.log('[API]getAPIsList/check user: invalid user')
|
|
730
|
+
// return Promise.reject('errTokenNoPermission')
|
|
731
|
+
// }
|
|
732
|
+
|
|
733
|
+
// //getAPIsList
|
|
734
|
+
// let r = await getAPIsList({ isActive: 'y' })
|
|
735
|
+
|
|
736
|
+
// return r
|
|
737
|
+
// }
|
|
738
|
+
|
|
739
|
+
// //pm2resolve core
|
|
740
|
+
// let r = await pm2resolve(core)()
|
|
741
|
+
// if (isErr(r.msg)) {
|
|
742
|
+
// r.msg = r.msg.message
|
|
743
|
+
// }
|
|
744
|
+
// // console.log('getAPIsList', r)
|
|
745
|
+
|
|
746
|
+
// return r
|
|
747
|
+
// },
|
|
748
|
+
// },
|
|
749
|
+
|
|
750
|
+
// {
|
|
751
|
+
// method: 'POST',
|
|
752
|
+
// path: '/updateAPIs',
|
|
753
|
+
// handler: async function (req, res) {
|
|
754
|
+
// // console.log('updateAPIs', req)
|
|
755
|
+
// // console.log('payload', req.payload)
|
|
756
|
+
|
|
757
|
+
// async function core() {
|
|
758
|
+
|
|
759
|
+
// //token
|
|
760
|
+
// let token = get(req, 'query.token', '')
|
|
761
|
+
|
|
762
|
+
// //check
|
|
763
|
+
// if (!isestr(token)) {
|
|
764
|
+
// console.log('req.query', get(req, 'query'))
|
|
765
|
+
// console.log('token', token)
|
|
766
|
+
// console.log('[API]updateAPIs/check token: invalid token')
|
|
767
|
+
// return Promise.reject('errTokenNoPermission')
|
|
768
|
+
// }
|
|
769
|
+
|
|
770
|
+
// //getAndVerifyClientUser
|
|
771
|
+
// let user = await getAndVerifyClientUser(token, 'updateAPIs')
|
|
772
|
+
|
|
773
|
+
// //check
|
|
774
|
+
// if (!iseobj(user)) {
|
|
775
|
+
// console.log('token', token)
|
|
776
|
+
// console.log('[API]updateAPIs/check user: invalid user')
|
|
777
|
+
// return Promise.reject('errTokenNoPermission')
|
|
778
|
+
// }
|
|
779
|
+
|
|
780
|
+
// //parsePayload
|
|
781
|
+
// let inp = await parsePayload(req)
|
|
782
|
+
|
|
783
|
+
// //updateAPIs
|
|
784
|
+
// let r = await updateAPIs(inp)
|
|
785
|
+
|
|
786
|
+
// return r
|
|
787
|
+
// }
|
|
788
|
+
|
|
789
|
+
// //pm2resolve core
|
|
790
|
+
// let r = await pm2resolve(core)()
|
|
791
|
+
// if (isErr(r.msg)) {
|
|
792
|
+
// r.msg = r.msg.message
|
|
793
|
+
// }
|
|
794
|
+
// // console.log('updateAPIs', r)
|
|
795
|
+
|
|
796
|
+
// return r
|
|
797
|
+
// },
|
|
798
|
+
// },
|
|
799
|
+
|
|
800
|
+
{
|
|
801
|
+
method: 'POST',
|
|
802
|
+
path: '/syncAndReplaceTabs',
|
|
803
|
+
handler: async function (req, res) {
|
|
804
|
+
// console.log('syncAndReplaceTabs', req)
|
|
805
|
+
// console.log('payload', req.payload)
|
|
806
|
+
|
|
807
|
+
async function core() {
|
|
808
|
+
|
|
809
|
+
//token
|
|
810
|
+
let token = get(req, 'query.token', '')
|
|
811
|
+
|
|
812
|
+
//check
|
|
813
|
+
if (!isestr(token)) {
|
|
814
|
+
console.log('req.query', get(req, 'query'))
|
|
815
|
+
console.log('token', maskTok(token))
|
|
816
|
+
console.log('[API]syncAndReplaceTabs/check token: invalid token')
|
|
817
|
+
return Promise.reject('errTokenNoPermission')
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
//keyTable
|
|
821
|
+
let keyTable = get(req, 'query.keyTable', '')
|
|
822
|
+
|
|
823
|
+
//check
|
|
824
|
+
if (!isestr(keyTable)) {
|
|
825
|
+
console.log('req.query', get(req, 'query'))
|
|
826
|
+
console.log('keyTable', keyTable)
|
|
827
|
+
console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
|
|
828
|
+
return Promise.reject('errKeyTableInvalid')
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
//check keyTable
|
|
832
|
+
let allowedTables = ['apis']
|
|
833
|
+
if (!allowedTables.includes(keyTable)) {
|
|
834
|
+
console.log('[API]syncAndReplaceTabs/check keyTable: unexpected keyTable', keyTable)
|
|
835
|
+
return Promise.reject('errKeyTableInvalid')
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
//getAndVerifyAppUser
|
|
839
|
+
let user = await getAndVerifyAppUser(token, 'syncAndReplaceTabs')
|
|
840
|
+
|
|
841
|
+
//check
|
|
842
|
+
if (!iseobj(user)) {
|
|
843
|
+
console.log('token', maskTok(token))
|
|
844
|
+
console.log('[API]syncAndReplaceTabs/check user: invalid user')
|
|
845
|
+
return Promise.reject('errTokenNoPermission')
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
//userId
|
|
849
|
+
let userId = get(user, 'id', '')
|
|
850
|
+
|
|
851
|
+
//check userId
|
|
852
|
+
if (!isestr(userId)) {
|
|
853
|
+
console.log('token', maskTok(token))
|
|
854
|
+
console.log('user', user)
|
|
855
|
+
console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
|
|
856
|
+
return Promise.reject('errTokenNoPermission')
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
//parsePayload
|
|
860
|
+
let inp = await parsePayload(req)
|
|
861
|
+
|
|
862
|
+
//syncAndReplaceTabs
|
|
863
|
+
let r = await syncAndReplaceTabs(userId, inp, keyTable)
|
|
864
|
+
|
|
865
|
+
srLog.info({ event: 'api/syncAndReplaceTabs', userId, keyTable })
|
|
866
|
+
return r
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
//pm2resolve core
|
|
870
|
+
let r = await pm2resolve(core)()
|
|
871
|
+
if (isErr(r.msg)) {
|
|
872
|
+
r.msg = r.msg.message
|
|
873
|
+
}
|
|
874
|
+
// console.log('syncAndReplaceTabs', r)
|
|
875
|
+
|
|
876
|
+
return r
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
|
|
880
|
+
]
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
//tableNamesExec, tableNamesSync
|
|
884
|
+
let tableNamesExec = keys(ds)
|
|
885
|
+
// let tableNamesSync = filter(tableNamesExec, (v) => {
|
|
886
|
+
// return strright(v, 5) !== 'Items'//不同步數據
|
|
887
|
+
// })
|
|
888
|
+
let tableNamesSync = tableNamesExec
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
//WServHapiServer
|
|
892
|
+
instWServHapiServer = new WServHapiServer({
|
|
893
|
+
port: serverPort,
|
|
894
|
+
pathStaticFiles,
|
|
895
|
+
apiName: 'api',
|
|
896
|
+
apis,
|
|
897
|
+
tokenType: 'Bearer',
|
|
898
|
+
verifyConn: async ({ apiType, authorization, query, headers, req }) => {
|
|
899
|
+
// console.log('verifyConn', `apiType[${apiType}]`, `authorization[${authorization}]`)
|
|
900
|
+
|
|
901
|
+
//token
|
|
902
|
+
let token = strdelleft(authorization, 7) //刪除Bearer
|
|
903
|
+
// console.log('token', token)
|
|
904
|
+
|
|
905
|
+
//check
|
|
906
|
+
if (!isestr(token)) {
|
|
907
|
+
console.log('apiType', apiType)
|
|
908
|
+
console.log('authorization', maskTok(authorization))
|
|
909
|
+
console.log('invalid token')
|
|
910
|
+
return false
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
//checkToken:套件層連線閘僅做「token 認證」(解析 user + 驗欄位齊備)。**授權**(如限管理員)屬
|
|
914
|
+
//套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
|
|
915
|
+
//或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
|
|
916
|
+
//保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
|
|
917
|
+
let b = await p.checkToken(token)
|
|
918
|
+
|
|
919
|
+
srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過(checkToken 失敗會 throw 不到此)
|
|
920
|
+
return b
|
|
921
|
+
},
|
|
922
|
+
getUserIdByToken: async (token) => { //可使用async或sync函數
|
|
923
|
+
// console.log('getUserIdByToken', token)
|
|
924
|
+
if (!isestr(token)) {
|
|
925
|
+
return ''
|
|
926
|
+
}
|
|
927
|
+
let user = await getUserByToken(token)
|
|
928
|
+
let userId = get(user, 'id', '')
|
|
929
|
+
return userId
|
|
930
|
+
},
|
|
931
|
+
useDbOrm: true,
|
|
932
|
+
kpOrm: woItems,
|
|
933
|
+
operOrm: procOrm, //procOrm的輸入為: userId, tableName, methodName, input
|
|
934
|
+
tableNamesExec,
|
|
935
|
+
methodsExec: ['select', 'insert', 'save', 'del'], //mix需於procOrm內註冊以提供
|
|
936
|
+
tableNamesSync,
|
|
937
|
+
kpFunExt: { //接收參數第1個為userId, 之後才是前端給予參數(皆經 wrapErrLog:錯誤 srLog err-key 後 re-reject)
|
|
938
|
+
|
|
939
|
+
getWebInfor: wrapErrLog('getWebInfor', getWebInfor),
|
|
940
|
+
|
|
941
|
+
proxyRequest: wrapErrLog('proxyRequest', (userId, reqSpec) => {
|
|
942
|
+
return proxyRequest(reqSpec)
|
|
943
|
+
}),
|
|
944
|
+
|
|
945
|
+
getApisList: wrapErrLog('getApisList', (userId) => {
|
|
946
|
+
return getApisList(userId)
|
|
947
|
+
}),
|
|
948
|
+
saveApi: wrapErrLog('saveApi', (userId, row) => {
|
|
949
|
+
return saveApi(userId, row)
|
|
950
|
+
}),
|
|
951
|
+
deleteApi: wrapErrLog('deleteApi', (userId, id) => {
|
|
952
|
+
return deleteApi(userId, id)
|
|
953
|
+
}),
|
|
954
|
+
|
|
955
|
+
//統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
|
|
956
|
+
//授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
|
|
957
|
+
getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
|
|
958
|
+
return staEvent(timeLength, timeInterval, { fdLog: logFd })
|
|
959
|
+
}),
|
|
960
|
+
|
|
961
|
+
// getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
|
|
962
|
+
// return getAPIsList(query)
|
|
963
|
+
// },
|
|
964
|
+
// updateApis: (userId, rows) => { //待提供api管理系統界面使用
|
|
965
|
+
// return updateApis(rows)
|
|
966
|
+
// },
|
|
967
|
+
|
|
968
|
+
},
|
|
969
|
+
fnTableTags: 'tableTags-web-api.json',
|
|
970
|
+
})
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
return instWServHapiServer
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
export default WWebApi
|
|
978
978
|
</code></pre>
|
|
979
979
|
</article>
|
|
980
980
|
</section>
|
|
@@ -989,7 +989,7 @@ export default WWebApi
|
|
|
989
989
|
<br class="clear">
|
|
990
990
|
|
|
991
991
|
<footer>
|
|
992
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on
|
|
992
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
993
993
|
</footer>
|
|
994
994
|
|
|
995
995
|
<script>prettyPrint();</script>
|