@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,3 +1,4 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  /** 计算 coupon 适用 + 折扣。跨域调用:orders 下单流程会用。 */
2
3
  export function applyCouponToOrder(db, couponCode, sellerId, productId, totalAmount) {
3
4
  const code = couponCode.trim().toUpperCase();
@@ -36,8 +37,9 @@ export function applyCouponToOrder(db, couponCode, sellerId, productId, totalAmo
36
37
  return { ok: true, coupon, discount };
37
38
  }
38
39
  export function registerCouponsRoutes(app, deps) {
39
- const { db, generateId, auth, isTrustedRole, safeRoles, errorRes } = deps;
40
- app.post('/api/coupons', (req, res) => {
40
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun);applyCouponToOrder 仍同步(订单/结算金钱路径,随 money batch 一起迁)
41
+ const { generateId, auth, isTrustedRole, safeRoles, errorRes } = deps;
42
+ app.post('/api/coupons', async (req, res) => {
41
43
  const user = auth(req, res);
42
44
  if (!user)
43
45
  return;
@@ -62,7 +64,7 @@ export function registerCouponsRoutes(app, deps) {
62
64
  if (discount_type === 'percentage' && dv > 90)
63
65
  return void res.status(400).json({ error: 'percentage 最高 90' });
64
66
  if (scope === 'product') {
65
- const p = db.prepare('SELECT seller_id FROM products WHERE id = ?').get(scope_id);
67
+ const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [scope_id]);
66
68
  if (!p)
67
69
  return void res.status(404).json({ error: '商品不存在' });
68
70
  if (p.seller_id !== user.id)
@@ -74,8 +76,9 @@ export function registerCouponsRoutes(app, deps) {
74
76
  }
75
77
  const id = generateId('cpn');
76
78
  try {
77
- db.prepare(`INSERT INTO coupons (id, seller_id, code, scope, scope_id, discount_type, discount_value, min_order_amount, max_uses, starts_at, expires_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`)
78
- .run(id, user.id, codeStr, scope, scope_id || null, discount_type, dv, Number(min_order_amount) || 0, Number(max_uses) || 0, starts_at || null, expires_at || null);
79
+ await dbRun(`INSERT INTO coupons (id, seller_id, code, scope, scope_id, discount_type, discount_value, min_order_amount, max_uses, starts_at, expires_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)`, [id, user.id, codeStr, scope, scope_id || null, discount_type, dv,
80
+ Number(min_order_amount) || 0, Number(max_uses) || 0,
81
+ starts_at || null, expires_at || null]);
79
82
  }
80
83
  catch {
81
84
  return void res.status(409).json({ error: '此 code 已存在(每个卖家 code 唯一)' });
@@ -83,13 +86,13 @@ export function registerCouponsRoutes(app, deps) {
83
86
  res.json({ success: true, id, code: codeStr });
84
87
  });
85
88
  // buyer 视角:全平台 + 已购卖家店铺/单品券 + 历史
86
- app.get('/api/coupons/available', (req, res) => {
89
+ app.get('/api/coupons/available', async (req, res) => {
87
90
  const user = auth(req, res);
88
91
  if (!user)
89
92
  return;
90
93
  if (isTrustedRole(user))
91
94
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无购物功能');
92
- const purchasedSellers = db.prepare(`SELECT DISTINCT seller_id FROM orders WHERE buyer_id = ?`).all(user.id);
95
+ const purchasedSellers = await dbAll(`SELECT DISTINCT seller_id FROM orders WHERE buyer_id = ?`, [user.id]);
93
96
  const sellerIds = purchasedSellers.map(r => r.seller_id);
94
97
  const placeholders = sellerIds.length > 0 ? sellerIds.map(() => '?').join(',') : '';
95
98
  const sellerCondition = sellerIds.length > 0 ? `OR (c.seller_id IN (${placeholders}) AND c.scope IN ('shop','product'))` : '';
@@ -112,8 +115,8 @@ export function registerCouponsRoutes(app, deps) {
112
115
  c.created_at DESC
113
116
  LIMIT 100
114
117
  `;
115
- const rows = db.prepare(sql).all(...sellerIds);
116
- const history = db.prepare(`
118
+ const rows = await dbAll(sql, sellerIds);
119
+ const history = await dbAll(`
117
120
  SELECT o.id as order_id, o.created_at, o.coupon_discount,
118
121
  c.code, c.scope, c.discount_type, c.discount_value,
119
122
  p.title as product_title
@@ -122,23 +125,23 @@ export function registerCouponsRoutes(app, deps) {
122
125
  JOIN products p ON p.id = o.product_id
123
126
  WHERE o.buyer_id = ? AND o.coupon_id IS NOT NULL
124
127
  ORDER BY o.created_at DESC LIMIT 50
125
- `).all(user.id);
128
+ `, [user.id]);
126
129
  res.json({ available: rows, history });
127
130
  });
128
- app.get('/api/coupons/mine', (req, res) => {
131
+ app.get('/api/coupons/mine', async (req, res) => {
129
132
  const user = auth(req, res);
130
133
  if (!user)
131
134
  return;
132
- const rows = db.prepare(`
135
+ const rows = await dbAll(`
133
136
  SELECT * FROM coupons WHERE seller_id = ? ORDER BY created_at DESC LIMIT 100
134
- `).all(user.id);
137
+ `, [user.id]);
135
138
  res.json({ items: rows });
136
139
  });
137
- app.patch('/api/coupons/:id', (req, res) => {
140
+ app.patch('/api/coupons/:id', async (req, res) => {
138
141
  const user = auth(req, res);
139
142
  if (!user)
140
143
  return;
141
- const coupon = db.prepare('SELECT * FROM coupons WHERE id = ? AND seller_id = ?').get(req.params.id, user.id);
144
+ const coupon = await dbOne('SELECT * FROM coupons WHERE id = ? AND seller_id = ?', [req.params.id, user.id]);
142
145
  if (!coupon)
143
146
  return void res.status(404).json({ error: '优惠码不存在或无权限' });
144
147
  const { is_active, expires_at, max_uses } = req.body || {};
@@ -159,7 +162,7 @@ export function registerCouponsRoutes(app, deps) {
159
162
  if (sets.length === 0)
160
163
  return void res.status(400).json({ error: '无可更新字段' });
161
164
  args.push(req.params.id);
162
- db.prepare(`UPDATE coupons SET ${sets.join(', ')} WHERE id = ?`).run(...args);
165
+ await dbRun(`UPDATE coupons SET ${sets.join(', ')} WHERE id = ?`, args);
163
166
  res.json({ success: true });
164
167
  });
165
168
  }
@@ -1,13 +1,15 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerDashboardsRoutes(app, deps) {
2
- const { db, auth } = deps;
3
- app.get('/api/tokenomics/status', (_req, res) => {
4
- const gf = db.prepare("SELECT pool_balance FROM global_fund WHERE id = 1").get();
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
+ const { auth } = deps;
5
+ app.get('/api/tokenomics/status', async (_req, res) => {
6
+ const gf = await dbOne("SELECT pool_balance FROM global_fund WHERE id = 1");
5
7
  const poolBalance = Number(gf?.pool_balance ?? 0);
6
- const histRow = db.prepare(`
8
+ const histRow = (await dbOne(`
7
9
  SELECT AVG(deposited_this_period) as avg_dep
8
10
  FROM settlement_periods
9
11
  WHERE status = 'completed' AND started_at > datetime('now', '-28 days')
10
- `).get();
12
+ `));
11
13
  const historyAverage = Math.round(Number(histRow?.avg_dep ?? 0) * 100) / 100;
12
14
  let healthLevel = 'cold_start';
13
15
  let distributionCap = 1.0;
@@ -23,15 +25,15 @@ export function registerDashboardsRoutes(app, deps) {
23
25
  else
24
26
  healthLevel = 'critical';
25
27
  }
26
- const lastSettled = db.prepare(`
28
+ const lastSettled = await dbOne(`
27
29
  SELECT effective_unit_cash, payout_rate, started_at FROM settlement_periods
28
30
  WHERE status = 'completed' ORDER BY started_at DESC LIMIT 1
29
- `).get();
30
- const recentPaused = db.prepare(`
31
+ `);
32
+ const recentPaused = await dbOne(`
31
33
  SELECT period_id, started_at, note FROM settlement_periods
32
34
  WHERE status = 'paused_low_water' AND started_at > datetime('now', '-7 days')
33
35
  ORDER BY started_at DESC LIMIT 1
34
- `).get();
36
+ `);
35
37
  res.json({
36
38
  pool_balance: poolBalance,
37
39
  history_average: historyAverage,
@@ -42,12 +44,12 @@ export function registerDashboardsRoutes(app, deps) {
42
44
  last_settlement: lastSettled || null,
43
45
  });
44
46
  });
45
- app.get('/api/shares/dashboard', (req, res) => {
47
+ app.get('/api/shares/dashboard', async (req, res) => {
46
48
  const user = auth(req, res);
47
49
  if (!user)
48
50
  return;
49
51
  const userId = user.id;
50
- const bought = db.prepare(`
52
+ const bought = await dbAll(`
51
53
  SELECT
52
54
  o.id as order_id,
53
55
  o.updated_at as completed_at,
@@ -65,8 +67,8 @@ export function registerDashboardsRoutes(app, deps) {
65
67
  WHERE o.buyer_id = ? AND o.status = 'completed'
66
68
  ORDER BY o.updated_at DESC
67
69
  LIMIT 50
68
- `).all(userId, userId, userId, userId, userId, userId);
69
- const highComm = db.prepare(`
70
+ `, [userId, userId, userId, userId, userId, userId]);
71
+ const highComm = await dbAll(`
70
72
  SELECT p.id, p.title, p.price, p.commission_rate, p.images, p.category,
71
73
  (SELECT COUNT(*) FROM orders o WHERE o.product_id = p.id AND o.status = 'completed') as sales_count
72
74
  FROM products p
@@ -76,8 +78,8 @@ export function registerDashboardsRoutes(app, deps) {
76
78
  AND p.id NOT IN (SELECT product_id FROM orders WHERE buyer_id = ? AND status = 'completed')
77
79
  ORDER BY p.commission_rate DESC, sales_count DESC
78
80
  LIMIT 10
79
- `).all(userId, userId);
80
- const myCreations = db.prepare(`
81
+ `, [userId, userId]);
82
+ const myCreations = await dbAll(`
81
83
  SELECT s.id, s.type, s.title, s.external_platform, s.external_url,
82
84
  s.related_product_id, s.related_order_id, s.related_anchor, p.title as product_title,
83
85
  s.click_count, s.like_count, s.created_at,
@@ -89,7 +91,7 @@ export function registerDashboardsRoutes(app, deps) {
89
91
  WHERE s.owner_id = ? AND s.status = 'active'
90
92
  ORDER BY s.created_at DESC
91
93
  LIMIT 30
92
- `).all(userId);
94
+ `, [userId]);
93
95
  res.json({
94
96
  bought_products: bought,
95
97
  high_commission_products: highComm,
@@ -1,12 +1,13 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerDisputeCasesRoutes(app, deps) {
2
3
  const { db, auth, getUser, generateId, piiSanitize, detectFraud, commentBlocklistHit, llmModerateComment } = deps;
3
4
  // 公共发言门槛 — 防新号/小号刷评论/投票
4
5
  // 至少满足其一:账户 >= 3 天 / 完成过 >= 1 单 / lifetime_score >= 5
5
- function meetsPublicSpeechThreshold(user) {
6
+ async function meetsPublicSpeechThreshold(user) {
6
7
  const lifetime = Number(user.lifetime_score || 0);
7
8
  if (lifetime >= 5)
8
9
  return { ok: true };
9
- const completed = db.prepare(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND status = 'completed'`).get(user.id, user.id).n;
10
+ const completed = (await dbOne(`SELECT COUNT(*) as n FROM orders WHERE (buyer_id = ? OR seller_id = ?) AND status = 'completed'`, [user.id, user.id])).n;
10
11
  if (completed >= 1)
11
12
  return { ok: true };
12
13
  const created = user.created_at ? new Date(String(user.created_at).replace(' ', 'T') + 'Z').getTime() : 0;
@@ -15,7 +16,7 @@ export function registerDisputeCasesRoutes(app, deps) {
15
16
  return { ok: false, reason: '账号需 ≥ 3 天 或 完成 ≥ 1 单 或 lifetime_score ≥ 5 才能公开发言(防小号刷量)' };
16
17
  }
17
18
  // 公开列表(全网)— 判例库总览
18
- app.get('/api/disputes/cases', (req, res) => {
19
+ app.get('/api/disputes/cases', async (req, res) => {
19
20
  const limit = Math.min(50, Math.max(5, Number(req.query.limit) || 20));
20
21
  const category = req.query.category ? String(req.query.category) : null;
21
22
  const winner = req.query.winner ? String(req.query.winner) : null;
@@ -47,7 +48,7 @@ export function registerDisputeCasesRoutes(app, deps) {
47
48
  orderSql = 'comment_count DESC, fairness_yes DESC, published_at DESC';
48
49
  else if (sort === 'fair')
49
50
  orderSql = 'fairness_yes DESC, comment_count DESC, published_at DESC';
50
- const rows = db.prepare(`
51
+ const rows = await dbAll(`
51
52
  SELECT id, product_id, category_tag, winner, resolution, amount_bucket,
52
53
  fairness_yes, fairness_no, comment_count, published_at,
53
54
  (SELECT title FROM products WHERE id = dispute_cases.product_id) as product_title,
@@ -56,30 +57,30 @@ export function registerDisputeCasesRoutes(app, deps) {
56
57
  ${whereSql}
57
58
  ORDER BY ${orderSql}
58
59
  LIMIT ?
59
- `).all(...args, limit);
60
+ `, [...args, limit]);
60
61
  // 类目统计(侧栏过滤用)— 受 q 影响(搜索时只显匹配 q 的类目计数)
61
62
  const catCountWhere = q ? `WHERE (ruling_text LIKE ? OR buyer_argument LIKE ? OR seller_argument LIKE ? OR resolution LIKE ?
62
63
  OR EXISTS (SELECT 1 FROM products p WHERE p.id = dispute_cases.product_id AND p.title LIKE ?))` : '';
63
64
  const catCountArgs = q ? Array(5).fill('%' + q.replace(/[%_]/g, '\\$&') + '%') : [];
64
- const categoryCounts = db.prepare(`SELECT category_tag, COUNT(*) as n FROM dispute_cases ${catCountWhere} GROUP BY category_tag ORDER BY n DESC`).all(...catCountArgs);
65
+ const categoryCounts = await dbAll(`SELECT category_tag, COUNT(*) as n FROM dispute_cases ${catCountWhere} GROUP BY category_tag ORDER BY n DESC`, catCountArgs);
65
66
  res.json({ items: rows, category_counts: categoryCounts, total: rows.length, query: q, sort });
66
67
  });
67
68
  // 公开列表(按商品)
68
- app.get('/api/disputes/cases/by-product/:product_id', (req, res) => {
69
- const rows = db.prepare(`
69
+ app.get('/api/disputes/cases/by-product/:product_id', async (req, res) => {
70
+ const rows = await dbAll(`
70
71
  SELECT id, category_tag, winner, resolution, amount_bucket, ruling_text,
71
72
  fairness_yes, fairness_no, comment_count, published_at
72
73
  FROM dispute_cases
73
74
  WHERE product_id = ?
74
75
  ORDER BY published_at DESC
75
76
  LIMIT 50
76
- `).all(req.params.product_id);
77
+ `, [req.params.product_id]);
77
78
  res.json({ items: rows });
78
79
  });
79
80
  // 案件详情(含评论 + 评论者身份标签)
80
- app.get('/api/disputes/cases/:case_id', (req, res) => {
81
+ app.get('/api/disputes/cases/:case_id', async (req, res) => {
81
82
  const me = getUser(req);
82
- const c = db.prepare(`SELECT * FROM dispute_cases WHERE id = ?`).get(req.params.case_id);
83
+ const c = await dbOne(`SELECT * FROM dispute_cases WHERE id = ?`, [req.params.case_id]);
83
84
  if (!c)
84
85
  return void res.status(404).json({ error: '判例不存在' });
85
86
  // 不外露内部 ID(buyer_id/dispute_id/order_id 不返回给前端)
@@ -91,7 +92,7 @@ export function registerDisputeCasesRoutes(app, deps) {
91
92
  product_id: c.product_id, seller_id: c.seller_id,
92
93
  };
93
94
  // 评论 + 自动身份标签
94
- const rawComments = db.prepare(`
95
+ const rawComments = await dbAll(`
95
96
  SELECT dc.*, u.handle, u.name, u.role, u.lifetime_score,
96
97
  (SELECT COUNT(*) FROM orders o
97
98
  WHERE o.buyer_id = dc.commenter_id AND o.product_id = ? AND o.status = 'completed') as bought_count,
@@ -105,7 +106,7 @@ export function registerDisputeCasesRoutes(app, deps) {
105
106
  (u.role IN ('verifier','arbitrator')) DESC,
106
107
  dc.created_at DESC
107
108
  LIMIT 50
108
- `).all(c.product_id, c.product_id, c.id);
109
+ `, [c.product_id, c.product_id, c.id]);
109
110
  // 脱敏:anonymous=1 时清除 handle/name/commenter_id(保留贡献标签字段:bought/same_cat/role/lifetime)
110
111
  // 验证员/仲裁员角色在脱敏时降级为 'staff'(避免小池子反推身份)
111
112
  const anonymize = (row) => {
@@ -116,12 +117,12 @@ export function registerDisputeCasesRoutes(app, deps) {
116
117
  };
117
118
  // W5: 取所有子回复,按 parent_comment_id 分组挂在 comments 下
118
119
  const commentIds = rawComments.map(r => r.id);
119
- const rawReplies = commentIds.length > 0 ? db.prepare(`
120
+ const rawReplies = commentIds.length > 0 ? await dbAll(`
120
121
  SELECT r.*, u.handle, u.name, u.role, u.lifetime_score
121
122
  FROM dispute_comment_replies r LEFT JOIN users u ON u.id = r.replier_id
122
123
  WHERE r.parent_comment_id IN (${commentIds.map(() => '?').join(',')})
123
124
  ORDER BY r.created_at ASC
124
- `).all(...commentIds) : [];
125
+ `, commentIds) : [];
125
126
  const repliesByParent = new Map();
126
127
  for (const r of rawReplies) {
127
128
  const pid = String(r.parent_comment_id);
@@ -136,7 +137,7 @@ export function registerDisputeCasesRoutes(app, deps) {
136
137
  // 我的公正度投票(如已投)
137
138
  let myVote = null;
138
139
  if (me) {
139
- const v = db.prepare('SELECT vote FROM dispute_fairness_votes WHERE case_id = ? AND voter_id = ?').get(c.id, me.id);
140
+ const v = await dbOne('SELECT vote FROM dispute_fairness_votes WHERE case_id = ? AND voter_id = ?', [c.id, me.id]);
140
141
  myVote = v?.vote || null;
141
142
  }
142
143
  // 我是否当事人(决定能否评论 / 投票)
@@ -148,10 +149,10 @@ export function registerDisputeCasesRoutes(app, deps) {
148
149
  const user = auth(req, res);
149
150
  if (!user)
150
151
  return;
151
- const gate = meetsPublicSpeechThreshold(user);
152
+ const gate = await meetsPublicSpeechThreshold(user);
152
153
  if (!gate.ok)
153
154
  return void res.status(403).json({ error: gate.reason, error_code: 'SPEECH_THRESHOLD' });
154
- const c = db.prepare(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`).get(req.params.case_id);
155
+ const c = await dbOne(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`, [req.params.case_id]);
155
156
  if (!c)
156
157
  return void res.status(404).json({ error: '判例不存在' });
157
158
  if (user.id === c.buyer_id || user.id === c.seller_id || user.id === c.arbitrator_id) {
@@ -194,16 +195,16 @@ export function registerDisputeCasesRoutes(app, deps) {
194
195
  const user = auth(req, res);
195
196
  if (!user)
196
197
  return;
197
- const gate = meetsPublicSpeechThreshold(user);
198
+ const gate = await meetsPublicSpeechThreshold(user);
198
199
  if (!gate.ok)
199
200
  return void res.status(403).json({ error: gate.reason, error_code: 'SPEECH_THRESHOLD' });
200
- const c = db.prepare(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`).get(req.params.case_id);
201
+ const c = await dbOne(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`, [req.params.case_id]);
201
202
  if (!c)
202
203
  return void res.status(404).json({ error: '判例不存在' });
203
204
  if (user.id === c.buyer_id || user.id === c.seller_id || user.id === c.arbitrator_id) {
204
205
  return void res.status(403).json({ error: '当事人禁止评论', error_code: 'PARTY_NO_COMMENT' });
205
206
  }
206
- const parent = db.prepare(`SELECT id FROM dispute_comments WHERE id = ? AND case_id = ?`).get(req.params.comment_id, c.id);
207
+ const parent = await dbOne(`SELECT id FROM dispute_comments WHERE id = ? AND case_id = ?`, [req.params.comment_id, c.id]);
207
208
  if (!parent)
208
209
  return void res.status(404).json({ error: '父评论不存在' });
209
210
  const rawBody = String(req.body?.body || '').trim();
@@ -230,17 +231,17 @@ export function registerDisputeCasesRoutes(app, deps) {
230
231
  res.json({ success: true, id: rid, flag_reasons: repReasons });
231
232
  });
232
233
  // 公正度投票(👍 / 👎)— 一人一案一票
233
- app.post('/api/disputes/cases/:case_id/fairness', (req, res) => {
234
+ app.post('/api/disputes/cases/:case_id/fairness', async (req, res) => {
234
235
  const user = auth(req, res);
235
236
  if (!user)
236
237
  return;
237
- const gate = meetsPublicSpeechThreshold(user);
238
+ const gate = await meetsPublicSpeechThreshold(user);
238
239
  if (!gate.ok)
239
240
  return void res.status(403).json({ error: gate.reason, error_code: 'SPEECH_THRESHOLD' });
240
241
  const vote = req.body?.vote;
241
242
  if (vote !== 'yes' && vote !== 'no')
242
243
  return void res.status(400).json({ error: 'vote 必须是 yes 或 no' });
243
- const c = db.prepare(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`).get(req.params.case_id);
244
+ const c = await dbOne(`SELECT id, buyer_id, seller_id, arbitrator_id FROM dispute_cases WHERE id = ?`, [req.params.case_id]);
244
245
  if (!c)
245
246
  return void res.status(404).json({ error: '判例不存在' });
246
247
  if (user.id === c.buyer_id || user.id === c.seller_id || user.id === c.arbitrator_id) {
@@ -1,29 +1,30 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerDisputesReadRoutes(app, deps) {
2
3
  const { db, auth, errorRes, getOpenDisputes, getDisputeDetails, getEvidenceRequests, listEvidenceFiles, isEligibleArbitrator } = deps;
3
4
  // 仲裁员:查看所有开放争议
4
- app.get('/api/disputes', (req, res) => {
5
+ app.get('/api/disputes', async (req, res) => {
5
6
  const user = auth(req, res);
6
7
  if (!user)
7
8
  return;
8
9
  const elig = isEligibleArbitrator(user.id);
9
10
  if (!elig.ok)
10
11
  return void errorRes(res, 403, 'NOT_ARBITRATOR', elig.reason || '仅限仲裁员访问');
11
- res.json(getOpenDisputes(db));
12
+ res.json(await getOpenDisputes(db));
12
13
  });
13
14
  // A2 同类判例推荐
14
- app.get('/api/disputes/:id/similar-cases', (req, res) => {
15
+ app.get('/api/disputes/:id/similar-cases', async (req, res) => {
15
16
  const user = auth(req, res);
16
17
  if (!user)
17
18
  return;
18
- const dispute = db.prepare('SELECT id, order_id, reason, initiator_id, defendant_id FROM disputes WHERE id = ?').get(req.params.id);
19
+ const dispute = await dbOne('SELECT id, order_id, reason, initiator_id, defendant_id FROM disputes WHERE id = ?', [req.params.id]);
19
20
  if (!dispute)
20
21
  return void res.status(404).json({ error: '争议不存在' });
21
22
  const role = user.role;
22
23
  if (dispute.initiator_id !== user.id && dispute.defendant_id !== user.id && role !== 'arbitrator') {
23
24
  return void res.status(403).json({ error: '无权查看' });
24
25
  }
25
- const order = db.prepare('SELECT product_id FROM orders WHERE id = ?').get(dispute.order_id);
26
- const productCategory = order ? db.prepare('SELECT category FROM products WHERE id = ?').get(order.product_id)?.category : null;
26
+ const order = await dbOne('SELECT product_id FROM orders WHERE id = ?', [dispute.order_id]);
27
+ const productCategory = order ? (await dbOne('SELECT category FROM products WHERE id = ?', [order.product_id]))?.category : null;
27
28
  const reasonWords = (dispute.reason || '').split(/[\s,,。;\n]+/).filter(w => w.length >= 2).slice(0, 3);
28
29
  const results = [];
29
30
  const seen = new Set();
@@ -35,7 +36,7 @@ export function registerDisputesReadRoutes(app, deps) {
35
36
  };
36
37
  // ① 同 product 类目
37
38
  if (productCategory) {
38
- const r1 = db.prepare(`
39
+ const r1 = await dbAll(`
39
40
  SELECT dc.id, dc.product_id, dc.category_tag, dc.winner, dc.resolution,
40
41
  dc.amount_bucket, dc.fairness_yes, dc.comment_count, dc.published_at,
41
42
  (SELECT title FROM products WHERE id = dc.product_id) as product_title,
@@ -44,7 +45,7 @@ export function registerDisputesReadRoutes(app, deps) {
44
45
  JOIN products p ON p.id = dc.product_id
45
46
  WHERE p.category = ? AND (dc.dispute_id IS NULL OR dc.dispute_id != ?)
46
47
  ORDER BY dc.published_at DESC LIMIT 3
47
- `).all(productCategory, dispute.id);
48
+ `, [productCategory, dispute.id]);
48
49
  r1.forEach(pushIfNew);
49
50
  }
50
51
  // ② reason 关键词命中 ruling_text / 双方陈述
@@ -53,7 +54,7 @@ export function registerDisputesReadRoutes(app, deps) {
53
54
  if (results.length >= 3)
54
55
  break;
55
56
  const pat = '%' + w.replace(/[%_]/g, '\\$&') + '%';
56
- const r2 = db.prepare(`
57
+ const r2 = await dbAll(`
57
58
  SELECT dc.id, dc.product_id, dc.category_tag, dc.winner, dc.resolution,
58
59
  dc.amount_bucket, dc.fairness_yes, dc.comment_count, dc.published_at,
59
60
  (SELECT title FROM products WHERE id = dc.product_id) as product_title,
@@ -62,13 +63,13 @@ export function registerDisputesReadRoutes(app, deps) {
62
63
  WHERE (dc.dispute_id IS NULL OR dc.dispute_id != ?)
63
64
  AND (dc.ruling_text LIKE ? OR dc.buyer_argument LIKE ? OR dc.seller_argument LIKE ?)
64
65
  ORDER BY dc.published_at DESC LIMIT 3
65
- `).all(dispute.id, pat, pat, pat);
66
+ `, [dispute.id, pat, pat, pat]);
66
67
  r2.forEach(pushIfNew);
67
68
  }
68
69
  }
69
70
  // ③ 兜底:最近 3 条已发布判例
70
71
  if (results.length < 3) {
71
- const r3 = db.prepare(`
72
+ const r3 = await dbAll(`
72
73
  SELECT dc.id, dc.product_id, dc.category_tag, dc.winner, dc.resolution,
73
74
  dc.amount_bucket, dc.fairness_yes, dc.comment_count, dc.published_at,
74
75
  (SELECT title FROM products WHERE id = dc.product_id) as product_title,
@@ -76,30 +77,29 @@ export function registerDisputesReadRoutes(app, deps) {
76
77
  FROM dispute_cases dc
77
78
  WHERE (dc.dispute_id IS NULL OR dc.dispute_id != ?)
78
79
  ORDER BY dc.published_at DESC LIMIT 3
79
- `).all(dispute.id);
80
+ `, [dispute.id]);
80
81
  r3.forEach(pushIfNew);
81
82
  }
82
83
  res.json({ items: results.slice(0, 3), product_category: productCategory, reason_keywords: reasonWords });
83
84
  });
84
85
  // 详情聚合(含 W4 timeline + chain ruling)
85
- app.get('/api/disputes/:id', (req, res) => {
86
+ app.get('/api/disputes/:id', async (req, res) => {
86
87
  const user = auth(req, res);
87
88
  if (!user)
88
89
  return;
89
- const dispute = getDisputeDetails(db, req.params.id);
90
+ const dispute = await getDisputeDetails(db, req.params.id);
90
91
  if (!dispute)
91
92
  return void res.status(404).json({ error: '争议不存在' });
92
93
  const role = user.role;
93
94
  // 允许:发起方、被告方、物流方、仲裁员
94
- const orderForAuth = db.prepare('SELECT logistics_id FROM orders WHERE id = ?')
95
- .get(dispute.order_id);
95
+ const orderForAuth = await dbOne('SELECT logistics_id FROM orders WHERE id = ?', [dispute.order_id]);
96
96
  const isLogisticsParty = orderForAuth?.logistics_id === user.id;
97
97
  if (dispute.initiator_id !== user.id && dispute.defendant_id !== user.id
98
98
  && !isLogisticsParty && role !== 'arbitrator') {
99
99
  return void res.status(403).json({ error: '无权查看此争议' });
100
100
  }
101
101
  // 原告证据 — 从状态机历史中取 disputed 转移时附带的
102
- const hist = db.prepare(`SELECT evidence_ids FROM order_state_history WHERE order_id = ? AND to_status = 'disputed'`).get(dispute.order_id);
102
+ const hist = await dbOne(`SELECT evidence_ids FROM order_state_history WHERE order_id = ? AND to_status = 'disputed'`, [dispute.order_id]);
103
103
  // P1 fix: 单条脏 JSON 不应封死整个 dispute 详情
104
104
  const safeJsonArr = (s) => {
105
105
  try {
@@ -112,26 +112,24 @@ export function registerDisputesReadRoutes(app, deps) {
112
112
  };
113
113
  const plaintiffEvidenceIds = hist ? safeJsonArr(hist.evidence_ids) : [];
114
114
  const defEvidenceIds = safeJsonArr(dispute.defendant_evidence_ids);
115
- const fetchEvidence = (ids) => ids.length
116
- ? db.prepare(`SELECT * FROM evidence WHERE id IN (${ids.map(() => '?').join(',')})`).all(...ids)
115
+ const fetchEvidence = async (ids) => ids.length
116
+ ? await dbAll(`SELECT * FROM evidence WHERE id IN (${ids.map(() => '?').join(',')})`, ids)
117
117
  : [];
118
- const evidenceRequests = getEvidenceRequests(db, req.params.id);
118
+ const evidenceRequests = await getEvidenceRequests(db, req.params.id);
119
119
  const myPendingRequests = evidenceRequests.filter((r) => r.requested_from_id === user.id && r.status === 'pending');
120
- const order = db.prepare('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?')
121
- .get(dispute.order_id);
120
+ const order = await dbOne('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?', [dispute.order_id]);
122
121
  const partyIds = [dispute.initiator_id, dispute.defendant_id, order?.logistics_id].filter(Boolean);
123
- const parties = [...new Set(partyIds)].map(id => db.prepare('SELECT id, name, role FROM users WHERE id = ?').get(id)).filter(Boolean);
122
+ const parties = (await Promise.all([...new Set(partyIds)].map(id => dbOne('SELECT id, name, role FROM users WHERE id = ?', [id])))).filter(Boolean);
124
123
  const partyEvidenceIds = safeJsonArr(dispute.party_evidence_ids);
125
- const orderParties = db.prepare('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?')
126
- .get(dispute.order_id);
124
+ const orderParties = await dbOne('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?', [dispute.order_id]);
127
125
  const allPartyIds = [
128
126
  orderParties?.buyer_id, orderParties?.seller_id, orderParties?.logistics_id,
129
127
  dispute.initiator_id, dispute.defendant_id
130
128
  ].filter(Boolean);
131
129
  const isParty = allPartyIds.includes(user.id);
132
- const plaintiffEvidence = fetchEvidence(plaintiffEvidenceIds);
133
- const defendantEvidence = fetchEvidence(defEvidenceIds);
134
- const partyEvidence = fetchEvidence(partyEvidenceIds);
130
+ const plaintiffEvidence = await fetchEvidence(plaintiffEvidenceIds);
131
+ const defendantEvidence = await fetchEvidence(defEvidenceIds);
132
+ const partyEvidence = await fetchEvidence(partyEvidenceIds);
135
133
  // W4 timeline 归一化
136
134
  const partyMap = new Map();
137
135
  partyMap.set(dispute.initiator_id, 'plaintiff');
@@ -149,15 +147,15 @@ export function registerDisputesReadRoutes(app, deps) {
149
147
  return 'unknown';
150
148
  };
151
149
  const userById = new Map();
152
- const loadUser = (uid) => {
150
+ const loadUser = async (uid) => {
153
151
  if (!uid || userById.has(uid))
154
152
  return;
155
- const u = db.prepare('SELECT id, name, handle, role FROM users WHERE id = ?').get(uid);
153
+ const u = await dbOne('SELECT id, name, handle, role FROM users WHERE id = ?', [uid]);
156
154
  if (u)
157
155
  userById.set(uid, u);
158
156
  };
159
- loadUser(dispute.initiator_id);
160
- loadUser(dispute.defendant_id);
157
+ await loadUser(dispute.initiator_id);
158
+ await loadUser(dispute.defendant_id);
161
159
  const events = [];
162
160
  // 1) open
163
161
  events.push({
@@ -174,8 +172,8 @@ export function registerDisputesReadRoutes(app, deps) {
174
172
  },
175
173
  });
176
174
  // 2) plaintiff evidence
177
- const evToEvent = (ev, role) => {
178
- loadUser(ev.uploader_id);
175
+ const evToEvent = async (ev, role) => {
176
+ await loadUser(ev.uploader_id);
179
177
  let fr = [];
180
178
  try {
181
179
  fr = ev.flag_reasons ? JSON.parse(String(ev.flag_reasons)) : [];
@@ -204,7 +202,7 @@ export function registerDisputesReadRoutes(app, deps) {
204
202
  };
205
203
  };
206
204
  for (const ev of plaintiffEvidence)
207
- events.push(evToEvent(ev, 'plaintiff'));
205
+ events.push(await evToEvent(ev, 'plaintiff'));
208
206
  // 3) defendant response — 用 defendant 第一条证据时间逼近 respond_at
209
207
  if (dispute.defendant_notes) {
210
208
  const firstDefTs = defendantEvidence[0]?.created_at;
@@ -218,11 +216,11 @@ export function registerDisputesReadRoutes(app, deps) {
218
216
  });
219
217
  }
220
218
  for (const ev of defendantEvidence)
221
- events.push(evToEvent(ev, 'defendant'));
219
+ events.push(await evToEvent(ev, 'defendant'));
222
220
  // 4) party evidence (物流等)
223
221
  for (const ev of partyEvidence) {
224
222
  const r = orderRole(ev.uploader_id);
225
- events.push(evToEvent(ev, r === 'unknown' ? 'party' : r));
223
+ events.push(await evToEvent(ev, r === 'unknown' ? 'party' : r));
226
224
  }
227
225
  // 5) evidence requests + submitted_items
228
226
  for (const r of evidenceRequests) {
@@ -231,7 +229,7 @@ export function registerDisputesReadRoutes(app, deps) {
231
229
  evidenceTypes = JSON.parse(String(r.evidence_types || '[]'));
232
230
  }
233
231
  catch { }
234
- loadUser(r.requested_from_id);
232
+ await loadUser(r.requested_from_id);
235
233
  events.push({
236
234
  id: `req-${r.id}`,
237
235
  type: 'evidence_request',
@@ -252,14 +250,14 @@ export function registerDisputesReadRoutes(app, deps) {
252
250
  const submitted = (r.submitted_items || []);
253
251
  for (const si of submitted) {
254
252
  const role = orderRole(si.uploader_id);
255
- const ev = evToEvent(si, role === 'unknown' ? 'party' : role);
253
+ const ev = await evToEvent(si, role === 'unknown' ? 'party' : role);
256
254
  ev.meta = { ...(ev.meta || {}), in_response_to: r.id };
257
255
  events.push(ev);
258
256
  }
259
257
  }
260
258
  // 6) ruling — 从 order_events 签名链取
261
259
  try {
262
- const chainRows = db.prepare(`SELECT actor_id, signed_at, payload_json FROM order_events WHERE order_id = ? ORDER BY seq ASC`).all(dispute.order_id);
260
+ const chainRows = await dbAll(`SELECT actor_id, signed_at, payload_json FROM order_events WHERE order_id = ? ORDER BY seq ASC`, [dispute.order_id]);
263
261
  for (const row of chainRows) {
264
262
  let payload = {};
265
263
  try {
@@ -270,7 +268,7 @@ export function registerDisputesReadRoutes(app, deps) {
270
268
  }
271
269
  const extra = (payload.extra || {});
272
270
  if (extra.action === 'arbitration_ruling' && extra.dispute_id === dispute.id) {
273
- loadUser(row.actor_id);
271
+ await loadUser(row.actor_id);
274
272
  events.push({
275
273
  id: `rule-${row.signed_at}`,
276
274
  type: 'ruling',
@@ -324,12 +322,12 @@ export function registerDisputesReadRoutes(app, deps) {
324
322
  });
325
323
  });
326
324
  // 当事人 + 仲裁员可查(meta only,blob 单独拉)
327
- app.get('/api/disputes/:id/evidence-list', (req, res) => {
325
+ app.get('/api/disputes/:id/evidence-list', async (req, res) => {
328
326
  const user = auth(req, res);
329
327
  if (!user)
330
328
  return;
331
329
  try {
332
- const rows = listEvidenceFiles(db, req.params.id, user.id);
330
+ const rows = await listEvidenceFiles(db, req.params.id, user.id);
333
331
  res.json(rows);
334
332
  }
335
333
  catch (e) {
@@ -338,21 +336,17 @@ export function registerDisputesReadRoutes(app, deps) {
338
336
  }
339
337
  });
340
338
  // 涉案三方(仲裁员选择发证据请求的对象)
341
- app.get('/api/disputes/:id/parties', (req, res) => {
339
+ app.get('/api/disputes/:id/parties', async (req, res) => {
342
340
  const user = auth(req, res);
343
341
  if (!user)
344
342
  return;
345
- const dispute = getDisputeDetails(db, req.params.id);
343
+ const dispute = await getDisputeDetails(db, req.params.id);
346
344
  if (!dispute)
347
345
  return void res.status(404).json({ error: '争议不存在' });
348
- const order = db.prepare('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?')
349
- .get(dispute.order_id);
346
+ const order = await dbOne('SELECT buyer_id, seller_id, logistics_id FROM orders WHERE id = ?', [dispute.order_id]);
350
347
  const partyIds = [dispute.initiator_id, dispute.defendant_id, order?.logistics_id].filter(Boolean);
351
348
  const uniqueIds = [...new Set(partyIds)];
352
- const parties = uniqueIds.map(id => {
353
- const u = db.prepare('SELECT id, name, role FROM users WHERE id = ?').get(id);
354
- return u;
355
- }).filter(Boolean);
349
+ const parties = (await Promise.all(uniqueIds.map(id => dbOne('SELECT id, name, role FROM users WHERE id = ?', [id])))).filter(Boolean);
356
350
  res.json(parties);
357
351
  });
358
352
  }