@seasonkoh/webaz 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/README.md +5 -1
  2. package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
  3. package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
  4. package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
  5. package/dist/layer0-foundation/L0-1-database/db.js +65 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
  7. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
  8. package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
  11. package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
  12. package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
  13. package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
  14. package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
  15. package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
  16. package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
  17. package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
  18. package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
  19. package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
  20. package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
  21. package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
  22. package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
  23. package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
  24. package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
  25. package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
  26. package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
  27. package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
  28. package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
  29. package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
  30. package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
  31. package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
  32. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
  33. package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
  34. package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  38. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  39. package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
  40. package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
  41. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  42. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  43. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  44. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  45. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  46. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  47. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  48. package/dist/pwa/acp-feed.js +13 -1
  49. package/dist/pwa/admin-bearer-auth.js +21 -0
  50. package/dist/pwa/contract-fingerprint.js +2 -0
  51. package/dist/pwa/email-delivery.js +127 -0
  52. package/dist/pwa/endpoint-actions.js +5 -1
  53. package/dist/pwa/goal-index.js +8 -8
  54. package/dist/pwa/human-presence.js +62 -0
  55. package/dist/pwa/public/app.js +1485 -283
  56. package/dist/pwa/public/i18n.js +297 -59
  57. package/dist/pwa/public/index.html +1 -0
  58. package/dist/pwa/public/openapi.json +5 -5
  59. package/dist/pwa/public/whitepaper/en/index.html +153 -0
  60. package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
  61. package/dist/pwa/rate-limit.js +22 -0
  62. package/dist/pwa/routes/account-deletion.js +15 -13
  63. package/dist/pwa/routes/addresses.js +10 -9
  64. package/dist/pwa/routes/admin-admins.js +13 -14
  65. package/dist/pwa/routes/admin-analytics.js +109 -69
  66. package/dist/pwa/routes/admin-atomic.js +10 -4
  67. package/dist/pwa/routes/admin-catalog.js +13 -11
  68. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  69. package/dist/pwa/routes/admin-events.js +5 -3
  70. package/dist/pwa/routes/admin-health.js +2 -1
  71. package/dist/pwa/routes/admin-moderation.js +50 -29
  72. package/dist/pwa/routes/admin-ops.js +35 -23
  73. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  74. package/dist/pwa/routes/admin-reports.js +23 -21
  75. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  76. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  77. package/dist/pwa/routes/admin-users-query.js +65 -53
  78. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  79. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  80. package/dist/pwa/routes/admin-wallet-ops.js +32 -7
  81. package/dist/pwa/routes/agent-buy.js +46 -22
  82. package/dist/pwa/routes/agent-governance.js +52 -56
  83. package/dist/pwa/routes/ai.js +7 -5
  84. package/dist/pwa/routes/analytics.js +43 -41
  85. package/dist/pwa/routes/anchors.js +19 -20
  86. package/dist/pwa/routes/announcements.js +13 -13
  87. package/dist/pwa/routes/arbitrator.js +97 -31
  88. package/dist/pwa/routes/auction.js +157 -116
  89. package/dist/pwa/routes/auth-login.js +6 -4
  90. package/dist/pwa/routes/auth-read.js +21 -10
  91. package/dist/pwa/routes/auth-register.js +111 -26
  92. package/dist/pwa/routes/auth-sessions.js +12 -11
  93. package/dist/pwa/routes/blocklist.js +16 -15
  94. package/dist/pwa/routes/build-feedback.js +10 -9
  95. package/dist/pwa/routes/build-reputation.js +6 -2
  96. package/dist/pwa/routes/build-tasks.js +45 -13
  97. package/dist/pwa/routes/buyer-feeds.js +27 -25
  98. package/dist/pwa/routes/cart.js +16 -15
  99. package/dist/pwa/routes/charity.js +212 -150
  100. package/dist/pwa/routes/chat.js +42 -43
  101. package/dist/pwa/routes/checkin-tasks.js +10 -9
  102. package/dist/pwa/routes/checkout-helpers.js +12 -10
  103. package/dist/pwa/routes/claim-initiators.js +34 -14
  104. package/dist/pwa/routes/claim-verify.js +86 -53
  105. package/dist/pwa/routes/claim-voting.js +43 -18
  106. package/dist/pwa/routes/contribution-identity.js +164 -0
  107. package/dist/pwa/routes/contribution-score.js +19 -0
  108. package/dist/pwa/routes/coupons.js +19 -16
  109. package/dist/pwa/routes/dashboards.js +18 -16
  110. package/dist/pwa/routes/dispute-cases.js +25 -24
  111. package/dist/pwa/routes/disputes-read.js +45 -51
  112. package/dist/pwa/routes/disputes-write.js +124 -61
  113. package/dist/pwa/routes/evidence.js +9 -9
  114. package/dist/pwa/routes/external-anchors.js +13 -12
  115. package/dist/pwa/routes/feedback.js +29 -33
  116. package/dist/pwa/routes/flash-sales.js +18 -16
  117. package/dist/pwa/routes/follows.js +25 -24
  118. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  119. package/dist/pwa/routes/governance-onboarding.js +70 -59
  120. package/dist/pwa/routes/group-buys.js +22 -22
  121. package/dist/pwa/routes/growth.js +34 -31
  122. package/dist/pwa/routes/import-product.js +12 -10
  123. package/dist/pwa/routes/kyc.js +9 -8
  124. package/dist/pwa/routes/leaderboard.js +20 -18
  125. package/dist/pwa/routes/listings.js +23 -22
  126. package/dist/pwa/routes/logistics.js +10 -8
  127. package/dist/pwa/routes/manifests.js +27 -27
  128. package/dist/pwa/routes/me-data.js +23 -21
  129. package/dist/pwa/routes/notifications.js +7 -6
  130. package/dist/pwa/routes/offers.js +30 -12
  131. package/dist/pwa/routes/orders-action.js +51 -29
  132. package/dist/pwa/routes/orders-create.js +75 -20
  133. package/dist/pwa/routes/orders-read.js +21 -20
  134. package/dist/pwa/routes/p2p-products.js +30 -18
  135. package/dist/pwa/routes/payments-governance.js +61 -56
  136. package/dist/pwa/routes/peers.js +9 -8
  137. package/dist/pwa/routes/pin-receipts.js +13 -13
  138. package/dist/pwa/routes/products-aliases.js +12 -10
  139. package/dist/pwa/routes/products-claims.js +36 -17
  140. package/dist/pwa/routes/products-create.js +53 -38
  141. package/dist/pwa/routes/products-crud.js +17 -16
  142. package/dist/pwa/routes/products-links.js +49 -26
  143. package/dist/pwa/routes/products-list.js +6 -4
  144. package/dist/pwa/routes/products-meta.js +40 -39
  145. package/dist/pwa/routes/products-update.js +19 -5
  146. package/dist/pwa/routes/profile-credentials.js +20 -19
  147. package/dist/pwa/routes/profile-identity.js +14 -13
  148. package/dist/pwa/routes/profile-location.js +7 -6
  149. package/dist/pwa/routes/profile-placement.js +20 -19
  150. package/dist/pwa/routes/profile-prefs.js +11 -11
  151. package/dist/pwa/routes/promoter.js +58 -66
  152. package/dist/pwa/routes/public-build-tasks.js +19 -0
  153. package/dist/pwa/routes/public-utils.js +108 -46
  154. package/dist/pwa/routes/push.js +16 -15
  155. package/dist/pwa/routes/ratings.js +92 -32
  156. package/dist/pwa/routes/recover-key.js +66 -26
  157. package/dist/pwa/routes/referral.js +37 -52
  158. package/dist/pwa/routes/reputation.js +3 -2
  159. package/dist/pwa/routes/returns.js +76 -73
  160. package/dist/pwa/routes/reviews.js +41 -18
  161. package/dist/pwa/routes/rewards-apply.js +16 -15
  162. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  163. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  164. package/dist/pwa/routes/rfqs.js +163 -85
  165. package/dist/pwa/routes/search.js +16 -14
  166. package/dist/pwa/routes/secondhand.js +25 -22
  167. package/dist/pwa/routes/seller-quota.js +24 -26
  168. package/dist/pwa/routes/share-redirects.js +60 -55
  169. package/dist/pwa/routes/shareables-interactions.js +34 -35
  170. package/dist/pwa/routes/shareables.js +55 -51
  171. package/dist/pwa/routes/shop-referral.js +58 -0
  172. package/dist/pwa/routes/shops.js +25 -20
  173. package/dist/pwa/routes/signaling.js +10 -9
  174. package/dist/pwa/routes/skill-market.js +16 -16
  175. package/dist/pwa/routes/skills.js +15 -14
  176. package/dist/pwa/routes/snf.js +14 -13
  177. package/dist/pwa/routes/tags.js +10 -9
  178. package/dist/pwa/routes/task-proposals.js +121 -0
  179. package/dist/pwa/routes/trial.js +72 -52
  180. package/dist/pwa/routes/trusted-kpi.js +20 -18
  181. package/dist/pwa/routes/url-claim.js +67 -28
  182. package/dist/pwa/routes/users-public.js +62 -70
  183. package/dist/pwa/routes/variants.js +12 -13
  184. package/dist/pwa/routes/verifier-user.js +61 -21
  185. package/dist/pwa/routes/verify-tasks.js +49 -25
  186. package/dist/pwa/routes/waitlist.js +16 -15
  187. package/dist/pwa/routes/wallet-read.js +75 -37
  188. package/dist/pwa/routes/wallet-write.js +12 -9
  189. package/dist/pwa/routes/webauthn.js +25 -26
  190. package/dist/pwa/routes/webhooks.js +26 -26
  191. package/dist/pwa/routes/welcome.js +45 -50
  192. package/dist/pwa/routes/wishlist-qa.js +29 -32
  193. package/dist/pwa/server.js +304 -90
  194. package/dist/version.js +1 -1
  195. package/package.json +76 -3
@@ -1,17 +1,18 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminAdminsRoutes(app, deps) {
2
3
  const { db, generateId, requireAdmin, requireRootAdmin, isRootAdmin, getAdminPermissions, ADMIN_PERMISSIONS } = deps;
3
4
  // GET 全部 admin 列表
4
- app.get('/api/admin/admins', (req, res) => {
5
+ app.get('/api/admin/admins', async (req, res) => {
5
6
  const me = requireAdmin(req, res);
6
7
  if (!me)
7
8
  return;
8
- const items = db.prepare(`
9
+ const items = await dbAll(`
9
10
  SELECT id, name, handle, role, admin_type, admin_scope, admin_permissions, email, created_at,
10
11
  (SELECT MAX(created_at) FROM admin_audit_log WHERE admin_id = users.id) AS last_action_at
11
12
  FROM users
12
13
  WHERE role = 'admin' OR (roles IS NOT NULL AND roles LIKE '%admin%')
13
14
  ORDER BY admin_type DESC, created_at ASC
14
- `).all();
15
+ `, []);
15
16
  // 普通 admin 视角下 email 脱敏
16
17
  const masked = items.map((u) => {
17
18
  const enriched = { ...u, admin_permissions: u.admin_type === 'root' ? ['all'] : (() => { try {
@@ -31,7 +32,7 @@ export function registerAdminAdminsRoutes(app, deps) {
31
32
  });
32
33
  });
33
34
  // POST 创建 admin(仅 root)
34
- app.post('/api/admin/admins', (req, res) => {
35
+ app.post('/api/admin/admins', async (req, res) => {
35
36
  const root = requireRootAdmin(req, res);
36
37
  if (!root)
37
38
  return;
@@ -60,7 +61,7 @@ export function registerAdminAdminsRoutes(app, deps) {
60
61
  return void res.json({ error: 'admin_type 无效' });
61
62
  if (!['global', 'china', 'us', 'eu', 'india', 'singapore', 'global_north'].includes(adminScope))
62
63
  return void res.json({ error: 'admin_scope 无效' });
63
- const target = db.prepare(`SELECT id, role, roles, admin_type FROM users WHERE id = ?`).get(targetUserId);
64
+ const target = await dbOne(`SELECT id, role, roles, admin_type FROM users WHERE id = ?`, [targetUserId]);
64
65
  if (!target)
65
66
  return void res.json({ error: '用户不存在' });
66
67
  if (target.admin_type)
@@ -87,7 +88,7 @@ export function registerAdminAdminsRoutes(app, deps) {
87
88
  res.json({ ok: true, user_id: targetUserId, admin_type: adminType, admin_scope: adminScope, admin_permissions: adminPerms });
88
89
  });
89
90
  // PATCH 更新权限(root only)
90
- app.patch('/api/admin/admins/:id/permissions', (req, res) => {
91
+ app.patch('/api/admin/admins/:id/permissions', async (req, res) => {
91
92
  const root = requireRootAdmin(req, res);
92
93
  if (!root)
93
94
  return;
@@ -102,33 +103,31 @@ export function registerAdminAdminsRoutes(app, deps) {
102
103
  for (const p of adminPerms)
103
104
  if (!validPerms.has(p))
104
105
  return void res.json({ error: `权限 "${p}" 无效` });
105
- const target = db.prepare(`SELECT id, admin_type FROM users WHERE id = ?`).get(targetId);
106
+ const target = await dbOne(`SELECT id, admin_type FROM users WHERE id = ?`, [targetId]);
106
107
  if (!target?.admin_type)
107
108
  return void res.json({ error: '该用户不是 admin' });
108
109
  if (target.admin_type === 'root')
109
110
  return void res.json({ error: 'root admin 权限不可手动调整(永远是 all)' });
110
111
  if (adminPerms.length === 0)
111
112
  return void res.json({ error: '至少保留一项权限' });
112
- db.prepare(`UPDATE users SET admin_permissions = ?${adminScope ? ', admin_scope = ?' : ''} WHERE id = ?`)
113
- .run(JSON.stringify(adminPerms), ...(adminScope ? [adminScope, targetId] : [targetId]));
114
- db.prepare(`INSERT INTO admin_audit_log (id, admin_id, action, target_type, target_id, detail) VALUES (?,?,?,?,?,?)`)
115
- .run(generateId('audit'), root.id, 'admin_update_perms', 'user', targetId, JSON.stringify({ admin_permissions: adminPerms, admin_scope: adminScope }));
113
+ await dbRun(`UPDATE users SET admin_permissions = ?${adminScope ? ', admin_scope = ?' : ''} WHERE id = ?`, [JSON.stringify(adminPerms), ...(adminScope ? [adminScope, targetId] : [targetId])]);
114
+ await dbRun(`INSERT INTO admin_audit_log (id, admin_id, action, target_type, target_id, detail) VALUES (?,?,?,?,?,?)`, [generateId('audit'), root.id, 'admin_update_perms', 'user', targetId, JSON.stringify({ admin_permissions: adminPerms, admin_scope: adminScope })]);
116
115
  res.json({ ok: true, admin_permissions: adminPerms, admin_scope: adminScope });
117
116
  });
118
117
  // DELETE 撤销 admin(root only;不能撤自己;至少保留 1 个 root)
119
- app.delete('/api/admin/admins/:id', (req, res) => {
118
+ app.delete('/api/admin/admins/:id', async (req, res) => {
120
119
  const root = requireRootAdmin(req, res);
121
120
  if (!root)
122
121
  return;
123
122
  const targetId = req.params.id;
124
123
  if (targetId === root.id)
125
124
  return void res.json({ error: '不能撤销自己' });
126
- const target = db.prepare(`SELECT id, name, admin_type FROM users WHERE id = ?`).get(targetId);
125
+ const target = await dbOne(`SELECT id, name, admin_type FROM users WHERE id = ?`, [targetId]);
127
126
  if (!target || !target.admin_type)
128
127
  return void res.json({ error: '该用户不是 admin' });
129
128
  // 保护:至少保留 1 个 root
130
129
  if (target.admin_type === 'root') {
131
- const rootCount = db.prepare(`SELECT COUNT(1) as n FROM users WHERE admin_type = 'root'`).get().n;
130
+ const rootCount = (await dbOne(`SELECT COUNT(1) as n FROM users WHERE admin_type = 'root'`, [])).n;
132
131
  if (rootCount <= 1)
133
132
  return void res.json({ error: '至少保留 1 个 root admin,不可撤销最后一个' });
134
133
  }
@@ -1,36 +1,38 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminAnalyticsRoutes(app, deps) {
2
- const { db, adminAuth, requireAdmin, requireRootAdmin, getProtocolParam, INTERNAL_AUDITOR_ID } = deps;
3
- app.get('/api/admin/usage', (req, res) => {
3
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
+ const { adminAuth, requireAdmin, requireRootAdmin, getProtocolParam, INTERNAL_AUDITOR_ID } = deps;
5
+ app.get('/api/admin/usage', async (req, res) => {
4
6
  if (!adminAuth(req, res))
5
7
  return;
6
- const total = db.prepare(`SELECT COUNT(*) as n FROM mcp_tool_calls`).get();
7
- const total24h = db.prepare(`SELECT COUNT(*) as n FROM mcp_tool_calls WHERE ts > datetime('now','-1 day')`).get();
8
- const total7d = db.prepare(`SELECT COUNT(*) as n FROM mcp_tool_calls WHERE ts > datetime('now','-7 day')`).get();
9
- const totalUsers = db.prepare(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL`).get();
10
- const wau7d = db.prepare(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL AND ts > datetime('now','-7 day')`).get();
11
- const dau24h = db.prepare(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL AND ts > datetime('now','-1 day')`).get();
12
- const byTool = db.prepare(`
8
+ const total = (await dbOne(`SELECT COUNT(*) as n FROM mcp_tool_calls`));
9
+ const total24h = (await dbOne(`SELECT COUNT(*) as n FROM mcp_tool_calls WHERE ts > datetime('now','-1 day')`));
10
+ const total7d = (await dbOne(`SELECT COUNT(*) as n FROM mcp_tool_calls WHERE ts > datetime('now','-7 day')`));
11
+ const totalUsers = (await dbOne(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL`));
12
+ const wau7d = (await dbOne(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL AND ts > datetime('now','-7 day')`));
13
+ const dau24h = (await dbOne(`SELECT COUNT(DISTINCT user_id_hash) as n FROM mcp_tool_calls WHERE user_id_hash IS NOT NULL AND ts > datetime('now','-1 day')`));
14
+ const byTool = await dbAll(`
13
15
  SELECT tool_name,
14
16
  COUNT(*) AS calls,
15
17
  SUM(CASE WHEN outcome='error' THEN 1 ELSE 0 END) AS errors,
16
18
  ROUND(AVG(latency_ms), 0) AS avg_latency_ms
17
19
  FROM mcp_tool_calls WHERE ts > datetime('now','-7 day')
18
20
  GROUP BY tool_name ORDER BY calls DESC
19
- `).all();
20
- const byDay = db.prepare(`
21
+ `);
22
+ const byDay = await dbAll(`
21
23
  SELECT substr(ts, 1, 10) AS day,
22
24
  COUNT(*) AS calls,
23
25
  COUNT(DISTINCT user_id_hash) AS distinct_users
24
26
  FROM mcp_tool_calls WHERE ts > datetime('now','-14 day')
25
27
  GROUP BY day ORDER BY day
26
- `).all();
27
- const byVersion = db.prepare(`
28
+ `);
29
+ const byVersion = await dbAll(`
28
30
  SELECT server_version,
29
31
  COUNT(*) AS calls,
30
32
  COUNT(DISTINCT user_id_hash) AS distinct_users
31
33
  FROM mcp_tool_calls WHERE ts > datetime('now','-7 day')
32
34
  GROUP BY server_version ORDER BY calls DESC
33
- `).all();
35
+ `);
34
36
  res.json({
35
37
  summary: {
36
38
  total_calls: total.n,
@@ -45,24 +47,23 @@ export function registerAdminAnalyticsRoutes(app, deps) {
45
47
  by_version_7d: byVersion,
46
48
  });
47
49
  });
48
- app.get('/api/admin/auditor', (req, res) => {
50
+ app.get('/api/admin/auditor', async (req, res) => {
49
51
  const user = requireRootAdmin(req, res);
50
52
  if (!user)
51
53
  return;
52
- const auditor = db.prepare('SELECT id, name, api_key, created_at FROM users WHERE id = ?')
53
- .get(INTERNAL_AUDITOR_ID);
54
+ const auditor = await dbOne('SELECT id, name, api_key, created_at FROM users WHERE id = ?', [INTERNAL_AUDITOR_ID]);
54
55
  if (!auditor)
55
56
  return void res.json({ error: '内部审核账号未初始化' });
56
57
  res.json({ id: auditor.id, name: auditor.name, api_key: auditor.api_key });
57
58
  });
58
- app.get('/api/admin/finance/monthly', (req, res) => {
59
+ app.get('/api/admin/finance/monthly', async (req, res) => {
59
60
  const admin = requireAdmin(req, res);
60
61
  if (!admin)
61
62
  return;
62
63
  const months = Math.max(3, Math.min(24, Number(req.query.months) || 12));
63
64
  const feeShop = getProtocolParam('protocol_fee_rate_shop', 0.02);
64
65
  const feeSecondhand = getProtocolParam('protocol_fee_rate_secondhand', 0.01);
65
- const orderRows = db.prepare(`
66
+ const orderRows = await dbAll(`
66
67
  SELECT strftime('%Y-%m', created_at) as ym,
67
68
  COALESCE(SUM(CASE WHEN source = 'secondhand' THEN total_amount * ? ELSE total_amount * ? END), 0) as fee,
68
69
  COALESCE(SUM(total_amount), 0) as gmv,
@@ -71,13 +72,13 @@ export function registerAdminAnalyticsRoutes(app, deps) {
71
72
  WHERE status = 'completed'
72
73
  AND created_at > datetime('now', '-' || ? || ' months')
73
74
  GROUP BY ym ORDER BY ym DESC
74
- `).all(feeSecondhand, feeShop, months);
75
- const rewardRows = db.prepare(`
75
+ `, [feeSecondhand, feeShop, months]);
76
+ const rewardRows = await dbAll(`
76
77
  SELECT strftime('%Y-%m', created_at) as ym, COALESCE(SUM(amount), 0) as rewards, COUNT(*) as count
77
78
  FROM platform_reward_log
78
79
  WHERE created_at > datetime('now', '-' || ? || ' months')
79
80
  GROUP BY ym ORDER BY ym DESC
80
- `).all(months);
81
+ `, [months]);
81
82
  const byMonth = new Map();
82
83
  for (const o of orderRows)
83
84
  byMonth.set(o.ym, { ym: o.ym, fee: o.fee, gmv: o.gmv, orders: o.orders_count, rewards: 0, reward_count: 0 });
@@ -94,7 +95,7 @@ export function registerAdminAnalyticsRoutes(app, deps) {
94
95
  const totalFee = rows.reduce((s, r) => s + r.fee, 0);
95
96
  const totalRewards = rows.reduce((s, r) => s + r.rewards, 0);
96
97
  const totalGmv = rows.reduce((s, r) => s + r.gmv, 0);
97
- const sysWallet = db.prepare("SELECT balance FROM wallets WHERE user_id = 'sys_protocol'").get();
98
+ const sysWallet = await dbOne("SELECT balance FROM wallets WHERE user_id = 'sys_protocol'");
98
99
  res.json({
99
100
  months,
100
101
  fee_rate_shop: feeShop,
@@ -109,23 +110,23 @@ export function registerAdminAnalyticsRoutes(app, deps) {
109
110
  },
110
111
  });
111
112
  });
112
- app.get('/api/admin/protocol-kpi', (req, res) => {
113
+ app.get('/api/admin/protocol-kpi', async (req, res) => {
113
114
  const admin = requireAdmin(req, res);
114
115
  if (!admin)
115
116
  return;
116
- const windowCounts = (label, days) => {
117
+ const windowCounts = async (label, days) => {
117
118
  const t = `datetime('now','-${days} days')`;
118
- const orders = db.prepare(`SELECT COUNT(*) as n, COALESCE(SUM(total_amount),0) as gmv FROM orders WHERE created_at > ${t}`).get();
119
- const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE status='completed' AND created_at > ${t}`).get().n;
120
- const disputes = db.prepare(`SELECT COUNT(*) as n FROM disputes WHERE created_at > ${t}`).get().n;
121
- const refunds = db.prepare(`SELECT COUNT(*) as n FROM return_requests WHERE status='refunded' AND created_at > ${t}`).get().n;
122
- const newUsers = db.prepare(`SELECT COUNT(*) as n FROM users WHERE created_at > ${t} AND id NOT IN ('sys_protocol', ?)`).get(INTERNAL_AUDITOR_ID).n;
119
+ const orders = (await dbOne(`SELECT COUNT(*) as n, COALESCE(SUM(total_amount),0) as gmv FROM orders WHERE created_at > ${t}`));
120
+ const completed = (await dbOne(`SELECT COUNT(*) as n FROM orders WHERE status='completed' AND created_at > ${t}`)).n;
121
+ const disputes = (await dbOne(`SELECT COUNT(*) as n FROM disputes WHERE created_at > ${t}`)).n;
122
+ const refunds = (await dbOne(`SELECT COUNT(*) as n FROM return_requests WHERE status='refunded' AND created_at > ${t}`)).n;
123
+ const newUsers = (await dbOne(`SELECT COUNT(*) as n FROM users WHERE created_at > ${t} AND id NOT IN ('sys_protocol', ?)`, [INTERNAL_AUDITOR_ID])).n;
123
124
  return { label, days, orders: orders.n, gmv: orders.gmv, completed, disputes, refunds, new_users: newUsers,
124
125
  dispute_rate: orders.n > 0 ? disputes / orders.n : 0,
125
126
  refund_rate: completed > 0 ? refunds / completed : 0,
126
127
  };
127
128
  };
128
- const dauProxy = db.prepare(`
129
+ const dauProxy = (await dbOne(`
129
130
  SELECT COUNT(DISTINCT u_id) as n FROM (
130
131
  SELECT buyer_id as u_id FROM orders WHERE created_at > datetime('now', '-1 day')
131
132
  UNION SELECT seller_id FROM orders WHERE created_at > datetime('now', '-1 day')
@@ -133,8 +134,8 @@ export function registerAdminAnalyticsRoutes(app, deps) {
133
134
  UNION SELECT user_id FROM daily_checkins WHERE checkin_date >= date('now', '-1 day')
134
135
  UNION SELECT user_id FROM feedback_tickets WHERE created_at > datetime('now', '-1 day')
135
136
  )
136
- `).get().n;
137
- const mauProxy = db.prepare(`
137
+ `)).n;
138
+ const mauProxy = (await dbOne(`
138
139
  SELECT COUNT(DISTINCT u_id) as n FROM (
139
140
  SELECT buyer_id as u_id FROM orders WHERE created_at > datetime('now', '-30 days')
140
141
  UNION SELECT seller_id FROM orders WHERE created_at > datetime('now', '-30 days')
@@ -142,8 +143,8 @@ export function registerAdminAnalyticsRoutes(app, deps) {
142
143
  UNION SELECT user_id FROM daily_checkins WHERE checkin_date >= date('now', '-30 days')
143
144
  UNION SELECT user_id FROM feedback_tickets WHERE created_at > datetime('now', '-30 days')
144
145
  )
145
- `).get().n;
146
- const userTotals = db.prepare(`
146
+ `)).n;
147
+ const userTotals = (await dbOne(`
147
148
  SELECT
148
149
  SUM(CASE WHEN role='buyer' THEN 1 ELSE 0 END) as buyers,
149
150
  SUM(CASE WHEN role='seller' THEN 1 ELSE 0 END) as sellers,
@@ -153,26 +154,26 @@ export function registerAdminAnalyticsRoutes(app, deps) {
153
154
  SUM(CASE WHEN role='admin' THEN 1 ELSE 0 END) as admins,
154
155
  COUNT(*) as total
155
156
  FROM users WHERE id NOT IN ('sys_protocol', ?)
156
- `).get(INTERNAL_AUDITOR_ID);
157
- const sysWallet = db.prepare("SELECT balance FROM wallets WHERE user_id = 'sys_protocol'").get();
158
- const totalEscrowed = db.prepare("SELECT COALESCE(SUM(escrowed),0) as t FROM wallets").get().t;
159
- const totalStaked = db.prepare("SELECT COALESCE(SUM(staked),0) as t FROM wallets").get().t;
160
- const platformRewards = db.prepare("SELECT COALESCE(SUM(amount),0) as t FROM platform_reward_log").get().t;
161
- const platformRewardsToday = db.prepare("SELECT COALESCE(SUM(amount),0) as t FROM platform_reward_log WHERE created_at > datetime('now','-1 day')").get().t;
162
- const products = db.prepare("SELECT COUNT(*) as n, SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active FROM products").get();
163
- const ratings = db.prepare("SELECT COUNT(*) as n FROM order_ratings").get().n;
164
- const subs = db.prepare("SELECT COUNT(*) as n FROM push_subscriptions WHERE enabled=1").get().n;
165
- const trustOpen = db.prepare(`
157
+ `, [INTERNAL_AUDITOR_ID]));
158
+ const sysWallet = await dbOne("SELECT balance FROM wallets WHERE user_id = 'sys_protocol'");
159
+ const totalEscrowed = (await dbOne("SELECT COALESCE(SUM(escrowed),0) as t FROM wallets")).t;
160
+ const totalStaked = (await dbOne("SELECT COALESCE(SUM(staked),0) as t FROM wallets")).t;
161
+ const platformRewards = (await dbOne("SELECT COALESCE(SUM(amount),0) as t FROM platform_reward_log")).t;
162
+ const platformRewardsToday = (await dbOne("SELECT COALESCE(SUM(amount),0) as t FROM platform_reward_log WHERE created_at > datetime('now','-1 day')")).t;
163
+ const products = (await dbOne("SELECT COUNT(*) as n, SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) as active FROM products"));
164
+ const ratings = (await dbOne("SELECT COUNT(*) as n FROM order_ratings")).n;
165
+ const subs = (await dbOne("SELECT COUNT(*) as n FROM push_subscriptions WHERE enabled=1")).n;
166
+ const trustOpen = (await dbOne(`
166
167
  SELECT
167
168
  (SELECT COUNT(*) FROM disputes WHERE status IN ('open','in_review')) as disputes_open,
168
169
  (SELECT COUNT(*) FROM feedback_tickets WHERE status IN ('open','in_progress')) as feedback_open,
169
170
  (SELECT COUNT(*) FROM return_requests WHERE status='pending') as returns_pending
170
- `).get();
171
+ `));
171
172
  res.json({
172
173
  activity: {
173
174
  dau_proxy: dauProxy,
174
175
  mau_proxy: mauProxy,
175
- windows: [windowCounts('24h', 1), windowCounts('7d', 7), windowCounts('30d', 30)],
176
+ windows: await Promise.all([windowCounts('24h', 1), windowCounts('7d', 7), windowCounts('30d', 30)]),
176
177
  },
177
178
  users: userTotals,
178
179
  finance: {
@@ -191,35 +192,35 @@ export function registerAdminAnalyticsRoutes(app, deps) {
191
192
  trust_open: trustOpen,
192
193
  });
193
194
  });
194
- app.get('/api/admin/dashboard', (req, res) => {
195
+ app.get('/api/admin/dashboard', async (req, res) => {
195
196
  const admin = requireAdmin(req, res);
196
197
  if (!admin)
197
198
  return;
198
- const u = db.prepare("SELECT COUNT(*) as n FROM users WHERE id NOT IN ('sys_protocol', ?)").get(INTERNAL_AUDITOR_ID);
199
- const sellers = db.prepare("SELECT COUNT(*) as n FROM users WHERE role = 'seller' AND id NOT IN ('sys_protocol', ?)").get(INTERNAL_AUDITOR_ID);
200
- const active = db.prepare("SELECT COUNT(*) as n FROM products WHERE status = 'active'").get();
201
- const o24 = db.prepare("SELECT COUNT(*) as n, COALESCE(SUM(total_amount),0) as gmv FROM orders WHERE created_at > datetime('now','-1 day')").get();
202
- const dOpen = db.prepare("SELECT COUNT(*) as n FROM disputes WHERE status IN ('open','in_review')").get();
203
- const vOpen = db.prepare("SELECT COUNT(*) as n FROM verify_tasks WHERE status IN ('open','code_issued')").get();
204
- const locked = db.prepare("SELECT COALESCE(SUM(staked + escrowed),0) as t FROM wallets").get();
205
- const sus = db.prepare("SELECT COUNT(*) as n FROM user_moderation WHERE suspended = 1").get();
206
- const verifierApps = db.prepare("SELECT COUNT(*) as n FROM verifier_applications WHERE status = 'pending'").get();
207
- const verifierAppeals = db.prepare("SELECT COUNT(*) as n FROM verifier_appeals WHERE status = 'pending'").get();
208
- const quotaApps = db.prepare("SELECT COUNT(*) as n FROM quota_increase_applications WHERE status = 'pending'").get();
209
- const listingPaused = db.prepare("SELECT COUNT(*) as n FROM users WHERE listing_paused = 1").get();
210
- const activeVerifiers = db.prepare(`
199
+ const u = (await dbOne("SELECT COUNT(*) as n FROM users WHERE id NOT IN ('sys_protocol', ?)", [INTERNAL_AUDITOR_ID]));
200
+ const sellers = (await dbOne("SELECT COUNT(*) as n FROM users WHERE role = 'seller' AND id NOT IN ('sys_protocol', ?)", [INTERNAL_AUDITOR_ID]));
201
+ const active = (await dbOne("SELECT COUNT(*) as n FROM products WHERE status = 'active'"));
202
+ const o24 = (await dbOne("SELECT COUNT(*) as n, COALESCE(SUM(total_amount),0) as gmv FROM orders WHERE created_at > datetime('now','-1 day')"));
203
+ const dOpen = (await dbOne("SELECT COUNT(*) as n FROM disputes WHERE status IN ('open','in_review')"));
204
+ const vOpen = (await dbOne("SELECT COUNT(*) as n FROM verify_tasks WHERE status IN ('open','code_issued')"));
205
+ const locked = (await dbOne("SELECT COALESCE(SUM(staked + escrowed),0) as t FROM wallets"));
206
+ const sus = (await dbOne("SELECT COUNT(*) as n FROM user_moderation WHERE suspended = 1"));
207
+ const verifierApps = (await dbOne("SELECT COUNT(*) as n FROM verifier_applications WHERE status = 'pending'"));
208
+ const verifierAppeals = (await dbOne("SELECT COUNT(*) as n FROM verifier_appeals WHERE status = 'pending'"));
209
+ const quotaApps = (await dbOne("SELECT COUNT(*) as n FROM quota_increase_applications WHERE status = 'pending'"));
210
+ const listingPaused = (await dbOne("SELECT COUNT(*) as n FROM users WHERE listing_paused = 1"));
211
+ const activeVerifiers = (await dbOne(`
211
212
  SELECT COUNT(*) as n FROM verifier_whitelist vw
212
213
  LEFT JOIN verifier_stats vs ON vs.user_id = vw.user_id
213
214
  WHERE (vw.cooldown_until IS NULL OR vw.cooldown_until < datetime('now'))
214
215
  AND (vs.suspended_until IS NULL OR vs.suspended_until < datetime('now'))
215
- `).get();
216
- const tokenomics = (() => {
217
- const gf = db.prepare("SELECT pool_balance, total_scores_pending, current_n, last_settled_at FROM global_fund WHERE id=1").get();
218
- const mb = db.prepare("SELECT balance FROM management_bonus_pool WHERE id=1").get();
219
- const pendingLedger = db.prepare("SELECT COUNT(*) as n FROM pv_ledger WHERE processed = 0").get().n;
220
- const commCount = db.prepare("SELECT COUNT(*) as n, COALESCE(SUM(amount),0) as t FROM commission_records").get();
221
- const dirtyUsers = db.prepare("SELECT COUNT(*) as n FROM users WHERE pv_dirty_at IS NOT NULL").get().n;
222
- const matchedTotal = db.prepare("SELECT COUNT(*) as n, COALESCE(SUM(waz_amount),0) as w FROM binary_score_records WHERE settled_at IS NOT NULL").get();
216
+ `));
217
+ const tokenomics = await (async () => {
218
+ const gf = await dbOne("SELECT pool_balance, total_scores_pending, current_n, last_settled_at FROM global_fund WHERE id=1");
219
+ const mb = await dbOne("SELECT balance FROM management_bonus_pool WHERE id=1");
220
+ const pendingLedger = (await dbOne("SELECT COUNT(*) as n FROM pv_ledger WHERE processed = 0")).n;
221
+ const commCount = (await dbOne("SELECT COUNT(*) as n, COALESCE(SUM(amount),0) as t FROM commission_records"));
222
+ const dirtyUsers = (await dbOne("SELECT COUNT(*) as n FROM users WHERE pv_dirty_at IS NOT NULL")).n;
223
+ const matchedTotal = (await dbOne("SELECT COUNT(*) as n, COALESCE(SUM(waz_amount),0) as w FROM binary_score_records WHERE settled_at IS NOT NULL"));
223
224
  return {
224
225
  pool_balance: Number(gf?.pool_balance ?? 0),
225
226
  scores_pending: Number(gf?.total_scores_pending ?? 0),
@@ -250,4 +251,43 @@ export function registerAdminAnalyticsRoutes(app, deps) {
250
251
  tokenomics,
251
252
  });
252
253
  });
254
+ // RFC-002 rewards opt-in 生命周期监控(#937 A8)— 申请流 / 佣金 escrow / consent 版本漂移。
255
+ // 之前这三张表(rewards_applications / pending_commission_escrow / rewards_consent_texts)只有
256
+ // 引擎 cron 读写,无 admin 监控视图;上线后需盯:opt-in 申请量、escrow 待兑付/将到期、
257
+ // 以及"在旧 major consent 上仍 opted-in"= 下次 auto_downgrade cron 的降级候选。
258
+ app.get('/api/admin/rewards-health', async (req, res) => {
259
+ const admin = requireAdmin(req, res);
260
+ if (!admin)
261
+ return;
262
+ // 1. 申请流:按 action 计数 + 当前 opted-in 用户数 + 最近 20 条
263
+ const appsByAction = await dbAll(`SELECT action, COUNT(*) AS n FROM rewards_applications GROUP BY action ORDER BY n DESC`);
264
+ const optedIn = (await dbOne(`SELECT COUNT(*) AS n FROM users WHERE rewards_opted_in = 1`)).n;
265
+ const recentApps = await dbAll(`SELECT id, user_id, action, consent_version, verification_method, created_at
266
+ FROM rewards_applications ORDER BY created_at DESC LIMIT 20`);
267
+ // 2. 佣金 escrow:按 status 计数+金额、待兑付按 attribution_path、24h 内将到期
268
+ const nowSec = Math.floor(Date.now() / 1000);
269
+ const escrowByStatus = await dbAll(`SELECT status, COUNT(*) AS n, COALESCE(SUM(amount),0) AS total
270
+ FROM pending_commission_escrow GROUP BY status`);
271
+ const escrowPendingByPath = await dbAll(`SELECT attribution_path, COUNT(*) AS n, COALESCE(SUM(amount),0) AS total
272
+ FROM pending_commission_escrow WHERE status='pending' GROUP BY attribution_path`);
273
+ const expiringSoon = (await dbOne(`SELECT COUNT(*) AS n, COALESCE(SUM(amount),0) AS total
274
+ FROM pending_commission_escrow WHERE status='pending' AND expires_at <= ?`, [nowSec + 86400]));
275
+ // 3. consent 版本:当前 major + 仍停留在旧 major 上的 opted-in 用户数(= auto_downgrade 候选)
276
+ const currentMajor = await dbOne(`SELECT version, effective_at FROM rewards_consent_texts WHERE change_class='major' ORDER BY effective_at DESC LIMIT 1`);
277
+ let staleConsentOptedIn = 0;
278
+ if (currentMajor) {
279
+ staleConsentOptedIn = (await dbOne(`SELECT COUNT(*) AS n FROM users u
280
+ WHERE u.rewards_opted_in = 1 AND (
281
+ SELECT consent_version FROM rewards_applications
282
+ WHERE user_id = u.id AND action IN ('activate','reconfirm') ORDER BY created_at DESC LIMIT 1
283
+ ) IS NOT ?`, [currentMajor.version])).n;
284
+ }
285
+ const consentVersions = await dbAll(`SELECT version, change_class, effective_at FROM rewards_consent_texts ORDER BY effective_at DESC LIMIT 10`);
286
+ res.json({
287
+ applications: { by_action: appsByAction, opted_in_users: optedIn, recent: recentApps },
288
+ commission_escrow: { by_status: escrowByStatus, pending_by_path: escrowPendingByPath, expiring_within_24h: expiringSoon },
289
+ consent: { current_major: currentMajor || null, stale_consent_opted_in: staleConsentOptedIn, versions: consentVersions },
290
+ generated_at: new Date().toISOString(),
291
+ });
292
+ });
253
293
  }
@@ -1,21 +1,27 @@
1
1
  export function registerAdminAtomicRoutes(app, deps) {
2
- const { requireProtocolAdmin, processPvLedger, runBinarySettlement, executeSafeSettlementCron } = deps;
2
+ const { requireProtocolAdmin, processPvLedger, runBinarySettlement, executeSafeSettlementCron, logAdminAction } = deps;
3
3
  app.post('/api/admin/atomic/process-ledger', (req, res) => {
4
4
  const admin = requireProtocolAdmin(req, res);
5
5
  if (!admin)
6
6
  return;
7
- res.json({ processed: processPvLedger() });
7
+ const processed = processPvLedger();
8
+ logAdminAction(admin.id, 'atomic_process_ledger', 'protocol', null, { processed });
9
+ res.json({ processed });
8
10
  });
9
11
  app.post('/api/admin/atomic/run-settlement', (req, res) => {
10
12
  const admin = requireProtocolAdmin(req, res);
11
13
  if (!admin)
12
14
  return;
13
- res.json({ settled: runBinarySettlement() });
15
+ const settled = runBinarySettlement();
16
+ logAdminAction(admin.id, 'atomic_run_settlement', 'protocol', null, { settled });
17
+ res.json({ settled });
14
18
  });
15
19
  app.post('/api/admin/atomic/distribute', (req, res) => {
16
20
  const admin = requireProtocolAdmin(req, res);
17
21
  if (!admin)
18
22
  return;
19
- res.json(executeSafeSettlementCron());
23
+ const result = executeSafeSettlementCron();
24
+ logAdminAction(admin.id, 'atomic_distribute', 'protocol', null, { result });
25
+ res.json(result);
20
26
  });
21
27
  }
@@ -1,7 +1,9 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminCatalogRoutes(app, deps) {
2
- const { db, requireContentAdmin, logAdminAction } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { requireContentAdmin, logAdminAction } = deps;
3
5
  // ─── 类目 季节性配置 ─────────────────────────────────────
4
- app.post('/api/admin/categories/:id/seasonal', (req, res) => {
6
+ app.post('/api/admin/categories/:id/seasonal', async (req, res) => {
5
7
  const admin = requireContentAdmin(req, res);
6
8
  if (!admin)
7
9
  return;
@@ -12,22 +14,22 @@ export function registerAdminCatalogRoutes(app, deps) {
12
14
  const valid = months.filter(m => Number.isInteger(m) && m >= 1 && m <= 12);
13
15
  if (valid.length === 0)
14
16
  return void res.json({ error: '没有有效的月份(1-12)' });
15
- const cat = db.prepare('SELECT id, name FROM product_categories WHERE id = ?').get(req.params.id);
17
+ const cat = await dbOne('SELECT id, name FROM product_categories WHERE id = ?', [req.params.id]);
16
18
  if (!cat)
17
19
  return void res.status(404).json({ error: 'category 不存在' });
18
20
  const csv = [...new Set(valid)].sort((a, b) => a - b).join(',');
19
- db.prepare('UPDATE product_categories SET seasonal_months = ? WHERE id = ?').run(csv, req.params.id);
21
+ await dbRun('UPDATE product_categories SET seasonal_months = ? WHERE id = ?', [csv, req.params.id]);
20
22
  res.json({ success: true, category: cat.name, seasonal_months: csv });
21
23
  });
22
- app.delete('/api/admin/categories/:id/seasonal', (req, res) => {
24
+ app.delete('/api/admin/categories/:id/seasonal', async (req, res) => {
23
25
  const admin = requireContentAdmin(req, res);
24
26
  if (!admin)
25
27
  return;
26
- db.prepare('UPDATE product_categories SET seasonal_months = NULL WHERE id = ?').run(req.params.id);
28
+ await dbRun('UPDATE product_categories SET seasonal_months = NULL WHERE id = ?', [req.params.id]);
27
29
  res.json({ success: true });
28
30
  });
29
31
  // ─── 商品 列表 + 强制下架 ───────────────────────────────
30
- app.get('/api/admin/products', (req, res) => {
32
+ app.get('/api/admin/products', async (req, res) => {
31
33
  const admin = requireContentAdmin(req, res);
32
34
  if (!admin)
33
35
  return;
@@ -41,23 +43,23 @@ export function registerAdminCatalogRoutes(app, deps) {
41
43
  params.push(status);
42
44
  }
43
45
  sql += ` ORDER BY p.created_at DESC LIMIT 100`;
44
- res.json({ products: db.prepare(sql).all(...params) });
46
+ res.json({ products: await dbAll(sql, params) });
45
47
  });
46
- app.post('/api/admin/products/:id/force-delist', (req, res) => {
48
+ app.post('/api/admin/products/:id/force-delist', async (req, res) => {
47
49
  // P0.5: 需 content 权限(之前仅 requireAdmin)
48
50
  const admin = requireContentAdmin(req, res);
49
51
  if (!admin)
50
52
  return;
51
53
  const { reason } = req.body;
52
54
  const productId = req.params.id;
53
- const product = db.prepare("SELECT id, status, title FROM products WHERE id = ?").get(productId);
55
+ const product = await dbOne("SELECT id, status, title FROM products WHERE id = ?", [productId]);
54
56
  if (!product)
55
57
  return void res.json({ error: '商品不存在' });
56
58
  if (product.status === 'deleted')
57
59
  return void res.json({ error: '商品已删除' });
58
60
  if (product.status === 'paused')
59
61
  return void res.json({ error: '商品已是下架状态' });
60
- db.prepare("UPDATE products SET status = 'paused', updated_at = datetime('now') WHERE id = ?").run(productId);
62
+ await dbRun("UPDATE products SET status = 'paused', updated_at = datetime('now') WHERE id = ?", [productId]);
61
63
  logAdminAction(admin.id, 'force_delist', 'product', productId, { reason: reason || null, title: product.title });
62
64
  res.json({ success: true });
63
65
  });
@@ -1,6 +1,8 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminEditorPicksRoutes(app, deps) {
2
- const { db, requireContentAdmin, generateId } = deps;
3
- app.post('/api/admin/editor-picks', (req, res) => {
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { requireContentAdmin, generateId } = deps;
5
+ app.post('/api/admin/editor-picks', async (req, res) => {
4
6
  const admin = requireContentAdmin(req, res);
5
7
  if (!admin)
6
8
  return;
@@ -10,11 +12,11 @@ export function registerAdminEditorPicksRoutes(app, deps) {
10
12
  if (!target_id)
11
13
  return void res.status(400).json({ error: 'target_id 必填' });
12
14
  if (kind === 'product') {
13
- if (!db.prepare("SELECT 1 FROM products WHERE id = ? AND status != 'deleted'").get(target_id))
15
+ if (!await dbOne("SELECT 1 FROM products WHERE id = ? AND status != 'deleted'", [target_id]))
14
16
  return void res.status(400).json({ error: '商品不存在' });
15
17
  }
16
18
  else {
17
- if (!db.prepare("SELECT 1 FROM users WHERE id = ? AND role = 'seller'").get(target_id))
19
+ if (!await dbOne("SELECT 1 FROM users WHERE id = ? AND role = 'seller'", [target_id]))
18
20
  return void res.status(400).json({ error: '卖家不存在' });
19
21
  }
20
22
  // SQLite 兼容:"YYYY-MM-DD HH:MM:SS" — ISO 'T'/毫秒会让窗口比较失效
@@ -24,22 +26,25 @@ export function registerAdminEditorPicksRoutes(app, deps) {
24
26
  if (endsDate <= startsDate)
25
27
  return void res.status(400).json({ error: 'ends_at 必须晚于 starts_at' });
26
28
  const id = generateId('ep');
27
- db.prepare(`INSERT INTO editor_picks (id, kind, target_id, title, note, starts_at, ends_at, sort_order, created_by) VALUES (?,?,?,?,?,?,?,?,?)`)
28
- .run(id, kind, target_id, title ? String(title).slice(0, 100) : null, note ? String(note).slice(0, 500) : null, toSqliteUtc(startsDate), toSqliteUtc(endsDate), Number(sort_order) || 0, admin.id);
29
+ await dbRun(`INSERT INTO editor_picks (id, kind, target_id, title, note, starts_at, ends_at, sort_order, created_by) VALUES (?,?,?,?,?,?,?,?,?)`, [id, kind, target_id,
30
+ title ? String(title).slice(0, 100) : null,
31
+ note ? String(note).slice(0, 500) : null,
32
+ toSqliteUtc(startsDate), toSqliteUtc(endsDate),
33
+ Number(sort_order) || 0, admin.id]);
29
34
  res.json({ success: true, id });
30
35
  });
31
- app.delete('/api/admin/editor-picks/:id', (req, res) => {
36
+ app.delete('/api/admin/editor-picks/:id', async (req, res) => {
32
37
  const admin = requireContentAdmin(req, res);
33
38
  if (!admin)
34
39
  return;
35
- db.prepare('DELETE FROM editor_picks WHERE id = ?').run(req.params.id);
40
+ await dbRun('DELETE FROM editor_picks WHERE id = ?', [req.params.id]);
36
41
  res.json({ success: true });
37
42
  });
38
- app.get('/api/admin/editor-picks', (req, res) => {
43
+ app.get('/api/admin/editor-picks', async (req, res) => {
39
44
  const admin = requireContentAdmin(req, res);
40
45
  if (!admin)
41
46
  return;
42
- const rows = db.prepare(`SELECT * FROM editor_picks ORDER BY ends_at DESC LIMIT 200`).all();
47
+ const rows = await dbAll(`SELECT * FROM editor_picks ORDER BY ends_at DESC LIMIT 200`);
43
48
  res.json({ items: rows });
44
49
  });
45
50
  }
@@ -1,5 +1,7 @@
1
+ import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminEventsRoutes(app, deps) {
2
- const { db, requireAdmin, generateId, systemEventBuffer, SYSTEM_EVENT_BUFFER_SIZE, adminEventClients } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne),不再直接用 deps.db
4
+ const { requireAdmin, generateId, systemEventBuffer, SYSTEM_EVENT_BUFFER_SIZE, adminEventClients } = deps;
3
5
  app.get('/api/admin/events/recent', (req, res) => {
4
6
  const admin = requireAdmin(req, res);
5
7
  if (!admin)
@@ -24,14 +26,14 @@ export function registerAdminEventsRoutes(app, deps) {
24
26
  sseTickets.set(ticket, { userId: String(admin.id), expiresAt: Date.now() + 60_000 });
25
27
  res.json({ ticket, expires_in: 60 });
26
28
  });
27
- app.get('/api/admin/events/stream', (req, res) => {
29
+ app.get('/api/admin/events/stream', async (req, res) => {
28
30
  const ticket = String(req.query.ticket || '');
29
31
  cleanupSseTickets();
30
32
  const info = sseTickets.get(ticket);
31
33
  if (!info)
32
34
  return void res.status(403).json({ error: '无效或已过期的 ticket' });
33
35
  sseTickets.delete(ticket); // 单次使用
34
- const u = db.prepare('SELECT role FROM users WHERE id = ?').get(info.userId);
36
+ const u = await dbOne('SELECT role FROM users WHERE id = ?', [info.userId]);
35
37
  if (!u || u.role !== 'admin')
36
38
  return void res.status(403).json({ error: '需要 admin 身份' });
37
39
  res.setHeader('Content-Type', 'text/event-stream');
@@ -1,3 +1,4 @@
1
+ import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerAdminHealthRoutes(app, deps) {
2
3
  const { db, requireProtocolAdmin, getPublicClient, getRpcUrl, getNetwork, adminEventClients, sseClients, systemEventBuffer, authFailures } = deps;
3
4
  app.get('/api/admin/health', async (req, res) => {
@@ -21,7 +22,7 @@ export function registerAdminHealthRoutes(app, deps) {
21
22
  if (t === 'system_events_buffer_in_mem')
22
23
  continue;
23
24
  try {
24
- tableCounts[t] = db.prepare(`SELECT COUNT(*) as n FROM ${t}`).get().n;
25
+ tableCounts[t] = (await dbOne(`SELECT COUNT(*) as n FROM ${t}`)).n;
25
26
  }
26
27
  catch {
27
28
  tableCounts[t] = -1;