trustbase-seller-backend 0.4.2 → 0.4.4
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/audit-page-scripts.js +59 -0
- package/package.json +1 -1
- package/routes/sp-applyment.js +13 -1
- package/test-sp-applyment.js +19 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// 全页面 JS 体检: 抓取所有 HTML 页面的内联脚本并逐一编译 (抓 0.4.1 那类模板转义回归)
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const Database = require("better-sqlite3");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
function compileScripts(name, html) {
|
|
8
|
+
const blocks = [...html.matchAll(/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi)];
|
|
9
|
+
if (!blocks.length) { console.log(`-- ${name}: 无内联脚本`); return 0; }
|
|
10
|
+
let bad = 0;
|
|
11
|
+
blocks.forEach((b, i) => {
|
|
12
|
+
try { new Function(b[1]); console.log(`OK ${name} script#${i + 1} (${b[1].length} chars)`); }
|
|
13
|
+
catch (e) {
|
|
14
|
+
bad++;
|
|
15
|
+
console.log(`FAIL ${name} script#${i + 1}: ${e.message}`);
|
|
16
|
+
const lines = b[1].split("\n");
|
|
17
|
+
// 用 node --check 精确定位
|
|
18
|
+
fs.writeFileSync(path.join(__dirname, `audit-${name}-${i + 1}.js`), b[1]);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return bad;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
(async () => {
|
|
25
|
+
let fails = 0;
|
|
26
|
+
const db = new Database(":memory:");
|
|
27
|
+
process.env.SP_ADMIN_API_KEY = process.env.SP_ADMIN_API_KEY || "audit-key";
|
|
28
|
+
|
|
29
|
+
// 1) onboard.html (磁盘直读)
|
|
30
|
+
const onboardHtml = fs.readFileSync(path.join(__dirname, "routes", "onboard.html"), "utf8");
|
|
31
|
+
fails += compileScripts("onboard.html", onboardHtml);
|
|
32
|
+
|
|
33
|
+
// 2) 起 mock server 抓动态页
|
|
34
|
+
const app = express();
|
|
35
|
+
app.use(express.json({ limit: "20mb" }));
|
|
36
|
+
app.use("/api/sp", require("./routes/sp-applyment").createSpApplymentRouter({ db }));
|
|
37
|
+
app.use("/m2m", require("./routes/m2m-viewer").createM2MViewerRouter({ db }));
|
|
38
|
+
const srv = await new Promise((r) => { const s = app.listen(0, () => r(s)); });
|
|
39
|
+
const base = `http://127.0.0.1:${srv.address().port}`;
|
|
40
|
+
const H = { Accept: "text/html" };
|
|
41
|
+
|
|
42
|
+
for (const [name, url] of [
|
|
43
|
+
["sp-config", base + "/api/sp/config"],
|
|
44
|
+
["sp-apply", base + "/api/sp/apply"],
|
|
45
|
+
["m2m-viewer", base + "/m2m"],
|
|
46
|
+
]) {
|
|
47
|
+
try {
|
|
48
|
+
const r = await fetch(url, { headers: H });
|
|
49
|
+
const html = await r.text();
|
|
50
|
+
console.log(`\n== ${name}: HTTP ${r.status} (${html.length} chars)`);
|
|
51
|
+
if (r.status === 200 && html.includes("<")) fails += compileScripts(name, html);
|
|
52
|
+
} catch (e) { console.log(`\n== ${name}: FETCH FAIL ${e.message}`); }
|
|
53
|
+
}
|
|
54
|
+
srv.close();
|
|
55
|
+
|
|
56
|
+
// 3) run-unified.js 里可能还有别的内联 HTML — 搜 res.send 模板
|
|
57
|
+
console.log(`\n== 总计: ${fails} 个脚本块编译失败`);
|
|
58
|
+
process.exit(fails ? 1 : 0);
|
|
59
|
+
})();
|
package/package.json
CHANGED
package/routes/sp-applyment.js
CHANGED
|
@@ -458,6 +458,8 @@ h1{font-size:20px;margin-bottom:4px}
|
|
|
458
458
|
.chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
|
|
459
459
|
.chip{font-size:12px;border-radius:999px;padding:5px 12px;border:1px solid #EAE3D6;background:#FCFAF4;color:#8A857B}
|
|
460
460
|
.chip.ok{background:#F0FDF4;border-color:#BBF7D0;color:#166534}
|
|
461
|
+
.hd{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
|
|
462
|
+
.hd .upbtn{margin-top:6px}
|
|
461
463
|
.box{background:#FCFAF4;border:1px solid #EAE3D6;border-radius:14px;padding:18px;margin-bottom:14px}
|
|
462
464
|
.box h3{font-size:15px;color:#C4633C;margin-bottom:10px}
|
|
463
465
|
label{display:block;font-size:13px;color:#6B655B;margin:10px 0 4px}
|
|
@@ -490,8 +492,10 @@ input:focus,select:focus{outline:none;border-color:#C4633C;box-shadow:0 0 0 3px
|
|
|
490
492
|
</head>
|
|
491
493
|
<body>
|
|
492
494
|
<div class="wrap">
|
|
495
|
+
<div class="hd"><div>
|
|
493
496
|
<h1>服务商配置</h1>
|
|
494
497
|
<div class="sub">TrustBase 服务商运营台 · 应用/名片/状态 一屏三件事</div>
|
|
498
|
+
</div><button class="upbtn" onclick="logout()" title="清除本机保存的管理密钥,下次进入需重新输入">退出登录</button></div>
|
|
495
499
|
|
|
496
500
|
<div id="toast"></div>
|
|
497
501
|
|
|
@@ -562,6 +566,13 @@ function busy(btn,on,txtBusy){
|
|
|
562
566
|
else{btn.disabled=false;btn.textContent=btn.dataset.txt||btn.textContent;}
|
|
563
567
|
}
|
|
564
568
|
|
|
569
|
+
// 退出登录: 清掉本机 localStorage 里的 admin key, 刷新后重新输入 (2026-09-28 xiaomu 实测: 页面无退出入口)
|
|
570
|
+
function logout(){
|
|
571
|
+
localStorage.removeItem('spAdminKey');
|
|
572
|
+
showToast(true,'已退出,即将重新验证密钥');
|
|
573
|
+
setTimeout(function(){location.reload();},700);
|
|
574
|
+
}
|
|
575
|
+
|
|
565
576
|
function render(){
|
|
566
577
|
var c=CFG, comp=c.completeness;
|
|
567
578
|
document.getElementById('chips').innerHTML=
|
|
@@ -1064,7 +1075,8 @@ adminKey(); // 打开即要 key (后续 API 都要带)
|
|
|
1064
1075
|
function createSpApplymentRouter(deps) {
|
|
1065
1076
|
const router = express.Router();
|
|
1066
1077
|
const { db } = deps;
|
|
1067
|
-
ensureApplymentTables(db);
|
|
1078
|
+
ensureApplymentTables(db);
|
|
1079
|
+
ensureSpApps(db); // 0.4.2: 路由创建即建表 — 修 POST /apps 在 GET 之前调用时 "no such table: sp_apps" (node-c 实测)
|
|
1068
1080
|
|
|
1069
1081
|
/** admin 鉴权: env 未配置 → 全部拒绝 (401); 配置了 → header 精确比对 (同 sp-commission 口径) */
|
|
1070
1082
|
function checkAdmin(req, res) {
|
package/test-sp-applyment.js
CHANGED
|
@@ -547,6 +547,22 @@ test("多应用: CRUD + 默认唯一 + 旧配置自动迁移 + /info apps 列表
|
|
|
547
547
|
} finally { srv.close(); }
|
|
548
548
|
});
|
|
549
549
|
|
|
550
|
+
test("回归 0.4.2: 全新库直接 POST /apps (不先 GET) 不能报 no such table — node-c 实测坑", async () => {
|
|
551
|
+
const { srv, base } = await startServer();
|
|
552
|
+
try {
|
|
553
|
+
// startServer 每次新起 :memory: 库; 直接写, 不允许依赖 GET 懒建表
|
|
554
|
+
const r = await fetch(base + "/api/sp/apps", {
|
|
555
|
+
method: "POST", headers: H,
|
|
556
|
+
body: JSON.stringify({ name: "首个应用", appid: "wx_fresh_001", type: "app" }),
|
|
557
|
+
});
|
|
558
|
+
const j = await r.json();
|
|
559
|
+
assert.strictEqual(r.status, 200, JSON.stringify(j));
|
|
560
|
+
assert.strictEqual(j.ok, true);
|
|
561
|
+
assert.strictEqual(j.apps.length, 1);
|
|
562
|
+
assert.strictEqual(j.apps[0].is_default, true, "首个应用自动默认");
|
|
563
|
+
} finally { srv.close(); }
|
|
564
|
+
});
|
|
565
|
+
|
|
550
566
|
test("配置页操作反馈: toast 组件 + 成功/失败文案 + 保存中禁用 (页面断言)", async () => {
|
|
551
567
|
const { srv, base } = await startServer();
|
|
552
568
|
try {
|
|
@@ -563,6 +579,9 @@ test("配置页操作反馈: toast 组件 + 成功/失败文案 + 保存中禁
|
|
|
563
579
|
assert.ok(html.includes("r.json.message"), "失败显示后端 message 原文");
|
|
564
580
|
// 保存中禁用
|
|
565
581
|
assert.ok(html.includes("busy(btn,true)") && html.includes("保存中…"), "保存中禁用+文案");
|
|
582
|
+
// 退出登录入口 (0.4.4): 清 localStorage 密钥 + 刷新
|
|
583
|
+
assert.ok(html.includes("退出登录") && html.includes("function logout()"), "退出登录按钮+函数");
|
|
584
|
+
assert.ok(html.includes("removeItem('spAdminKey')"), "logout 清密钥");
|
|
566
585
|
// 所有 mutating 操作接入: 应用保存/设默认/删除/素材上传/名片保存
|
|
567
586
|
for (const fn of ["saveApp", "setDefault", "delApp", "upFormAsset", "saveProfile"]) {
|
|
568
587
|
assert.ok(html.includes(fn), fn + " 存在");
|