@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,3 +1,4 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  const GROWTH_TASK_CATALOG = [
2
3
  // 第 1 关:新手起步
3
4
  { id: 'first_purchase', chapter: 1,
@@ -59,8 +60,9 @@ const GROWTH_TASK_CATALOG = [
59
60
  desc_zh: '正式跻身分享达人', desc_en: 'Officially a share pro',
60
61
  evaluate: c => c.team_total >= 50 },
61
62
  ];
62
- function buildGrowthTaskCtx(db, userId) {
63
- const u = db.prepare("SELECT bio, search_anchor, default_address_json, default_address_text FROM users WHERE id = ?").get(userId);
63
+ // RFC-016: db 参数保留(签名兼容),内部走异步 seam(同实例,setSeamDb)
64
+ async function buildGrowthTaskCtx(_db, userId) {
65
+ const u = await dbOne("SELECT bio, search_anchor, default_address_json, default_address_text FROM users WHERE id = ?", [userId]);
64
66
  let line1 = null;
65
67
  try {
66
68
  const a = JSON.parse(u?.default_address_json || 'null');
@@ -69,21 +71,21 @@ function buildGrowthTaskCtx(db, userId) {
69
71
  catch { }
70
72
  if (!line1 && u?.default_address_text)
71
73
  line1 = u.default_address_text; // legacy 兜底
72
- const completed = db.prepare("SELECT COUNT(*) AS n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(userId).n;
73
- const l1 = db.prepare("SELECT COUNT(*) AS n FROM users WHERE sponsor_id = ?").get(userId).n;
74
- const teamTotal = db.prepare(`
74
+ const completed = (await dbOne("SELECT COUNT(*) AS n FROM orders WHERE buyer_id = ? AND status = 'completed'", [userId])).n;
75
+ const l1 = (await dbOne("SELECT COUNT(*) AS n FROM users WHERE sponsor_id = ?", [userId])).n;
76
+ const teamTotal = (await dbOne(`
75
77
  SELECT COUNT(*) AS n FROM users
76
78
  WHERE sponsor_id = ?
77
79
  OR sponsor_id IN (SELECT id FROM users WHERE sponsor_id = ?)
78
80
  OR sponsor_id IN (SELECT id FROM users WHERE sponsor_id IN (SELECT id FROM users WHERE sponsor_id = ?))
79
- `).get(userId, userId, userId).n;
80
- const grand = db.prepare("SELECT COALESCE(SUM(amount),0) AS s FROM commission_records WHERE beneficiary_id = ?").get(userId).s;
81
- const sCount = db.prepare("SELECT COUNT(*) AS n FROM shareables WHERE owner_id = ? AND status = 'active'").get(userId).n;
82
- const mCount = db.prepare("SELECT COUNT(*) AS n FROM manifest_registry WHERE owner_id = ? AND status = 'active'").get(userId).n;
83
- const pv = db.prepare("SELECT total_left_pv, total_right_pv FROM users WHERE id = ?").get(userId);
81
+ `, [userId, userId, userId])).n;
82
+ const grand = (await dbOne("SELECT COALESCE(SUM(amount),0) AS s FROM commission_records WHERE beneficiary_id = ?", [userId])).s;
83
+ const sCount = (await dbOne("SELECT COUNT(*) AS n FROM shareables WHERE owner_id = ? AND status = 'active'", [userId])).n;
84
+ const mCount = (await dbOne("SELECT COUNT(*) AS n FROM manifest_registry WHERE owner_id = ? AND status = 'active'", [userId])).n;
85
+ const pv = await dbOne("SELECT total_left_pv, total_right_pv FROM users WHERE id = ?", [userId]);
84
86
  const weakLeg = Math.min(Number(pv?.total_left_pv || 0), Number(pv?.total_right_pv || 0));
85
- const comm30 = db.prepare(`SELECT COALESCE(SUM(amount),0) AS s FROM commission_records WHERE beneficiary_id = ? AND created_at >= datetime('now','-30 days')`).get(userId).s;
86
- const waz30 = db.prepare(`SELECT COALESCE(SUM(waz_amount),0) AS s FROM binary_score_records WHERE user_id = ? AND settled_at >= datetime('now','-30 days')`).get(userId).s;
87
+ const comm30 = (await dbOne(`SELECT COALESCE(SUM(amount),0) AS s FROM commission_records WHERE beneficiary_id = ? AND created_at >= datetime('now','-30 days')`, [userId])).s;
88
+ const waz30 = (await dbOne(`SELECT COALESCE(SUM(waz_amount),0) AS s FROM binary_score_records WHERE user_id = ? AND settled_at >= datetime('now','-30 days')`, [userId])).s;
87
89
  return {
88
90
  userId,
89
91
  bio: u?.bio || null,
@@ -99,9 +101,9 @@ function buildGrowthTaskCtx(db, userId) {
99
101
  last_30_total: comm30 + waz30,
100
102
  };
101
103
  }
102
- function evaluateGrowthTasks(db, userId, lang = 'zh') {
103
- const ctx = buildGrowthTaskCtx(db, userId);
104
- const logs = db.prepare("SELECT task_id, status, claimed_at, completed_at FROM growth_task_log WHERE user_id = ?").all(userId);
104
+ async function evaluateGrowthTasks(_db, userId, lang = 'zh') {
105
+ const ctx = await buildGrowthTaskCtx(_db, userId);
106
+ const logs = await dbAll("SELECT task_id, status, claimed_at, completed_at FROM growth_task_log WHERE user_id = ?", [userId]);
105
107
  const logMap = new Map(logs.map(l => [l.task_id, l]));
106
108
  const out = [];
107
109
  for (const t of GROWTH_TASK_CATALOG) {
@@ -111,8 +113,8 @@ function evaluateGrowthTasks(db, userId, lang = 'zh') {
111
113
  let completed_at = log?.completed_at || null;
112
114
  if (done) {
113
115
  if (!log || log.status !== 'completed') {
114
- db.prepare(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
115
- VALUES (?,?,?,?,datetime('now'))`).run(userId, t.id, 'completed', log?.claimed_at || null);
116
+ await dbRun(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
117
+ VALUES (?,?,?,?,datetime('now'))`, [userId, t.id, 'completed', log?.claimed_at || null]);
116
118
  completed_at = new Date().toISOString().slice(0, 19).replace('T', ' ');
117
119
  }
118
120
  status = 'completed';
@@ -147,53 +149,54 @@ function evaluateGrowthTasks(db, userId, lang = 'zh') {
147
149
  return { tasks: out, summary };
148
150
  }
149
151
  export function registerGrowthRoutes(app, deps) {
152
+ // db 仍在 destructure 中(传给 evaluateGrowthTasks 的签名);本文件 handler 走 RFC-016 异步 seam
150
153
  const { db, auth } = deps;
151
- app.get('/api/growth/tasks', (req, res) => {
154
+ app.get('/api/growth/tasks', async (req, res) => {
152
155
  const user = auth(req, res);
153
156
  if (!user)
154
157
  return;
155
158
  const lang = String(req.headers['accept-language'] || '').startsWith('en') ? 'en' : 'zh';
156
- res.json(evaluateGrowthTasks(db, user.id, lang));
159
+ res.json(await evaluateGrowthTasks(db, user.id, lang));
157
160
  });
158
- app.post('/api/growth/tasks/:id/claim', (req, res) => {
161
+ app.post('/api/growth/tasks/:id/claim', async (req, res) => {
159
162
  const user = auth(req, res);
160
163
  if (!user)
161
164
  return;
162
165
  const taskId = req.params.id;
163
166
  if (!GROWTH_TASK_CATALOG.find(t => t.id === taskId))
164
167
  return void res.json({ error: 'unknown task' });
165
- const existing = db.prepare("SELECT status, completed_at FROM growth_task_log WHERE user_id = ? AND task_id = ?").get(user.id, taskId);
168
+ const existing = await dbOne("SELECT status, completed_at FROM growth_task_log WHERE user_id = ? AND task_id = ?", [user.id, taskId]);
166
169
  if (existing?.status === 'completed')
167
170
  return void res.json({ error: '该任务已完成' });
168
- db.prepare(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
169
- VALUES (?,?,?,datetime('now'),NULL)`).run(user.id, taskId, 'claimed');
171
+ await dbRun(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
172
+ VALUES (?,?,?,datetime('now'),NULL)`, [user.id, taskId, 'claimed']);
170
173
  res.json({ success: true, status: 'claimed' });
171
174
  });
172
- app.post('/api/growth/tasks/:id/skip', (req, res) => {
175
+ app.post('/api/growth/tasks/:id/skip', async (req, res) => {
173
176
  const user = auth(req, res);
174
177
  if (!user)
175
178
  return;
176
179
  const taskId = req.params.id;
177
180
  if (!GROWTH_TASK_CATALOG.find(t => t.id === taskId))
178
181
  return void res.json({ error: 'unknown task' });
179
- const existing = db.prepare("SELECT status FROM growth_task_log WHERE user_id = ? AND task_id = ?").get(user.id, taskId);
182
+ const existing = await dbOne("SELECT status FROM growth_task_log WHERE user_id = ? AND task_id = ?", [user.id, taskId]);
180
183
  if (existing?.status === 'completed')
181
184
  return void res.json({ error: '该任务已完成,无法跳过' });
182
- db.prepare(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
183
- VALUES (?,?,?,NULL,NULL)`).run(user.id, taskId, 'skipped');
185
+ await dbRun(`INSERT OR REPLACE INTO growth_task_log (user_id, task_id, status, claimed_at, completed_at)
186
+ VALUES (?,?,?,NULL,NULL)`, [user.id, taskId, 'skipped']);
184
187
  res.json({ success: true, status: 'skipped' });
185
188
  });
186
- app.post('/api/growth/tasks/:id/reset', (req, res) => {
189
+ app.post('/api/growth/tasks/:id/reset', async (req, res) => {
187
190
  const user = auth(req, res);
188
191
  if (!user)
189
192
  return;
190
193
  const taskId = req.params.id;
191
194
  if (!GROWTH_TASK_CATALOG.find(t => t.id === taskId))
192
195
  return void res.json({ error: 'unknown task' });
193
- const existing = db.prepare("SELECT status FROM growth_task_log WHERE user_id = ? AND task_id = ?").get(user.id, taskId);
196
+ const existing = await dbOne("SELECT status FROM growth_task_log WHERE user_id = ? AND task_id = ?", [user.id, taskId]);
194
197
  if (existing?.status === 'completed')
195
198
  return void res.json({ error: '已完成任务无法重置' });
196
- db.prepare("DELETE FROM growth_task_log WHERE user_id = ? AND task_id = ?").run(user.id, taskId);
199
+ await dbRun("DELETE FROM growth_task_log WHERE user_id = ? AND task_id = ?", [user.id, taskId]);
197
200
  res.json({ success: true, status: 'available' });
198
201
  });
199
202
  }
@@ -1,5 +1,7 @@
1
+ import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerImportProductRoutes(app, deps) {
2
- const { db, auth, safeFetch, rateLimitOk, generateId, checkSellerCanList, anthropic, AnthropicCtor, FREE_IMPORT_LIMIT } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
4
+ const { auth, safeFetch, rateLimitOk, generateId, checkSellerCanList, anthropic, AnthropicCtor, FREE_IMPORT_LIMIT } = deps;
3
5
  app.post('/api/import-product', async (req, res) => {
4
6
  const user = auth(req, res);
5
7
  if (!user)
@@ -14,19 +16,19 @@ export function registerImportProductRoutes(app, deps) {
14
16
  return void res.json({ error: '请提供商品链接' });
15
17
  if (!rateLimitOk(req.ip || 'unknown', 6, 60_000))
16
18
  return void res.status(429).json({ error: '请求过于频繁,请稍后再试' });
17
- const selfClaim = db.prepare(`
19
+ const selfClaim = await dbOne(`
18
20
  SELECT p.id as product_id, p.title FROM product_external_links pel
19
21
  JOIN products p ON pel.product_id = p.id
20
22
  WHERE pel.url = ? AND p.seller_id = ?
21
- `).get(url, user.id);
23
+ `, [url, user.id]);
22
24
  if (selfClaim) {
23
25
  return void res.json({ error: `您已上架过来自此链接的商品「${selfClaim.title}」,不能重复关联相同外部链接` });
24
26
  }
25
- const otherClaim = db.prepare(`
27
+ const otherClaim = await dbOne(`
26
28
  SELECT p.id as product_id FROM product_external_links pel
27
29
  JOIN products p ON pel.product_id = p.id
28
30
  WHERE pel.url = ? AND pel.verified = 1 AND p.seller_id != ?
29
- `).get(url, user.id);
31
+ `, [url, user.id]);
30
32
  if (otherClaim) {
31
33
  return void res.json({
32
34
  conflict: true,
@@ -36,7 +38,7 @@ export function registerImportProductRoutes(app, deps) {
36
38
  }
37
39
  const usingOwnKey = typeof user_api_key === 'string' && user_api_key.trim().startsWith('sk-ant-');
38
40
  if (!usingOwnKey) {
39
- const todayCount = db.prepare(`SELECT COUNT(*) as cnt FROM import_logs WHERE user_id = ? AND created_at >= datetime('now', '-1 day')`).get(user.id).cnt;
41
+ const todayCount = (await dbOne(`SELECT COUNT(*) as cnt FROM import_logs WHERE user_id = ? AND created_at >= datetime('now', '-1 day')`, [user.id])).cnt;
40
42
  if (todayCount >= FREE_IMPORT_LIMIT) {
41
43
  return void res.json({
42
44
  error: `今日免费导入次数已用完(${FREE_IMPORT_LIMIT} 次/天)。请在导入面板填入你自己的 Anthropic API Key 以继续使用。`,
@@ -67,10 +69,10 @@ export function registerImportProductRoutes(app, deps) {
67
69
  return void res.json({ error: '链接指向私网/localhost 或经 redirect 触达内部地址,已拦截' });
68
70
  return void res.json({ error: `无法访问该链接:${msg}` });
69
71
  }
70
- const avgPrices = db.prepare(`
72
+ const avgPrices = await dbAll(`
71
73
  SELECT category, AVG(price) as avg_price, MIN(price) as min_price, MAX(price) as max_price, COUNT(*) as cnt
72
74
  FROM products WHERE status = 'active' GROUP BY category
73
- `).all();
75
+ `);
74
76
  const priceContext = avgPrices.map(r => `${r.category || '未分类'}:均价 ${r.avg_price?.toFixed(0)} WAZ,最低 ${r.min_price} WAZ,最高 ${r.max_price} WAZ(${r.cnt} 件商品)`).join('\n');
75
77
  const client = usingOwnKey
76
78
  ? new AnthropicCtor({ apiKey: user_api_key.trim() })
@@ -138,9 +140,9 @@ ${html}`,
138
140
  extracted.description = title;
139
141
  }
140
142
  if (!usingOwnKey) {
141
- db.prepare(`INSERT INTO import_logs (id, user_id) VALUES (?, ?)`).run(generateId('iml'), user.id);
143
+ await dbRun(`INSERT INTO import_logs (id, user_id) VALUES (?, ?)`, [generateId('iml'), user.id]);
142
144
  }
143
- const usedToday = usingOwnKey ? 0 : db.prepare(`SELECT COUNT(*) as cnt FROM import_logs WHERE user_id = ? AND created_at >= datetime('now', '-1 day')`).get(user.id).cnt;
145
+ const usedToday = usingOwnKey ? 0 : (await dbOne(`SELECT COUNT(*) as cnt FROM import_logs WHERE user_id = ? AND created_at >= datetime('now', '-1 day')`, [user.id])).cnt;
144
146
  res.json({
145
147
  success: true,
146
148
  source_url: url,
@@ -1,8 +1,10 @@
1
1
  import { createHash } from 'crypto';
2
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
2
3
  export function registerKycRoutes(app, deps) {
3
- const { db, auth, MASTER_SEED } = deps;
4
+ // db 已走 RFC-016 异步 seam(dbOne/dbRun),不再直接用 deps.db
5
+ const { auth, MASTER_SEED } = deps;
4
6
  const VALID_KYC_ID_TYPES = new Set(['passport', 'national_id', 'driver_license', 'other']);
5
- app.post('/api/kyc/submit', (req, res) => {
7
+ app.post('/api/kyc/submit', async (req, res) => {
6
8
  const user = auth(req, res);
7
9
  if (!user)
8
10
  return;
@@ -17,24 +19,23 @@ export function registerKycRoutes(app, deps) {
17
19
  const idHash = createHash('sha256').update(idStr + MASTER_SEED).digest('hex');
18
20
  const idLast4 = idStr.slice(-4);
19
21
  // 已存在?必须先 admin reject 或允许重新提交
20
- const existing = db.prepare('SELECT status FROM kyc_records WHERE user_id = ?').get(user.id);
22
+ const existing = await dbOne('SELECT status FROM kyc_records WHERE user_id = ?', [user.id]);
21
23
  if (existing && existing.status === 'approved')
22
24
  return void res.status(400).json({ error: '已通过认证,无需重复提交' });
23
25
  if (existing && existing.status === 'pending')
24
26
  return void res.status(400).json({ error: '审核中,请耐心等待' });
25
- db.prepare(`INSERT INTO kyc_records (user_id, real_name, id_type, id_number_hash, id_number_last4, status, submitted_at)
27
+ await dbRun(`INSERT INTO kyc_records (user_id, real_name, id_type, id_number_hash, id_number_last4, status, submitted_at)
26
28
  VALUES (?,?,?,?,?,'pending', datetime('now'))
27
29
  ON CONFLICT(user_id) DO UPDATE SET real_name = excluded.real_name, id_type = excluded.id_type,
28
30
  id_number_hash = excluded.id_number_hash, id_number_last4 = excluded.id_number_last4,
29
- status = 'pending', reject_reason = NULL, submitted_at = datetime('now')`)
30
- .run(user.id, String(real_name).trim().slice(0, 60), String(id_type), idHash, idLast4);
31
+ status = 'pending', reject_reason = NULL, submitted_at = datetime('now')`, [user.id, String(real_name).trim().slice(0, 60), String(id_type), idHash, idLast4]);
31
32
  res.json({ success: true, status: 'pending' });
32
33
  });
33
- app.get('/api/kyc/me', (req, res) => {
34
+ app.get('/api/kyc/me', async (req, res) => {
34
35
  const user = auth(req, res);
35
36
  if (!user)
36
37
  return;
37
- const row = db.prepare('SELECT status, id_type, id_number_last4, reject_reason, submitted_at, reviewed_at FROM kyc_records WHERE user_id = ?').get(user.id);
38
+ const row = await dbOne('SELECT status, id_type, id_number_last4, reject_reason, submitted_at, reviewed_at FROM kyc_records WHERE user_id = ?', [user.id]);
38
39
  res.json({ kyc: row || null });
39
40
  });
40
41
  }
@@ -1,7 +1,9 @@
1
+ import { dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerLeaderboardRoutes(app, deps) {
2
- const { db, internalAuditorId, rateLimitOk } = deps;
3
+ // db 已走 RFC-016 异步 seam(dbAll),不再直接用 deps.db
4
+ const { internalAuditorId, rateLimitOk } = deps;
3
5
  const LB_RATE = 60; // 每 IP/分钟 60 次 — 公开端点防 DoS
4
- app.get('/api/leaderboard', (req, res) => {
6
+ app.get('/api/leaderboard', async (req, res) => {
5
7
  const ip = req.ip || 'unknown';
6
8
  if (!rateLimitOk(`lb:${ip}`, LB_RATE, 60_000))
7
9
  return void res.status(429).json({ error: 'rate-limited' });
@@ -10,7 +12,7 @@ export function registerLeaderboardRoutes(app, deps) {
10
12
  if (kind === 'products') {
11
13
  // recommend_count 严格语义:完成购买 + 4 星+评价 + 去重 buyer_id(一买家计 1)
12
14
  // 排序权重也用 recommend_count 替代旧 unique_sharer_count(任何人分享)
13
- const rows = db.prepare(`
15
+ const rows = await dbAll(`
14
16
  SELECT p.id, p.title, p.price, p.total_likes, p.completion_count,
15
17
  u.handle as seller_handle, u.name as seller_name,
16
18
  (SELECT COUNT(DISTINCT buyer_id) FROM order_ratings r
@@ -24,12 +26,12 @@ export function registerLeaderboardRoutes(app, deps) {
24
26
  WHERE p.status = 'active' AND p.stock > 0
25
27
  ORDER BY rank_score DESC, p.id DESC
26
28
  LIMIT ?
27
- `).all(limit);
29
+ `, [limit]);
28
30
  return void res.json({ kind, items: rows });
29
31
  }
30
32
  if (kind === 'creators') {
31
33
  // 创作者维度:聚合自己 shareables 的总点赞 + 关联商品总数
32
- const rows = db.prepare(`
34
+ const rows = await dbAll(`
33
35
  SELECT u.id, u.handle, u.name, u.region,
34
36
  COUNT(DISTINCT s.related_product_id) as products_shared,
35
37
  COUNT(s.id) as shareable_count,
@@ -40,24 +42,24 @@ export function registerLeaderboardRoutes(app, deps) {
40
42
  GROUP BY u.id
41
43
  ORDER BY total_likes DESC, shareable_count DESC, u.id DESC
42
44
  LIMIT ?
43
- `).all(limit);
45
+ `, [limit]);
44
46
  return void res.json({ kind, items: rows });
45
47
  }
46
48
  // B-2: 用户排行 — top buyers / sellers / verifiers
47
49
  // 2026-05-23 隐私第一原理:移除 gmv 字段(运营状态私密,防过早 fork)
48
50
  if (kind === 'buyers') {
49
- const rows = db.prepare(`
51
+ const rows = await dbAll(`
50
52
  SELECT u.id, u.handle, u.name, u.region,
51
53
  COUNT(*) as orders_count
52
54
  FROM orders o JOIN users u ON u.id = o.buyer_id
53
55
  WHERE o.status = 'completed' AND u.id NOT IN ('sys_protocol', ?)
54
56
  GROUP BY u.id ORDER BY orders_count DESC, u.id DESC LIMIT ?
55
- `).all(internalAuditorId, limit);
57
+ `, [internalAuditorId, limit]);
56
58
  return void res.json({ kind, items: rows });
57
59
  }
58
60
  if (kind === 'sellers') {
59
61
  // 排序改为 评分主导(avg_rating × log(1+rating_count)),不再按 GMV
60
- const rows = db.prepare(`
62
+ const rows = await dbAll(`
61
63
  SELECT u.id, u.handle, u.name, u.region,
62
64
  COUNT(*) as orders_count,
63
65
  (SELECT COALESCE(AVG(stars), 0) FROM order_ratings WHERE seller_id = u.id) as avg_rating,
@@ -67,13 +69,13 @@ export function registerLeaderboardRoutes(app, deps) {
67
69
  GROUP BY u.id
68
70
  ORDER BY (avg_rating * (1.0 + log(1.0 + rating_count))) DESC, rating_count DESC, orders_count DESC
69
71
  LIMIT ?
70
- `).all(limit);
72
+ `, [limit]);
71
73
  return void res.json({ kind, items: rows });
72
74
  }
73
75
  if (kind === 'value_products') {
74
76
  // 2026-05-23 S5:极致性价比榜 — 按 value_badge=1 + 同类目 rank 排
75
77
  // 排序:rank 越小越靠前(同类目第 1 名最便宜),相同 rank 按 pct 折扣大优先
76
- const rows = db.prepare(`
78
+ const rows = await dbAll(`
77
79
  SELECT p.id, p.title, p.price, p.category,
78
80
  p.value_badge_rank, p.value_badge_pct, p.value_badge_at,
79
81
  p.completion_count, p.total_likes,
@@ -82,14 +84,14 @@ export function registerLeaderboardRoutes(app, deps) {
82
84
  LEFT JOIN users u ON u.id = p.seller_id
83
85
  WHERE p.value_badge = 1 AND p.status = 'active' AND p.stock > 0
84
86
  ORDER BY p.value_badge_rank ASC, p.value_badge_pct DESC LIMIT ?
85
- `).all(limit);
87
+ `, [limit]);
86
88
  return void res.json({ kind, items: rows });
87
89
  }
88
90
  if (kind === 'agents') {
89
91
  // 2026-05-22 AG1:Agent 评测竞赛榜单
90
92
  // 数据源:agent_reputation(trust_score + level)+ agent_call_log(30d 调用数)
91
93
  // 不暴露 api_key(隐私),只展示 user handle + 聚合指标
92
- const rows = db.prepare(`
94
+ const rows = await dbAll(`
93
95
  SELECT u.id, u.handle, u.name,
94
96
  MAX(ar.trust_score) as trust_score,
95
97
  MAX(ar.level) as level,
@@ -103,7 +105,7 @@ export function registerLeaderboardRoutes(app, deps) {
103
105
  GROUP BY u.id
104
106
  HAVING calls_30d > 0 OR trust_score > 0
105
107
  ORDER BY trust_score DESC, calls_30d DESC LIMIT ?
106
- `).all(limit);
108
+ `, [limit]);
107
109
  return void res.json({ kind, items: rows });
108
110
  }
109
111
  if (kind === 'arbitrators') {
@@ -114,7 +116,7 @@ export function registerLeaderboardRoutes(app, deps) {
114
116
  // 2026-06-03 #1080 audit: ORDER BY 改为 case_count desc + u.id tie-breaker
115
117
  // 移除 fairness_score 作为 secondary sort key — spec §3 禁 composite/multi-key
116
118
  // ("display 4 separate dimensions, let user pick sort dimension")
117
- const rows = db.prepare(`
119
+ const rows = await dbAll(`
118
120
  SELECT u.id, u.handle, u.name,
119
121
  COUNT(dc.id) as cases_count,
120
122
  COALESCE(SUM(dc.fairness_yes), 0) as total_yes,
@@ -129,7 +131,7 @@ export function registerLeaderboardRoutes(app, deps) {
129
131
  WHERE dc.arbitrator_id IS NOT NULL
130
132
  GROUP BY u.id
131
133
  ORDER BY cases_count DESC, u.id DESC LIMIT ?
132
- `).all(limit);
134
+ `, [limit]);
133
135
  return void res.json({ kind, items: rows });
134
136
  }
135
137
  if (kind === 'verifiers') {
@@ -139,7 +141,7 @@ export function registerLeaderboardRoutes(app, deps) {
139
141
  // 2026-06-03 #1080 audit: ORDER BY 改为 tasks_done desc(spec default case_count desc)
140
142
  // + u.id tie-breaker。移除 tasks_correct/accuracy 作为 secondary sort key — 该排序奖励
141
143
  // "活跃 + 准确" 隐含 composite,spec §3 明确"最活跃 first ≠ 最好 first"。
142
- const rows = db.prepare(`
144
+ const rows = await dbAll(`
143
145
  SELECT u.id, u.handle, u.name,
144
146
  vs.tasks_done, vs.tasks_correct, vs.tasks_wrong,
145
147
  CASE WHEN vs.tasks_done > 0 THEN ROUND(CAST(vs.tasks_correct AS REAL) / vs.tasks_done, 3) ELSE NULL END as accuracy,
@@ -149,7 +151,7 @@ export function registerLeaderboardRoutes(app, deps) {
149
151
  LEFT JOIN verifier_whitelist vw ON vw.user_id = vs.user_id
150
152
  WHERE vs.tasks_done >= 1
151
153
  ORDER BY vs.tasks_done DESC, u.id DESC LIMIT ?
152
- `).all(limit);
154
+ `, [limit]);
153
155
  return void res.json({ kind, items: rows });
154
156
  }
155
157
  return void res.json({ error: 'kind 必须是 products / creators / buyers / sellers / verifiers / arbitrators / agents / value_products' });
@@ -1,3 +1,4 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  const URGENCY_WEIGHTS = {
2
3
  now: { price: 0.10, eta: 0.50, trust: 0.20, region: 0.10, fresh: 0.10, eta_hard_max: 4 },
3
4
  today: { price: 0.25, eta: 0.35, trust: 0.15, region: 0.15, fresh: 0.10, eta_hard_max: 24 },
@@ -28,12 +29,12 @@ function computeOfferScore(o, urgency, ctx) {
28
29
  }
29
30
  export function registerListingsRoutes(app, deps) {
30
31
  const { db, generateId, auth, LISTING_CATEGORIES, BASE_LISTING_STAKE, VALID_FULFILLMENT_TYPES, isListingCategoryKey } = deps;
31
- function sellerCompletedSales(uid) {
32
- const r = db.prepare(`SELECT COUNT(1) as n FROM orders WHERE seller_id = ? AND status = 'completed'`).get(uid);
32
+ async function sellerCompletedSales(uid) {
33
+ const r = await dbOne(`SELECT COUNT(1) as n FROM orders WHERE seller_id = ? AND status = 'completed'`, [uid]);
33
34
  return Number(r?.n ?? 0);
34
35
  }
35
36
  // 列表搜索(公开)
36
- app.get('/api/listings', (req, res) => {
37
+ app.get('/api/listings', async (req, res) => {
37
38
  const q = String(req.query.q || '').trim();
38
39
  const category = String(req.query.category || '').trim();
39
40
  const limit = Math.min(100, Math.max(1, Number(req.query.limit) || 30));
@@ -50,7 +51,7 @@ export function registerListingsRoutes(app, deps) {
50
51
  args.push(category);
51
52
  }
52
53
  const orderBy = sort === 'popular' ? 'l.total_sales DESC, l.created_at DESC' : 'l.created_at DESC';
53
- const rows = db.prepare(`
54
+ const rows = await dbAll(`
54
55
  SELECT l.*,
55
56
  (SELECT MIN(p.price) FROM products p WHERE p.listing_id = l.id AND p.status = 'active') as min_price,
56
57
  (SELECT COUNT(1) FROM products p WHERE p.listing_id = l.id AND p.status = 'active') as offer_count
@@ -58,17 +59,17 @@ export function registerListingsRoutes(app, deps) {
58
59
  WHERE ${where.join(' AND ')}
59
60
  ORDER BY ${orderBy}
60
61
  LIMIT ?
61
- `).all(...args, limit);
62
+ `, [...args, limit]);
62
63
  res.json({ items: rows, categories: LISTING_CATEGORIES });
63
64
  });
64
65
  // 我的跟卖
65
- app.get('/api/listings/mine', (req, res) => {
66
+ app.get('/api/listings/mine', async (req, res) => {
66
67
  const user = auth(req, res);
67
68
  if (!user)
68
69
  return;
69
70
  if (user.role !== 'seller')
70
71
  return void res.status(403).json({ error: '仅卖家可用', error_code: 'SELLER_ONLY' });
71
- const rows = db.prepare(`
72
+ const rows = await dbAll(`
72
73
  SELECT l.id, l.title, l.category, l.category_path, l.external_id, l.created_at,
73
74
  (SELECT COUNT(*) FROM products WHERE listing_id = l.id AND seller_id = ? AND status = 'active') as my_offer_count,
74
75
  (SELECT MIN(price) FROM products WHERE listing_id = l.id AND seller_id = ? AND status = 'active') as my_min_price,
@@ -81,18 +82,18 @@ export function registerListingsRoutes(app, deps) {
81
82
  )
82
83
  ORDER BY l.created_at DESC
83
84
  LIMIT 100
84
- `).all(user.id, user.id, user.id, user.id);
85
+ `, [user.id, user.id, user.id, user.id]);
85
86
  res.json({ items: rows });
86
87
  });
87
88
  // 详情 + offers 加权排序
88
- app.get('/api/listings/:id', (req, res) => {
89
- const listing = db.prepare("SELECT * FROM listings WHERE id = ? AND status != 'blocked'").get(req.params.id);
89
+ app.get('/api/listings/:id', async (req, res) => {
90
+ const listing = await dbOne("SELECT * FROM listings WHERE id = ? AND status != 'blocked'", [req.params.id]);
90
91
  if (!listing)
91
92
  return void res.status(404).json({ error: 'listing 不存在' });
92
93
  const urgency = isUrgencyKey(String(req.query.urgency || '')) ? String(req.query.urgency) : 'flex';
93
94
  const sortParam = String(req.query.sort || 'smart');
94
95
  const sortMode = VALID_OFFER_SORTS.has(sortParam) ? sortParam : 'smart';
95
- const offers = db.prepare(`
96
+ const offers = await dbAll(`
96
97
  SELECT p.id, p.seller_id, p.title, p.price, p.stock, p.status,
97
98
  p.fulfillment_type, p.eta_hours, p.freshness_ts, p.is_clearance, p.clearance_until,
98
99
  p.cold_start_remaining, p.listing_stake_locked, p.ship_regions, p.commission_rate,
@@ -103,7 +104,7 @@ export function registerListingsRoutes(app, deps) {
103
104
  FROM products p
104
105
  LEFT JOIN users u ON u.id = p.seller_id
105
106
  WHERE p.listing_id = ? AND p.status = 'active'
106
- `).all(req.params.id);
107
+ `, [req.params.id]);
107
108
  const buyerRegion = req.query.buyer_region ? String(req.query.buyer_region) : null;
108
109
  const nowIso = new Date().toISOString();
109
110
  if (offers.length) {
@@ -165,7 +166,7 @@ export function registerListingsRoutes(app, deps) {
165
166
  res.json({ listing, offers, urgency, sort: sortMode, categories: LISTING_CATEGORIES });
166
167
  });
167
168
  // 创建 listing(首创者)
168
- app.post('/api/listings', (req, res) => {
169
+ app.post('/api/listings', async (req, res) => {
169
170
  const user = auth(req, res);
170
171
  if (!user)
171
172
  return;
@@ -178,26 +179,26 @@ export function registerListingsRoutes(app, deps) {
178
179
  return void res.json({ error: '类目无效' });
179
180
  const catCfg = LISTING_CATEGORIES[cat];
180
181
  if (catCfg.requires_kyc) {
181
- const k = db.prepare("SELECT status FROM kyc_records WHERE user_id = ?").get(user.id);
182
+ const k = await dbOne("SELECT status FROM kyc_records WHERE user_id = ?", [user.id]);
182
183
  if (!k || k.status !== 'approved') {
183
184
  return void res.json({ error: `${catCfg.name} 类目需先完成实名认证(KYC)`, error_code: 'KYC_REQUIRED' });
184
185
  }
185
186
  }
186
187
  if (catCfg.min_sales > 0) {
187
- const sales = sellerCompletedSales(user.id);
188
+ const sales = await sellerCompletedSales(user.id);
188
189
  if (sales < catCfg.min_sales) {
189
190
  return void res.json({ error: `${catCfg.name} 类目需至少 ${catCfg.min_sales} 单成功历史(当前 ${sales})` });
190
191
  }
191
192
  }
192
193
  // 首创者 stake = 1.5 × 基础 × 类目倍数
193
194
  const stakeRequired = Math.round(BASE_LISTING_STAKE * catCfg.stake_mult * 1.5 * 100) / 100;
194
- const wallet = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(user.id);
195
+ const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
195
196
  if (!wallet || Number(wallet.balance) < stakeRequired) {
196
197
  return void res.json({ error: `余额不足,创建 ${catCfg.name} listing 需 ${stakeRequired} WAZ` });
197
198
  }
198
199
  const externalId = body.external_id ? String(body.external_id).trim() : null;
199
200
  if (externalId) {
200
- const existing = db.prepare("SELECT id FROM listings WHERE external_id = ? AND status = 'active'").get(externalId);
201
+ const existing = await dbOne("SELECT id FROM listings WHERE external_id = ? AND status = 'active'", [externalId]);
201
202
  if (existing)
202
203
  return void res.json({ error: '该型号已存在 listing,请改为跟卖', listing_id: existing.id, suggestion: 'follow' });
203
204
  }
@@ -218,19 +219,19 @@ export function registerListingsRoutes(app, deps) {
218
219
  res.json({ id, stake_locked: stakeRequired, category: cat });
219
220
  });
220
221
  // 跟卖:为已有 listing 创建本卖家的 product(即一个 offer)
221
- app.post('/api/listings/:id/offers', (req, res) => {
222
+ app.post('/api/listings/:id/offers', async (req, res) => {
222
223
  const user = auth(req, res);
223
224
  if (!user)
224
225
  return;
225
226
  if (user.role !== 'seller')
226
227
  return void res.json({ error: '仅卖家可跟卖' });
227
- const listing = db.prepare("SELECT id, category, title, cover_image, description FROM listings WHERE id = ? AND status = 'active'").get(req.params.id);
228
+ const listing = await dbOne("SELECT id, category, title, cover_image, description FROM listings WHERE id = ? AND status = 'active'", [req.params.id]);
228
229
  if (!listing)
229
230
  return void res.status(404).json({ error: 'listing 不存在或已下架' });
230
231
  const cat = String(listing.category);
231
232
  const catCfg = isListingCategoryKey(cat) ? LISTING_CATEGORIES[cat] : LISTING_CATEGORIES.general;
232
233
  if (catCfg.min_sales > 0) {
233
- const sales = sellerCompletedSales(user.id);
234
+ const sales = await sellerCompletedSales(user.id);
234
235
  if (sales < catCfg.min_sales) {
235
236
  return void res.json({ error: `${catCfg.name} 类目跟卖需至少 ${catCfg.min_sales} 单成功历史(当前 ${sales})` });
236
237
  }
@@ -247,12 +248,12 @@ export function registerListingsRoutes(app, deps) {
247
248
  return void res.json({ error: 'fulfillment_type 无效' });
248
249
  const shipRegions = body.ship_regions ? String(body.ship_regions).trim() : '全国';
249
250
  const stakeRequired = Math.round(BASE_LISTING_STAKE * catCfg.stake_mult * 100) / 100;
250
- const wallet = db.prepare('SELECT balance FROM wallets WHERE user_id = ?').get(user.id);
251
+ const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
251
252
  if (!wallet || Number(wallet.balance) < stakeRequired) {
252
253
  return void res.json({ error: `余额不足,跟卖 ${catCfg.name} 需 ${stakeRequired} WAZ` });
253
254
  }
254
255
  // 一卖家 × 一 listing = 一 offer
255
- const existing = db.prepare("SELECT id, status FROM products WHERE listing_id = ? AND seller_id = ? AND status != 'deleted'").get(req.params.id, user.id);
256
+ const existing = await dbOne("SELECT id, status FROM products WHERE listing_id = ? AND seller_id = ? AND status != 'deleted'", [req.params.id, user.id]);
256
257
  if (existing)
257
258
  return void res.json({ error: '已存在该商品的 offer,请修改而非新建', offer_id: existing.id });
258
259
  const id = generateId('p');
@@ -1,16 +1,18 @@
1
+ import { dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerLogisticsRoutes(app, deps) {
2
- const { db, auth } = deps;
3
- app.get('/api/logistics/companies', (_req, res) => {
4
- const companies = db.prepare(`SELECT id, name FROM users WHERE role = 'logistics' ORDER BY name ASC`).all();
3
+ // db 已走 RFC-016 异步 seam(dbAll),不再直接用 deps.db
4
+ const { auth } = deps;
5
+ app.get('/api/logistics/companies', async (_req, res) => {
6
+ const companies = await dbAll(`SELECT id, name FROM users WHERE role = 'logistics' ORDER BY name ASC`);
5
7
  res.json(companies);
6
8
  });
7
- app.get('/api/logistics/orders', (req, res) => {
9
+ app.get('/api/logistics/orders', async (req, res) => {
8
10
  const user = auth(req, res);
9
11
  if (!user)
10
12
  return;
11
13
  if (user.role !== 'logistics')
12
14
  return void res.status(403).json({ error: '仅限物流角色' });
13
- const available = db.prepare(`
15
+ const available = await dbAll(`
14
16
  SELECT o.*, p.title as product_title, p.category,
15
17
  ub.name as buyer_name, us.name as seller_name
16
18
  FROM orders o
@@ -19,8 +21,8 @@ export function registerLogisticsRoutes(app, deps) {
19
21
  JOIN users us ON o.seller_id = us.id
20
22
  WHERE o.status = 'shipped' AND (o.logistics_id IS NULL OR o.logistics_id = '')
21
23
  ORDER BY o.created_at ASC LIMIT 20
22
- `).all();
23
- const mine = db.prepare(`
24
+ `);
25
+ const mine = await dbAll(`
24
26
  SELECT o.*, p.title as product_title, p.category,
25
27
  ub.name as buyer_name, us.name as seller_name
26
28
  FROM orders o
@@ -29,7 +31,7 @@ export function registerLogisticsRoutes(app, deps) {
29
31
  JOIN users us ON o.seller_id = us.id
30
32
  WHERE o.logistics_id = ? AND o.status IN ('shipped','picked_up','in_transit')
31
33
  ORDER BY o.created_at ASC LIMIT 20
32
- `).all(user.id);
34
+ `, [user.id]);
33
35
  res.json({ available, mine });
34
36
  });
35
37
  }