@seasonkoh/webaz 0.1.23 → 0.1.25

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 (187) hide show
  1. package/README.md +2 -0
  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 +198 -83
  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 +173 -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 +10 -2
  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-engine.js +109 -0
  35. package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
  36. package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
  37. package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
  38. package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
  39. package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
  40. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
  41. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
  42. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
  43. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
  44. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
  45. package/dist/pwa/acp-feed.js +13 -1
  46. package/dist/pwa/contract-fingerprint.js +2 -0
  47. package/dist/pwa/endpoint-actions.js +5 -1
  48. package/dist/pwa/goal-index.js +8 -8
  49. package/dist/pwa/human-presence.js +62 -0
  50. package/dist/pwa/public/app.js +575 -68
  51. package/dist/pwa/public/i18n.js +29 -20
  52. package/dist/pwa/public/index.html +1 -0
  53. package/dist/pwa/public/openapi.json +2 -2
  54. package/dist/pwa/rate-limit.js +22 -0
  55. package/dist/pwa/routes/account-deletion.js +15 -13
  56. package/dist/pwa/routes/addresses.js +10 -9
  57. package/dist/pwa/routes/admin-admins.js +13 -14
  58. package/dist/pwa/routes/admin-analytics.js +109 -69
  59. package/dist/pwa/routes/admin-catalog.js +13 -11
  60. package/dist/pwa/routes/admin-editor-picks.js +15 -10
  61. package/dist/pwa/routes/admin-events.js +5 -3
  62. package/dist/pwa/routes/admin-health.js +2 -1
  63. package/dist/pwa/routes/admin-moderation.js +26 -29
  64. package/dist/pwa/routes/admin-ops.js +22 -21
  65. package/dist/pwa/routes/admin-protocol-params.js +16 -19
  66. package/dist/pwa/routes/admin-reports.js +23 -21
  67. package/dist/pwa/routes/admin-tokenomics.js +26 -25
  68. package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
  69. package/dist/pwa/routes/admin-users-query.js +54 -53
  70. package/dist/pwa/routes/admin-verifier-flow.js +82 -41
  71. package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
  72. package/dist/pwa/routes/admin-wallet-ops.js +7 -5
  73. package/dist/pwa/routes/agent-buy.js +46 -22
  74. package/dist/pwa/routes/agent-governance.js +52 -56
  75. package/dist/pwa/routes/ai.js +7 -5
  76. package/dist/pwa/routes/analytics.js +43 -41
  77. package/dist/pwa/routes/anchors.js +19 -20
  78. package/dist/pwa/routes/announcements.js +13 -13
  79. package/dist/pwa/routes/arbitrator.js +97 -31
  80. package/dist/pwa/routes/auction.js +153 -114
  81. package/dist/pwa/routes/auth-login.js +6 -4
  82. package/dist/pwa/routes/auth-read.js +11 -9
  83. package/dist/pwa/routes/auth-register.js +35 -20
  84. package/dist/pwa/routes/auth-sessions.js +12 -11
  85. package/dist/pwa/routes/blocklist.js +16 -15
  86. package/dist/pwa/routes/build-feedback.js +10 -9
  87. package/dist/pwa/routes/build-reputation.js +6 -2
  88. package/dist/pwa/routes/build-tasks.js +45 -13
  89. package/dist/pwa/routes/buyer-feeds.js +27 -25
  90. package/dist/pwa/routes/cart.js +16 -15
  91. package/dist/pwa/routes/charity.js +212 -150
  92. package/dist/pwa/routes/chat.js +42 -43
  93. package/dist/pwa/routes/checkin-tasks.js +10 -9
  94. package/dist/pwa/routes/checkout-helpers.js +12 -10
  95. package/dist/pwa/routes/claim-initiators.js +34 -14
  96. package/dist/pwa/routes/claim-verify.js +86 -53
  97. package/dist/pwa/routes/claim-voting.js +43 -18
  98. package/dist/pwa/routes/contribution-identity.js +147 -0
  99. package/dist/pwa/routes/contribution-score.js +19 -0
  100. package/dist/pwa/routes/coupons.js +19 -16
  101. package/dist/pwa/routes/dashboards.js +18 -16
  102. package/dist/pwa/routes/dispute-cases.js +25 -24
  103. package/dist/pwa/routes/disputes-read.js +45 -51
  104. package/dist/pwa/routes/disputes-write.js +124 -61
  105. package/dist/pwa/routes/evidence.js +9 -9
  106. package/dist/pwa/routes/external-anchors.js +13 -12
  107. package/dist/pwa/routes/feedback.js +29 -33
  108. package/dist/pwa/routes/flash-sales.js +18 -16
  109. package/dist/pwa/routes/follows.js +25 -24
  110. package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
  111. package/dist/pwa/routes/governance-onboarding.js +70 -59
  112. package/dist/pwa/routes/group-buys.js +22 -22
  113. package/dist/pwa/routes/growth.js +33 -30
  114. package/dist/pwa/routes/import-product.js +12 -10
  115. package/dist/pwa/routes/kyc.js +9 -8
  116. package/dist/pwa/routes/leaderboard.js +20 -18
  117. package/dist/pwa/routes/listings.js +23 -22
  118. package/dist/pwa/routes/logistics.js +10 -8
  119. package/dist/pwa/routes/manifests.js +27 -27
  120. package/dist/pwa/routes/me-data.js +23 -21
  121. package/dist/pwa/routes/notifications.js +7 -6
  122. package/dist/pwa/routes/offers.js +30 -12
  123. package/dist/pwa/routes/orders-action.js +33 -17
  124. package/dist/pwa/routes/orders-create.js +75 -20
  125. package/dist/pwa/routes/orders-read.js +21 -20
  126. package/dist/pwa/routes/p2p-products.js +30 -18
  127. package/dist/pwa/routes/payments-governance.js +61 -56
  128. package/dist/pwa/routes/peers.js +9 -8
  129. package/dist/pwa/routes/pin-receipts.js +13 -13
  130. package/dist/pwa/routes/products-aliases.js +12 -10
  131. package/dist/pwa/routes/products-claims.js +36 -17
  132. package/dist/pwa/routes/products-create.js +53 -38
  133. package/dist/pwa/routes/products-crud.js +17 -16
  134. package/dist/pwa/routes/products-links.js +49 -26
  135. package/dist/pwa/routes/products-list.js +6 -4
  136. package/dist/pwa/routes/products-meta.js +40 -39
  137. package/dist/pwa/routes/products-update.js +19 -5
  138. package/dist/pwa/routes/profile-credentials.js +14 -16
  139. package/dist/pwa/routes/profile-identity.js +14 -13
  140. package/dist/pwa/routes/profile-location.js +7 -6
  141. package/dist/pwa/routes/profile-placement.js +19 -17
  142. package/dist/pwa/routes/profile-prefs.js +11 -11
  143. package/dist/pwa/routes/promoter.js +55 -49
  144. package/dist/pwa/routes/public-build-tasks.js +19 -0
  145. package/dist/pwa/routes/public-utils.js +108 -46
  146. package/dist/pwa/routes/push.js +16 -15
  147. package/dist/pwa/routes/ratings.js +30 -30
  148. package/dist/pwa/routes/recover-key.js +13 -12
  149. package/dist/pwa/routes/referral.js +37 -32
  150. package/dist/pwa/routes/reputation.js +3 -2
  151. package/dist/pwa/routes/returns.js +76 -73
  152. package/dist/pwa/routes/reviews.js +41 -18
  153. package/dist/pwa/routes/rewards-apply.js +16 -15
  154. package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
  155. package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
  156. package/dist/pwa/routes/rfqs.js +163 -85
  157. package/dist/pwa/routes/search.js +16 -14
  158. package/dist/pwa/routes/secondhand.js +25 -22
  159. package/dist/pwa/routes/seller-quota.js +24 -26
  160. package/dist/pwa/routes/share-redirects.js +59 -55
  161. package/dist/pwa/routes/shareables-interactions.js +34 -35
  162. package/dist/pwa/routes/shareables.js +55 -51
  163. package/dist/pwa/routes/shop-referral.js +57 -0
  164. package/dist/pwa/routes/shops.js +20 -18
  165. package/dist/pwa/routes/signaling.js +10 -9
  166. package/dist/pwa/routes/skill-market.js +16 -16
  167. package/dist/pwa/routes/skills.js +15 -14
  168. package/dist/pwa/routes/snf.js +14 -13
  169. package/dist/pwa/routes/tags.js +10 -9
  170. package/dist/pwa/routes/task-proposals.js +45 -0
  171. package/dist/pwa/routes/trial.js +69 -51
  172. package/dist/pwa/routes/trusted-kpi.js +20 -18
  173. package/dist/pwa/routes/url-claim.js +67 -28
  174. package/dist/pwa/routes/users-public.js +62 -60
  175. package/dist/pwa/routes/variants.js +12 -13
  176. package/dist/pwa/routes/verifier-user.js +61 -21
  177. package/dist/pwa/routes/verify-tasks.js +49 -25
  178. package/dist/pwa/routes/waitlist.js +16 -15
  179. package/dist/pwa/routes/wallet-read.js +74 -36
  180. package/dist/pwa/routes/wallet-write.js +12 -9
  181. package/dist/pwa/routes/webauthn.js +25 -26
  182. package/dist/pwa/routes/webhooks.js +26 -26
  183. package/dist/pwa/routes/welcome.js +45 -50
  184. package/dist/pwa/routes/wishlist-qa.js +29 -32
  185. package/dist/pwa/server.js +237 -81
  186. package/dist/version.js +1 -1
  187. package/package.json +47 -2
@@ -1,4 +1,5 @@
1
1
  import { recordRatingReputation } from '../../layer4-economics/L4-3-reputation/reputation-engine.js';
2
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
3
  const RATING_BLIND_DAYS = 14;
3
4
  function parseDim(v) {
4
5
  const n = Number(v);
@@ -7,20 +8,20 @@ function parseDim(v) {
7
8
  export function registerRatingsRoutes(app, deps) {
8
9
  const { db, generateId, auth, isTrustedRole, errorRes, broadcastSystemEvent } = deps;
9
10
  // buyer → seller 评价(一单一评,仅 completed 订单可评)
10
- app.post('/api/orders/:order_id/rating', (req, res) => {
11
+ app.post('/api/orders/:order_id/rating', async (req, res) => {
11
12
  const user = auth(req, res);
12
13
  if (!user)
13
14
  return;
14
15
  if (isTrustedRole(user))
15
16
  return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无购物功能');
16
- const order = db.prepare('SELECT id, buyer_id, seller_id, product_id, status FROM orders WHERE id = ?').get(req.params.order_id);
17
+ const order = await dbOne('SELECT id, buyer_id, seller_id, product_id, status FROM orders WHERE id = ?', [req.params.order_id]);
17
18
  if (!order)
18
19
  return void res.status(404).json({ error: '订单不存在' });
19
20
  if (order.buyer_id !== user.id)
20
21
  return void res.status(403).json({ error: '仅买家可评价' });
21
22
  if (order.status !== 'completed')
22
23
  return void res.status(400).json({ error: '订单完成后才能评价' });
23
- const existing = db.prepare('SELECT order_id FROM order_ratings WHERE order_id = ?').get(order.id);
24
+ const existing = await dbOne('SELECT order_id FROM order_ratings WHERE order_id = ?', [order.id]);
24
25
  if (existing)
25
26
  return void res.status(400).json({ error: '已评价过,每单仅可评一次' });
26
27
  const stars = Number(req.body?.stars);
@@ -55,18 +56,18 @@ export function registerRatingsRoutes(app, deps) {
55
56
  res.json({ success: true });
56
57
  });
57
58
  // seller → buyer 反向评价
58
- app.post('/api/orders/:order_id/buyer-rating', (req, res) => {
59
+ app.post('/api/orders/:order_id/buyer-rating', async (req, res) => {
59
60
  const user = auth(req, res);
60
61
  if (!user)
61
62
  return;
62
- const order = db.prepare('SELECT id, buyer_id, seller_id, status FROM orders WHERE id = ?').get(req.params.order_id);
63
+ const order = await dbOne('SELECT id, buyer_id, seller_id, status FROM orders WHERE id = ?', [req.params.order_id]);
63
64
  if (!order)
64
65
  return void res.status(404).json({ error: '订单不存在' });
65
66
  if (order.seller_id !== user.id)
66
67
  return void res.status(403).json({ error: '仅卖家可评价买家' });
67
68
  if (order.status !== 'completed')
68
69
  return void res.status(400).json({ error: '订单完成后才能评价' });
69
- const existing = db.prepare('SELECT order_id FROM buyer_ratings WHERE order_id = ?').get(order.id);
70
+ const existing = await dbOne('SELECT order_id FROM buyer_ratings WHERE order_id = ?', [order.id]);
70
71
  if (existing)
71
72
  return void res.status(400).json({ error: '已评价过,每单仅可评一次' });
72
73
  const stars = Number(req.body?.stars);
@@ -96,21 +97,21 @@ export function registerRatingsRoutes(app, deps) {
96
97
  res.json({ success: true });
97
98
  });
98
99
  // 查 seller → buyer 评价(双盲遮蔽:buyer 看不到,除非自己也评过 OR 窗口到期)
99
- app.get('/api/orders/:order_id/buyer-rating', (req, res) => {
100
+ app.get('/api/orders/:order_id/buyer-rating', async (req, res) => {
100
101
  const user = auth(req, res);
101
102
  if (!user)
102
103
  return;
103
- const order = db.prepare('SELECT buyer_id, seller_id FROM orders WHERE id = ?').get(req.params.order_id);
104
+ const order = await dbOne('SELECT buyer_id, seller_id FROM orders WHERE id = ?', [req.params.order_id]);
104
105
  if (!order)
105
106
  return void res.status(404).json({ error: '订单不存在' });
106
107
  if (order.buyer_id !== user.id && order.seller_id !== user.id) {
107
108
  return void res.status(403).json({ error: '无权查看' });
108
109
  }
109
- const br = db.prepare(`SELECT stars, comment, dim_payment_speed, dim_communication, dim_responsiveness, hidden_until, created_at FROM buyer_ratings WHERE order_id = ?`).get(req.params.order_id);
110
+ const br = await dbOne(`SELECT stars, comment, dim_payment_speed, dim_communication, dim_responsiveness, hidden_until, created_at FROM buyer_ratings WHERE order_id = ?`, [req.params.order_id]);
110
111
  if (!br)
111
112
  return void res.json({ item: null });
112
113
  const isBuyerView = order.buyer_id === user.id;
113
- const buyerAlsoRated = !!db.prepare(`SELECT order_id FROM order_ratings WHERE order_id = ?`).get(req.params.order_id);
114
+ const buyerAlsoRated = !!(await dbOne(`SELECT order_id FROM order_ratings WHERE order_id = ?`, [req.params.order_id]));
114
115
  const blindExpired = br.hidden_until && new Date(br.hidden_until) < new Date();
115
116
  if (isBuyerView && !buyerAlsoRated && !blindExpired) {
116
117
  return void res.json({ item: { masked: true, hidden_until: br.hidden_until, reason: 'blind_until_both_or_expire' } });
@@ -118,32 +119,32 @@ export function registerRatingsRoutes(app, deps) {
118
119
  res.json({ item: br });
119
120
  });
120
121
  // 查 buyer → seller 评价(双盲遮蔽:seller 视角同样)
121
- app.get('/api/orders/:order_id/rating', (req, res) => {
122
+ app.get('/api/orders/:order_id/rating', async (req, res) => {
122
123
  const user = auth(req, res);
123
124
  if (!user)
124
125
  return;
125
- const order = db.prepare('SELECT buyer_id, seller_id FROM orders WHERE id = ?').get(req.params.order_id);
126
+ const order = await dbOne('SELECT buyer_id, seller_id FROM orders WHERE id = ?', [req.params.order_id]);
126
127
  if (!order)
127
128
  return void res.status(404).json({ error: '订单不存在' });
128
129
  if (order.buyer_id !== user.id && order.seller_id !== user.id) {
129
130
  return void res.status(403).json({ error: '无权查看' });
130
131
  }
131
- const r = db.prepare('SELECT stars, comment, reply, replied_at, buyer_followup, buyer_followup_at, dim_quality, dim_speed, dim_service, hidden_until, created_at FROM order_ratings WHERE order_id = ?').get(req.params.order_id);
132
+ const r = await dbOne('SELECT stars, comment, reply, replied_at, buyer_followup, buyer_followup_at, dim_quality, dim_speed, dim_service, hidden_until, created_at FROM order_ratings WHERE order_id = ?', [req.params.order_id]);
132
133
  if (!r)
133
134
  return void res.json({ item: null });
134
135
  const isSellerView = order.seller_id === user.id;
135
- const sellerAlsoRated = !!db.prepare(`SELECT order_id FROM buyer_ratings WHERE order_id = ?`).get(req.params.order_id);
136
+ const sellerAlsoRated = !!(await dbOne(`SELECT order_id FROM buyer_ratings WHERE order_id = ?`, [req.params.order_id]));
136
137
  const blindExpired = r.hidden_until && new Date(r.hidden_until) < new Date();
137
138
  if (isSellerView && !sellerAlsoRated && !blindExpired) {
138
139
  return void res.json({ item: { masked: true, hidden_until: r.hidden_until, reason: 'blind_until_both_or_expire' } });
139
140
  }
140
141
  res.json({ item: r });
141
142
  });
142
- app.post('/api/orders/:order_id/rating/reply', (req, res) => {
143
+ app.post('/api/orders/:order_id/rating/reply', async (req, res) => {
143
144
  const user = auth(req, res);
144
145
  if (!user)
145
146
  return;
146
- const r = db.prepare('SELECT seller_id, reply FROM order_ratings WHERE order_id = ?').get(req.params.order_id);
147
+ const r = await dbOne('SELECT seller_id, reply FROM order_ratings WHERE order_id = ?', [req.params.order_id]);
147
148
  if (!r)
148
149
  return void res.status(404).json({ error: '该订单暂无评价' });
149
150
  if (r.seller_id !== user.id)
@@ -153,15 +154,15 @@ export function registerRatingsRoutes(app, deps) {
153
154
  const reply = req.body?.reply ? String(req.body.reply).slice(0, 500) : null;
154
155
  if (!reply)
155
156
  return void res.status(400).json({ error: '回复不能为空' });
156
- db.prepare(`UPDATE order_ratings SET reply = ?, replied_at = datetime('now') WHERE order_id = ?`).run(reply, req.params.order_id);
157
+ await dbRun(`UPDATE order_ratings SET reply = ?, replied_at = datetime('now') WHERE order_id = ?`, [reply, req.params.order_id]);
157
158
  res.json({ success: true });
158
159
  });
159
160
  // W3 买家追问 — 在卖家 reply 后可追问一次
160
- app.post('/api/orders/:order_id/rating/followup', (req, res) => {
161
+ app.post('/api/orders/:order_id/rating/followup', async (req, res) => {
161
162
  const user = auth(req, res);
162
163
  if (!user)
163
164
  return;
164
- const r = db.prepare('SELECT buyer_id, reply, buyer_followup FROM order_ratings WHERE order_id = ?').get(req.params.order_id);
165
+ const r = await dbOne('SELECT buyer_id, reply, buyer_followup FROM order_ratings WHERE order_id = ?', [req.params.order_id]);
165
166
  if (!r)
166
167
  return void res.status(404).json({ error: '该订单暂无评价' });
167
168
  if (r.buyer_id !== user.id)
@@ -173,15 +174,14 @@ export function registerRatingsRoutes(app, deps) {
173
174
  const followup = req.body?.followup ? String(req.body.followup).trim().slice(0, 200) : '';
174
175
  if (followup.length < 2)
175
176
  return void res.status(400).json({ error: '追问内容至少 2 字' });
176
- db.prepare(`UPDATE order_ratings SET buyer_followup = ?, buyer_followup_at = datetime('now') WHERE order_id = ?`)
177
- .run(followup, req.params.order_id);
177
+ await dbRun(`UPDATE order_ratings SET buyer_followup = ?, buyer_followup_at = datetime('now') WHERE order_id = ?`, [followup, req.params.order_id]);
178
178
  res.json({ success: true });
179
179
  });
180
180
  // 公开:商品评价 + 聚合(仅展示双盲已揭晓的)
181
- app.get('/api/products/:product_id/ratings', (req, res) => {
181
+ app.get('/api/products/:product_id/ratings', async (req, res) => {
182
182
  const limit = Math.min(50, Math.max(1, Number(req.query.limit) || 20));
183
183
  const blindOpen = `(EXISTS (SELECT 1 FROM buyer_ratings br WHERE br.order_id = r.order_id) OR r.hidden_until IS NULL OR datetime(r.hidden_until) <= datetime('now'))`;
184
- const rows = db.prepare(`
184
+ const rows = await dbAll(`
185
185
  SELECT r.stars, r.comment, r.reply, r.replied_at, r.buyer_followup, r.buyer_followup_at, r.created_at,
186
186
  r.dim_quality, r.dim_speed, r.dim_service,
187
187
  u.name as buyer_name, u.handle as buyer_handle
@@ -189,8 +189,8 @@ export function registerRatingsRoutes(app, deps) {
189
189
  JOIN users u ON u.id = r.buyer_id
190
190
  WHERE r.product_id = ? AND ${blindOpen}
191
191
  ORDER BY r.created_at DESC LIMIT ?
192
- `).all(req.params.product_id, limit);
193
- const agg = db.prepare(`
192
+ `, [req.params.product_id, limit]);
193
+ const agg = await dbOne(`
194
194
  SELECT COUNT(*) as cnt, COALESCE(AVG(stars), 0) as avg_stars,
195
195
  SUM(CASE WHEN stars = 5 THEN 1 ELSE 0 END) as s5,
196
196
  SUM(CASE WHEN stars = 4 THEN 1 ELSE 0 END) as s4,
@@ -198,13 +198,13 @@ export function registerRatingsRoutes(app, deps) {
198
198
  SUM(CASE WHEN stars = 2 THEN 1 ELSE 0 END) as s2,
199
199
  SUM(CASE WHEN stars = 1 THEN 1 ELSE 0 END) as s1
200
200
  FROM order_ratings r WHERE product_id = ? AND ${blindOpen}
201
- `).get(req.params.product_id);
201
+ `, [req.params.product_id]);
202
202
  res.json({ items: rows, agg });
203
203
  });
204
204
  // 公开:卖家评价聚合(卖家主页)
205
- app.get('/api/sellers/:seller_id/ratings', (req, res) => {
205
+ app.get('/api/sellers/:seller_id/ratings', async (req, res) => {
206
206
  const limit = Math.min(50, Math.max(1, Number(req.query.limit) || 20));
207
- const rows = db.prepare(`
207
+ const rows = await dbAll(`
208
208
  SELECT r.stars, r.comment, r.reply, r.replied_at, r.buyer_followup, r.buyer_followup_at, r.created_at, r.product_id,
209
209
  p.title as product_title,
210
210
  u.name as buyer_name, u.handle as buyer_handle
@@ -213,8 +213,8 @@ export function registerRatingsRoutes(app, deps) {
213
213
  JOIN users u ON u.id = r.buyer_id
214
214
  WHERE r.seller_id = ?
215
215
  ORDER BY r.created_at DESC LIMIT ?
216
- `).all(req.params.seller_id, limit);
217
- const agg = db.prepare(`SELECT COUNT(*) as cnt, COALESCE(AVG(stars), 0) as avg_stars FROM order_ratings WHERE seller_id = ?`).get(req.params.seller_id);
216
+ `, [req.params.seller_id, limit]);
217
+ const agg = await dbOne(`SELECT COUNT(*) as cnt, COALESCE(AVG(stars), 0) as avg_stars FROM order_ratings WHERE seller_id = ?`, [req.params.seller_id]);
218
218
  res.json({ items: rows, agg });
219
219
  });
220
220
  }
@@ -1,8 +1,10 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerRecoverKeyRoutes(app, deps) {
2
- const { db, internalAuditorId, issueCode, findActiveCode, CODE_TTL_MIN, MAX_CODE_ATTEMPTS } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { internalAuditorId, issueCode, findActiveCode, CODE_TTL_MIN, MAX_CODE_ATTEMPTS } = deps;
3
5
  // IP 级速率(5/min)— 防爆破列举账户
4
6
  const recoverKeyHits = new Map();
5
- app.post('/api/recover-key', (req, res) => {
7
+ app.post('/api/recover-key', async (req, res) => {
6
8
  const ip = req.ip || '';
7
9
  if (ip) {
8
10
  const now = Date.now();
@@ -24,7 +26,7 @@ export function registerRecoverKeyRoutes(app, deps) {
24
26
  const { name } = req.body;
25
27
  if (!name?.trim())
26
28
  return void res.json({ error: '请填写注册时使用的名称' });
27
- const rows = db.prepare("SELECT name, role, api_key, email, phone, created_at FROM users WHERE name = ? AND id NOT IN ('sys_protocol', ?)").all(name.trim(), internalAuditorId);
29
+ const rows = await dbAll("SELECT name, role, api_key, email, phone, created_at FROM users WHERE name = ? AND id NOT IN ('sys_protocol', ?)", [name.trim(), internalAuditorId]);
28
30
  if (rows.length === 0)
29
31
  return void res.json({ error: '未找到该名称的账号' });
30
32
  const mask = (s) => s && s.length > 8 ? `${s.slice(0, 4)}…${s.slice(-4)}` : s;
@@ -54,16 +56,16 @@ export function registerRecoverKeyRoutes(app, deps) {
54
56
  });
55
57
  });
56
58
  // 步骤 1:发送验证码到已绑定邮箱(防泄露:找没找到都同响应)
57
- app.post('/api/recover-key/start', (req, res) => {
59
+ app.post('/api/recover-key/start', async (req, res) => {
58
60
  const { name, email } = req.body;
59
61
  if (!name?.trim() || !email?.trim())
60
62
  return void res.json({ error: '请填写名称和邮箱' });
61
63
  const target = email.trim().toLowerCase();
62
- const user = db.prepare(`
64
+ const user = await dbOne(`
63
65
  SELECT id, name, email FROM users
64
66
  WHERE name = ? AND email = ? AND email_verified = 1
65
67
  AND id NOT IN ('sys_protocol', ?) LIMIT 1
66
- `).get(name.trim(), target, internalAuditorId);
68
+ `, [name.trim(), target, internalAuditorId]);
67
69
  if (user)
68
70
  issueCode(user.id, 'email', target, 'recover_key');
69
71
  res.json({
@@ -73,7 +75,7 @@ export function registerRecoverKeyRoutes(app, deps) {
73
75
  });
74
76
  });
75
77
  // 步骤 2:提交验证码 → 返回完整 api_key
76
- app.post('/api/recover-key/confirm', (req, res) => {
78
+ app.post('/api/recover-key/confirm', async (req, res) => {
77
79
  const { name, email, code } = req.body;
78
80
  if (!name?.trim() || !email?.trim() || !code?.trim())
79
81
  return void res.json({ error: '请填写完整信息' });
@@ -81,20 +83,19 @@ export function registerRecoverKeyRoutes(app, deps) {
81
83
  const row = findActiveCode('email', target, 'recover_key');
82
84
  if (!row)
83
85
  return void res.json({ error: '验证码已过期或未发送,请重新开始' });
84
- const user = db.prepare(`SELECT id, name, api_key FROM users WHERE id = ?`).get(row.user_id);
86
+ const user = await dbOne(`SELECT id, name, api_key FROM users WHERE id = ?`, [row.user_id]);
85
87
  if (!user || user.name !== name.trim())
86
88
  return void res.json({ error: '名称与验证码不匹配' });
87
89
  if (String(row.code) !== code.trim()) {
88
90
  const attempts = row.attempts + 1;
89
91
  if (attempts >= MAX_CODE_ATTEMPTS) {
90
- db.prepare("UPDATE verification_codes SET attempts = ?, used_at = datetime('now') WHERE id = ?")
91
- .run(attempts, row.id);
92
+ await dbRun("UPDATE verification_codes SET attempts = ?, used_at = datetime('now') WHERE id = ?", [attempts, row.id]);
92
93
  return void res.json({ error: '错误次数过多,验证码已作废,请重新开始' });
93
94
  }
94
- db.prepare("UPDATE verification_codes SET attempts = ? WHERE id = ?").run(attempts, row.id);
95
+ await dbRun("UPDATE verification_codes SET attempts = ? WHERE id = ?", [attempts, row.id]);
95
96
  return void res.json({ error: `验证码错误(剩余 ${MAX_CODE_ATTEMPTS - attempts} 次)` });
96
97
  }
97
- db.prepare("UPDATE verification_codes SET used_at = datetime('now') WHERE id = ?").run(row.id);
98
+ await dbRun("UPDATE verification_codes SET used_at = datetime('now') WHERE id = ?", [row.id]);
98
99
  res.json({ success: true, api_key: user.api_key, name: user.name });
99
100
  });
100
101
  }
@@ -1,28 +1,31 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerReferralRoutes(app, deps) {
2
- const { db, auth, requireProtocolAdmin, logAdminAction, issueInviteSlot, inviteRotationLookup } = deps;
3
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, requireProtocolAdmin, logAdminAction, issueInviteSlot, inviteRotationLookup } = deps;
3
5
  // B-1: 个人邀请 dashboard
4
- app.get('/api/referral/me', (req, res) => {
6
+ app.get('/api/referral/me', async (req, res) => {
5
7
  const user = auth(req, res);
6
8
  if (!user)
7
9
  return;
8
10
  const code = user.permanent_code || null;
9
11
  // 我直接邀请的人
10
- const directInvitees = db.prepare(`
12
+ const directInvitees = await dbAll(`
11
13
  SELECT u.id, u.handle, u.name, u.role, u.created_at,
12
14
  (SELECT COUNT(*) FROM orders WHERE buyer_id = u.id AND status = 'completed') as completed_orders,
13
15
  (SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE buyer_id = u.id AND status = 'completed') as gmv
14
16
  FROM users u WHERE u.sponsor_id = ?
15
17
  ORDER BY u.created_at DESC LIMIT 50
16
- `).all(user.id);
18
+ `, [user.id]);
17
19
  // 推土机奖励 / 商品分享佣金(commission_records 按订单粒度)
18
- const earnings = db.prepare(`
20
+ const earnings = (await dbOne(`
19
21
  SELECT COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total FROM commission_records WHERE beneficiary_id = ?
20
- `).get(user.id);
21
- const todayEarnings = db.prepare(`SELECT COALESCE(SUM(amount), 0) as t FROM commission_records WHERE beneficiary_id = ? AND created_at > datetime('now', '-1 day')`).get(user.id).t;
22
- const monthEarnings = db.prepare(`SELECT COALESCE(SUM(amount), 0) as t FROM commission_records WHERE beneficiary_id = ? AND created_at > datetime('now', '-30 days')`).get(user.id).t;
22
+ `, [user.id]));
23
+ const todayEarnings = (await dbOne(`SELECT COALESCE(SUM(amount), 0) as t FROM commission_records WHERE beneficiary_id = ? AND created_at > datetime('now', '-1 day')`, [user.id])).t;
24
+ const monthEarnings = (await dbOne(`SELECT COALESCE(SUM(amount), 0) as t FROM commission_records WHERE beneficiary_id = ? AND created_at > datetime('now', '-30 days')`, [user.id])).t;
23
25
  res.json({
24
26
  invite_code: code,
25
- invite_link: code ? `${req.protocol}://${req.get('host')}/?ref=${code}` : null,
27
+ invite_link: code ? `${req.protocol}://${req.get('host')}/i/${code}` : null,
28
+ invite_unavailable_reason: code ? null : 'permanent_code_missing — refresh or contact support',
26
29
  direct_invitees_count: directInvitees.length,
27
30
  direct_invitees: directInvitees,
28
31
  earnings: {
@@ -34,8 +37,8 @@ export function registerReferralRoutes(app, deps) {
34
37
  });
35
38
  });
36
39
  // 公开邀请码轮询(开关 ON 时)
37
- app.post('/api/invite/rotate', (_req, res) => {
38
- const enabled = db.prepare("SELECT value FROM system_state WHERE key='invite_rotation_enabled'").get()?.value === '1';
40
+ app.post('/api/invite/rotate', async (_req, res) => {
41
+ const enabled = (await dbOne("SELECT value FROM system_state WHERE key='invite_rotation_enabled'"))?.value === '1';
39
42
  if (!enabled)
40
43
  return void res.status(403).json({ error: '邀请码获取暂未开放', enabled: false });
41
44
  const slot = issueInviteSlot();
@@ -45,20 +48,20 @@ export function registerReferralRoutes(app, deps) {
45
48
  res.json({ enabled: true, code: u.code });
46
49
  });
47
50
  // protocol 开关
48
- app.post('/api/admin/invite-rotation/toggle', (req, res) => {
51
+ app.post('/api/admin/invite-rotation/toggle', async (req, res) => {
49
52
  const admin = requireProtocolAdmin(req, res);
50
53
  if (!admin)
51
54
  return;
52
55
  const { enabled } = req.body;
53
56
  const v = enabled ? '1' : '0';
54
- db.prepare("INSERT OR REPLACE INTO system_state (key, value) VALUES ('invite_rotation_enabled', ?)").run(v);
57
+ await dbRun("INSERT OR REPLACE INTO system_state (key, value) VALUES ('invite_rotation_enabled', ?)", [v]);
55
58
  logAdminAction(admin.id, 'invite_rotation_toggle', 'system', 'invite_rotation_enabled', { value: v });
56
59
  res.json({ success: true, enabled: !!enabled });
57
60
  });
58
61
  // RFC-003 #1122: 生成商品分享链接(把 MCP webaz_share_link 的本地计算搬到服务端,
59
62
  // 让 MCP NETWORK 模式可代理)。RFC-002 §3.5 valuation-layer gate:需 rewards opt-in。
60
63
  // 与 PWA pickPreferredSide 对齐的 side 选择(team_count | pv_count)。
61
- app.get('/api/share-link', (req, res) => {
64
+ app.get('/api/share-link', async (req, res) => {
62
65
  const user = auth(req, res);
63
66
  if (!user)
64
67
  return;
@@ -67,16 +70,16 @@ export function registerReferralRoutes(app, deps) {
67
70
  const sideArg = String(req.query.side || 'auto');
68
71
  if (!productId)
69
72
  return void res.status(400).json({ error: 'product_id required', error_code: 'PRODUCT_ID_REQUIRED' });
70
- const optIn = db.prepare("SELECT rewards_opted_in FROM users WHERE id = ?").get(userId)?.rewards_opted_in ?? 0;
73
+ const optIn = (await dbOne("SELECT rewards_opted_in FROM users WHERE id = ?", [userId]))?.rewards_opted_in ?? 0;
71
74
  if (optIn !== 1) {
72
- const getParam = (key, def) => {
73
- const r = db.prepare("SELECT value FROM protocol_params WHERE key = ?").get(key);
75
+ const getParam = async (key, def) => {
76
+ const r = await dbOne("SELECT value FROM protocol_params WHERE key = ?", [key]);
74
77
  return r ? Number(r.value) : def;
75
78
  };
76
- const minOrders = getParam('rewards_opt_in.min_completed_orders', 1);
77
- const requirePasskey = getParam('rewards_opt_in.require_passkey', 1);
78
- const totalCompleted = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
79
- const passkeyCount = db.prepare("SELECT COUNT(*) as n FROM webauthn_credentials WHERE user_id = ?").get(userId).n;
79
+ const minOrders = await getParam('rewards_opt_in.min_completed_orders', 1);
80
+ const requirePasskey = await getParam('rewards_opt_in.require_passkey', 1);
81
+ const totalCompleted = (await dbOne("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'", [userId])).n;
82
+ const passkeyCount = (await dbOne("SELECT COUNT(*) as n FROM webauthn_credentials WHERE user_id = ?", [userId])).n;
80
83
  const missing = [];
81
84
  if (totalCompleted < minOrders)
82
85
  missing.push(`completed_orders ${totalCompleted}/${minOrders}`);
@@ -86,16 +89,16 @@ export function registerReferralRoutes(app, deps) {
86
89
  missing.push('application_not_submitted');
87
90
  return void res.status(403).json({
88
91
  error: 'rewards_opt_in_required',
89
- message: 'Share-link generation is a valuation-layer action — requires builder-identity opt-in (RFC-002 §3.5)',
92
+ message: 'Share-link generation is a valuation-layer (rewards / share-link) action, NOT a contribution gate — requires rewards / share-commission opt-in (RFC-002 §3.5)',
90
93
  missing_requirements: missing,
91
94
  next_steps: [
92
- 'Open PWA #me → tap "申请共建身份 / Apply for builder identity"',
95
+ 'Open PWA #me → tap "申请分享分润 / Enable share-commission opt-in"',
93
96
  'Read the 8-second disclosure (cannot skip)',
94
97
  'Submit application — pre-checks run server-side',
95
98
  ],
96
99
  });
97
100
  }
98
- const product = db.prepare("SELECT id, title, price, commission_rate FROM products WHERE id = ? AND status='active'").get(productId);
101
+ const product = await dbOne("SELECT id, title, price, commission_rate FROM products WHERE id = ? AND status='active'", [productId]);
99
102
  if (!product)
100
103
  return void res.status(404).json({ error: '商品不存在或已下架', error_code: 'PRODUCT_NOT_FOUND' });
101
104
  let side = 'right';
@@ -103,14 +106,12 @@ export function registerReferralRoutes(app, deps) {
103
106
  side = sideArg;
104
107
  }
105
108
  else {
106
- const u = db.prepare("SELECT placement_pref, total_left_pv, total_right_pv, left_count, right_count FROM users WHERE id = ?")
107
- .get(userId);
109
+ const u = await dbOne("SELECT placement_pref, total_left_pv, total_right_pv, left_count, right_count FROM users WHERE id = ?", [userId]);
108
110
  const pref = u?.placement_pref || 'team_count';
109
111
  if (pref === 'pv_count') {
110
112
  const since = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' ');
111
- const w = db.prepare(`SELECT COALESCE(SUM(consumed_left_pv),0) AS l, COALESCE(SUM(consumed_right_pv),0) AS r
112
- FROM binary_score_records WHERE user_id = ? AND created_at >= ?`)
113
- .get(userId, since);
113
+ const w = (await dbOne(`SELECT COALESCE(SUM(consumed_left_pv),0) AS l, COALESCE(SUM(consumed_right_pv),0) AS r
114
+ FROM binary_score_records WHERE user_id = ? AND created_at >= ?`, [userId, since]));
114
115
  const leftPv = Number(u?.total_left_pv ?? 0) + Number(w.l);
115
116
  const rightPv = Number(u?.total_right_pv ?? 0) + Number(w.r);
116
117
  side = leftPv <= rightPv ? 'left' : 'right';
@@ -119,11 +120,15 @@ export function registerReferralRoutes(app, deps) {
119
120
  side = (Number(u?.left_count ?? 0) <= Number(u?.right_count ?? 0)) ? 'left' : 'right';
120
121
  }
121
122
  }
122
- const completed = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
123
- const override = db.prepare("SELECT l1_share_override FROM users WHERE id = ?").get(userId)?.l1_share_override ?? 0;
123
+ const completed = (await dbOne("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'", [userId])).n;
124
+ const override = (await dbOne("SELECT l1_share_override FROM users WHERE id = ?", [userId]))?.l1_share_override ?? 0;
124
125
  const canL1 = override === 1 || (override === 0 && completed > 0);
125
126
  const rate = Number(product.commission_rate ?? 0);
126
- const link = `/?ref=${userId}&side=${side}#order-product/${productId}`;
127
+ // share ref uses permanent_code ONLY — never the raw user_id; fail clearly if it's missing.
128
+ const refCode = (await dbOne("SELECT permanent_code FROM users WHERE id = ?", [userId]))?.permanent_code || null;
129
+ if (!refCode)
130
+ return void res.status(409).json({ error: '邀请码暂不可用,请刷新或联系支持', error_code: 'PERMANENT_CODE_MISSING' });
131
+ const link = `/?ref=${refCode}&side=${side}#order-product/${productId}`;
127
132
  res.json({
128
133
  product: { id: product.id, title: product.title, price: product.price, commission_rate: rate },
129
134
  share_link: link,
@@ -1,3 +1,4 @@
1
+ import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerReputationRoutes(app, deps) {
2
3
  const { db, auth, getReputation, getSellerMetrics } = deps;
3
4
  app.get('/api/reputation', (req, res) => {
@@ -16,9 +17,9 @@ export function registerReputationRoutes(app, deps) {
16
17
  metrics: getSellerMetrics(user.id),
17
18
  });
18
19
  });
19
- app.get('/api/reputation/:userId', (req, res) => {
20
+ app.get('/api/reputation/:userId', async (req, res) => {
20
21
  const rep = getReputation(db, req.params.userId);
21
- const decayRow = db.prepare(`SELECT last_decay_at FROM reputation_scores WHERE user_id = ?`).get(req.params.userId);
22
+ const decayRow = await dbOne(`SELECT last_decay_at FROM reputation_scores WHERE user_id = ?`, [req.params.userId]);
22
23
  res.json({
23
24
  level: rep.level,
24
25
  total_points: rep.total_points,