@seasonkoh/webaz 0.1.24 → 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 +165 -64
  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,21 +1,23 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  export function registerUsersPublicRoutes(app, deps) {
2
- const { db, auth, noteAuthenticityBadges } = deps;
3
+ // db 已全量走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
4
+ const { auth, noteAuthenticityBadges } = deps;
3
5
  // ref → user id(usr_xxx / permanent_code / @handle 三态)
4
- const resolveUserId = (ref0) => {
6
+ const resolveUserId = async (ref0) => {
5
7
  const ref = String(ref0 || '').trim();
6
8
  if (/^usr_[A-Za-z0-9_]+$/.test(ref))
7
9
  return ref;
8
10
  if (/^[A-Z0-9]{6,7}$/i.test(ref) && !ref.startsWith('@')) {
9
- const r = db.prepare("SELECT id FROM users WHERE permanent_code = ?").get(ref.toUpperCase());
11
+ const r = await dbOne("SELECT id FROM users WHERE permanent_code = ?", [ref.toUpperCase()]);
10
12
  if (r)
11
13
  return r.id;
12
14
  }
13
15
  const h = ref.replace(/^@/, '').toLowerCase();
14
- const r = db.prepare("SELECT id FROM users WHERE handle = ?").get(h);
16
+ const r = await dbOne("SELECT id FROM users WHERE handle = ?", [h]);
15
17
  return r ? r.id : null;
16
18
  };
17
19
  // 公开 reputation — 仅 level
18
- app.get('/api/users/:id/reputation', (req, res) => {
20
+ app.get('/api/users/:id/reputation', async (req, res) => {
19
21
  let userId = req.params.id;
20
22
  if (userId === 'me') {
21
23
  const user = auth(req, res);
@@ -23,17 +25,17 @@ export function registerUsersPublicRoutes(app, deps) {
23
25
  return;
24
26
  userId = user.id;
25
27
  }
26
- const row = db.prepare(`
28
+ const row = await dbOne(`
27
29
  SELECT level, MAX(trust_score) as max_score
28
30
  FROM agent_reputation WHERE user_id = ?
29
31
  GROUP BY user_id
30
- `).get(userId);
32
+ `, [userId]);
31
33
  if (!row)
32
34
  return void res.json({ user_id: userId, level: 'new' });
33
35
  res.json({ user_id: userId, level: row.level });
34
36
  });
35
37
  // PV 简报:组织图点击节点用
36
- app.get('/api/users/:id/pv-summary', (req, res) => {
38
+ app.get('/api/users/:id/pv-summary', async (req, res) => {
37
39
  const me = auth(req, res);
38
40
  if (!me)
39
41
  return;
@@ -42,29 +44,29 @@ export function registerUsersPublicRoutes(app, deps) {
42
44
  if (/^usr_[A-Za-z0-9_]+$/.test(ref))
43
45
  targetId = ref;
44
46
  else if (/^[A-Z0-9]{6,7}$/i.test(ref) && !ref.startsWith('@')) {
45
- const r = db.prepare("SELECT id FROM users WHERE permanent_code = ?").get(ref.toUpperCase());
47
+ const r = await dbOne("SELECT id FROM users WHERE permanent_code = ?", [ref.toUpperCase()]);
46
48
  if (r)
47
49
  targetId = r.id;
48
50
  }
49
51
  if (!targetId)
50
52
  return void res.json({ error: 'user not found' });
51
- const u = db.prepare(`
53
+ const u = await dbOne(`
52
54
  SELECT id, name, permanent_code, handle, total_left_pv, total_right_pv,
53
55
  placement_id, placement_side, placement_depth, left_child_id, right_child_id, created_at
54
56
  FROM users WHERE id = ?
55
- `).get(targetId);
57
+ `, [targetId]);
56
58
  if (!u)
57
59
  return void res.json({ error: 'user not found' });
58
- const placementName = u.placement_id ? db.prepare("SELECT name FROM users WHERE id = ?").get(u.placement_id)?.name : null;
59
- const leftChildName = u.left_child_id ? db.prepare("SELECT name FROM users WHERE id = ?").get(u.left_child_id)?.name : null;
60
- const rightChildName = u.right_child_id ? db.prepare("SELECT name FROM users WHERE id = ?").get(u.right_child_id)?.name : null;
61
- const score = db.prepare(`
60
+ const placementName = u.placement_id ? (await dbOne("SELECT name FROM users WHERE id = ?", [u.placement_id]))?.name : null;
61
+ const leftChildName = u.left_child_id ? (await dbOne("SELECT name FROM users WHERE id = ?", [u.left_child_id]))?.name : null;
62
+ const rightChildName = u.right_child_id ? (await dbOne("SELECT name FROM users WHERE id = ?", [u.right_child_id]))?.name : null;
63
+ const score = (await dbOne(`
62
64
  SELECT
63
65
  COALESCE(SUM(CASE WHEN settled_at IS NULL THEN score ELSE 0 END),0) AS pending_score,
64
66
  COALESCE(SUM(CASE WHEN settled_at IS NOT NULL THEN waz_amount ELSE 0 END),0) AS settled_waz,
65
67
  COUNT(*) AS total_hits
66
68
  FROM binary_score_records WHERE user_id = ?
67
- `).get(targetId);
69
+ `, [targetId]));
68
70
  const leftPv = Number(u.total_left_pv ?? 0);
69
71
  const rightPv = Number(u.total_right_pv ?? 0);
70
72
  const weak = Math.min(leftPv, rightPv);
@@ -86,25 +88,25 @@ export function registerUsersPublicRoutes(app, deps) {
86
88
  });
87
89
  });
88
90
  // 用户公开 shareables
89
- app.get('/api/users/:id/shareables', (req, res) => {
91
+ app.get('/api/users/:id/shareables', async (req, res) => {
90
92
  const ref = String(req.params.id || '').trim();
91
93
  let ownerId = null;
92
94
  if (/^usr_[A-Za-z0-9_]+$/.test(ref))
93
95
  ownerId = ref;
94
96
  else if (/^[A-Z0-9]{6,7}$/i.test(ref) && !ref.startsWith('@')) {
95
- const r = db.prepare("SELECT id FROM users WHERE permanent_code = ?").get(ref.toUpperCase());
97
+ const r = await dbOne("SELECT id FROM users WHERE permanent_code = ?", [ref.toUpperCase()]);
96
98
  if (r)
97
99
  ownerId = r.id;
98
100
  }
99
101
  if (!ownerId) {
100
102
  const h = ref.replace(/^@/, '').toLowerCase();
101
- const r = db.prepare("SELECT id FROM users WHERE handle = ?").get(h);
103
+ const r = await dbOne("SELECT id FROM users WHERE handle = ?", [h]);
102
104
  if (r)
103
105
  ownerId = r.id;
104
106
  }
105
107
  if (!ownerId)
106
108
  return void res.status(404).json({ error: 'user not found' });
107
- const rows = db.prepare(`
109
+ const rows = await dbAll(`
108
110
  SELECT s.id, s.owner_id, s.owner_code, s.type, s.external_url, s.external_platform, s.external_video_id,
109
111
  s.thumbnail_url, s.title, s.description, s.related_product_id, s.related_anchor,
110
112
  s.related_order_id, s.photo_hashes,
@@ -114,68 +116,68 @@ export function registerUsersPublicRoutes(app, deps) {
114
116
  LEFT JOIN products p ON p.id = s.related_product_id
115
117
  WHERE s.owner_id = ? AND s.status = 'active'
116
118
  ORDER BY s.created_at DESC LIMIT 100
117
- `).all(ownerId);
119
+ `, [ownerId]);
118
120
  for (const r of rows) {
119
121
  r.badges = noteAuthenticityBadges(r);
120
122
  }
121
123
  res.json({ shareables: rows });
122
124
  });
123
125
  // 用户在售二手(公开:available + reserved)
124
- app.get('/api/users/:id/secondhand', (req, res) => {
125
- const ownerId = resolveUserId(req.params.id);
126
+ app.get('/api/users/:id/secondhand', async (req, res) => {
127
+ const ownerId = await resolveUserId(req.params.id);
126
128
  if (!ownerId)
127
129
  return void res.status(404).json({ error: 'user not found' });
128
- const items = db.prepare(`
130
+ const items = await dbAll(`
129
131
  SELECT id, title, price, condition_grade, images, status, category, created_at
130
132
  FROM secondhand_items
131
133
  WHERE seller_id = ? AND status IN ('available', 'reserved')
132
134
  ORDER BY created_at DESC LIMIT 50
133
- `).all(ownerId);
135
+ `, [ownerId]);
134
136
  res.json({ items });
135
137
  });
136
138
  // 用户进行中拍卖(公开:open)
137
- app.get('/api/users/:id/auctions', (req, res) => {
138
- const ownerId = resolveUserId(req.params.id);
139
+ app.get('/api/users/:id/auctions', async (req, res) => {
140
+ const ownerId = await resolveUserId(req.params.id);
139
141
  if (!ownerId)
140
142
  return void res.status(404).json({ error: 'user not found' });
141
- const items = db.prepare(`
143
+ const items = await dbAll(`
142
144
  SELECT id, title, current_price, starting_price, status, deadline_at, bid_count, category, created_at
143
145
  FROM auctions
144
146
  WHERE seller_id = ? AND status = 'open' AND deadline_at > datetime('now')
145
147
  ORDER BY deadline_at ASC LIMIT 50
146
- `).all(ownerId);
148
+ `, [ownerId]);
147
149
  res.json({ items });
148
150
  });
149
151
  // 用户写的测评(公开:作为买家给出的评价)
150
- app.get('/api/users/:id/reviews', (req, res) => {
151
- const ownerId = resolveUserId(req.params.id);
152
+ app.get('/api/users/:id/reviews', async (req, res) => {
153
+ const ownerId = await resolveUserId(req.params.id);
152
154
  if (!ownerId)
153
155
  return void res.status(404).json({ error: 'user not found' });
154
- const items = db.prepare(`
156
+ const items = await dbAll(`
155
157
  SELECT r.order_id, r.product_id, r.stars, r.comment, r.reply, r.created_at,
156
158
  p.title AS product_title, p.images AS product_images
157
159
  FROM order_ratings r
158
160
  LEFT JOIN products p ON p.id = r.product_id
159
161
  WHERE r.buyer_id = ? AND (r.hidden_until IS NULL OR r.hidden_until <= datetime('now'))
160
162
  ORDER BY r.created_at DESC LIMIT 50
161
- `).all(ownerId);
163
+ `, [ownerId]);
162
164
  res.json({ items });
163
165
  });
164
166
  // 用户在售商品(公开:卖家 active 商品)
165
- app.get('/api/users/:id/products', (req, res) => {
166
- const ownerId = resolveUserId(req.params.id);
167
+ app.get('/api/users/:id/products', async (req, res) => {
168
+ const ownerId = await resolveUserId(req.params.id);
167
169
  if (!ownerId)
168
170
  return void res.status(404).json({ error: 'user not found' });
169
- const items = db.prepare(`
171
+ const items = await dbAll(`
170
172
  SELECT id, title, price, images, category, completion_count, total_likes, created_at
171
173
  FROM products
172
174
  WHERE seller_id = ? AND status = 'active' AND stock > 0
173
175
  ORDER BY completion_count DESC, created_at DESC LIMIT 50
174
- `).all(ownerId);
176
+ `, [ownerId]);
175
177
  res.json({ items });
176
178
  });
177
179
  // 用户赞过的 shareables(仅 owner 可见)
178
- app.get('/api/users/:id/liked-shareables', (req, res) => {
180
+ app.get('/api/users/:id/liked-shareables', async (req, res) => {
179
181
  const me = auth(req, res);
180
182
  if (!me)
181
183
  return;
@@ -185,7 +187,7 @@ export function registerUsersPublicRoutes(app, deps) {
185
187
  ownerId = me.id;
186
188
  if (!ownerId)
187
189
  return void res.status(403).json({ error: 'only owner can view liked list' });
188
- const rows = db.prepare(`
190
+ const rows = await dbAll(`
189
191
  SELECT s.id, s.owner_id, s.owner_code, s.type, s.external_url, s.external_platform,
190
192
  s.thumbnail_url, s.title, s.description, s.photo_hashes, s.related_product_id, s.related_anchor,
191
193
  s.click_count, s.like_count, s.created_at,
@@ -196,7 +198,7 @@ export function registerUsersPublicRoutes(app, deps) {
196
198
  LEFT JOIN products p ON p.id = s.related_product_id
197
199
  WHERE l.user_id = ? AND s.status = 'active'
198
200
  ORDER BY l.created_at DESC LIMIT 100
199
- `).all(ownerId);
201
+ `, [ownerId]);
200
202
  for (const r of rows) {
201
203
  if (typeof r.photo_hashes === 'string') {
202
204
  try {
@@ -210,20 +212,20 @@ export function registerUsersPublicRoutes(app, deps) {
210
212
  res.json({ shareables: rows });
211
213
  });
212
214
  // 公开卡(未登录可调,分享 banner 用)
213
- app.get('/api/users/:id/public-card', (req, res) => {
215
+ app.get('/api/users/:id/public-card', async (req, res) => {
214
216
  const ref = String(req.params.id || '').trim();
215
217
  const cols = "id, name, bio, search_anchor, created_at, permanent_code, handle";
216
218
  const filter = " AND id != 'sys_protocol'";
217
219
  let row;
218
220
  if (/^usr_[A-Za-z0-9_]+$/.test(ref)) {
219
- row = db.prepare(`SELECT ${cols} FROM users WHERE id = ?${filter}`).get(ref);
221
+ row = await dbOne(`SELECT ${cols} FROM users WHERE id = ?${filter}`, [ref]);
220
222
  }
221
223
  if (!row && /^[A-Z0-9]{6,7}$/.test(ref.toUpperCase()) && !ref.startsWith('@')) {
222
- row = db.prepare(`SELECT ${cols} FROM users WHERE permanent_code = ?${filter}`).get(ref.toUpperCase());
224
+ row = await dbOne(`SELECT ${cols} FROM users WHERE permanent_code = ?${filter}`, [ref.toUpperCase()]);
223
225
  }
224
226
  if (!row) {
225
227
  const h = ref.replace(/^@/, '').toLowerCase();
226
- row = db.prepare(`SELECT ${cols} FROM users WHERE handle = ?${filter}`).get(h);
228
+ row = await dbOne(`SELECT ${cols} FROM users WHERE handle = ?${filter}`, [h]);
227
229
  }
228
230
  if (!row)
229
231
  return void res.status(404).json({ error: 'not_found' });
@@ -240,7 +242,7 @@ export function registerUsersPublicRoutes(app, deps) {
240
242
  });
241
243
  });
242
244
  // 公开用户主页 + D2 信誉徽章墙
243
- app.get('/api/users/:user_id', (req, res) => {
245
+ app.get('/api/users/:user_id', async (req, res) => {
244
246
  const me = auth(req, res);
245
247
  if (!me)
246
248
  return;
@@ -248,30 +250,30 @@ export function registerUsersPublicRoutes(app, deps) {
248
250
  const cols = "id, name, role, region, bio, search_anchor, created_at, reputation, COALESCE(feed_visible, 1) as feed_visible";
249
251
  let target;
250
252
  if (/^usr_[A-Za-z0-9_]+$/.test(ref)) {
251
- target = db.prepare(`SELECT ${cols} FROM users WHERE id = ? AND id != 'sys_protocol'`).get(ref);
253
+ target = await dbOne(`SELECT ${cols} FROM users WHERE id = ? AND id != 'sys_protocol'`, [ref]);
252
254
  }
253
255
  else if (/^[A-Z0-9]{6,7}$/.test(ref) && !ref.startsWith('@')) {
254
- target = db.prepare(`SELECT ${cols} FROM users WHERE permanent_code = ? AND id != 'sys_protocol'`).get(ref.toUpperCase());
256
+ target = await dbOne(`SELECT ${cols} FROM users WHERE permanent_code = ? AND id != 'sys_protocol'`, [ref.toUpperCase()]);
255
257
  }
256
258
  if (!target) {
257
259
  const h = ref.replace(/^@/, '').toLowerCase();
258
- target = db.prepare(`SELECT ${cols} FROM users WHERE handle = ? AND id != 'sys_protocol'`).get(h);
260
+ target = await dbOne(`SELECT ${cols} FROM users WHERE handle = ? AND id != 'sys_protocol'`, [h]);
259
261
  }
260
262
  if (!target)
261
263
  return void res.status(404).json({ error: '用户不存在' });
262
- const followers = db.prepare("SELECT COUNT(*) as n FROM follows WHERE followee_id = ?").get(target.id).n;
263
- const following = db.prepare("SELECT COUNT(*) as n FROM follows WHERE follower_id = ?").get(target.id).n;
264
- const isFollowing = !!db.prepare("SELECT 1 FROM follows WHERE follower_id = ? AND followee_id = ?").get(me.id, target.id);
265
- const purchaseCount = db.prepare("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(target.id).n;
266
- const salesCount = db.prepare("SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND status = 'completed'").get(target.id).n;
267
- const likesReceived = db.prepare("SELECT COALESCE(SUM(like_count), 0) as n FROM shareables WHERE owner_id = ? AND status = 'active'").get(target.id).n;
264
+ const followers = (await dbOne("SELECT COUNT(*) as n FROM follows WHERE followee_id = ?", [target.id])).n;
265
+ const following = (await dbOne("SELECT COUNT(*) as n FROM follows WHERE follower_id = ?", [target.id])).n;
266
+ const isFollowing = !!(await dbOne("SELECT 1 FROM follows WHERE follower_id = ? AND followee_id = ?", [me.id, target.id]));
267
+ const purchaseCount = (await dbOne("SELECT COUNT(*) as n FROM orders WHERE buyer_id = ? AND status = 'completed'", [target.id])).n;
268
+ const salesCount = (await dbOne("SELECT COUNT(*) as n FROM orders WHERE seller_id = ? AND status = 'completed'", [target.id])).n;
269
+ const likesReceived = (await dbOne("SELECT COALESCE(SUM(like_count), 0) as n FROM shareables WHERE owner_id = ? AND status = 'active'", [target.id])).n;
268
270
  // 主人视角加私有统计
269
271
  const isOwner = me.id === target.id;
270
272
  let privateStats = null;
271
273
  if (isOwner) {
272
- const w = db.prepare('SELECT balance, earned FROM wallets WHERE user_id = ?').get(me.id);
273
- const pv = db.prepare("SELECT total_left_pv, total_right_pv FROM users WHERE id = ?").get(me.id);
274
- const score = db.prepare("SELECT COALESCE(SUM(score),0) as s FROM binary_score_records WHERE user_id = ? AND settled_at IS NULL").get(me.id).s;
274
+ const w = await dbOne('SELECT balance, earned FROM wallets WHERE user_id = ?', [me.id]);
275
+ const pv = await dbOne("SELECT total_left_pv, total_right_pv FROM users WHERE id = ?", [me.id]);
276
+ const score = (await dbOne("SELECT COALESCE(SUM(score),0) as s FROM binary_score_records WHERE user_id = ? AND settled_at IS NULL", [me.id])).s;
275
277
  privateStats = {
276
278
  wallet_balance: Number(w?.balance ?? 0),
277
279
  wallet_earned: Number(w?.earned ?? 0),
@@ -288,14 +290,14 @@ export function registerUsersPublicRoutes(app, deps) {
288
290
  rep >= 30 ? { tier: 2, label: '可靠', emoji: '✓', color: '#16a34a' } :
289
291
  { tier: 1, label: '新手', emoji: '🌱', color: '#9ca3af' };
290
292
  // Agent trust band(P1.2 隐私:trust_score 仅 owner 看,他人仅 level)
291
- const agentRow = db.prepare(`SELECT level, MAX(trust_score) as score FROM agent_reputation WHERE user_id = ? GROUP BY user_id`).get(target.id);
293
+ const agentRow = await dbOne(`SELECT level, MAX(trust_score) as score FROM agent_reputation WHERE user_id = ? GROUP BY user_id`, [target.id]);
292
294
  const agentBand = agentRow ? (isOwner
293
295
  ? { level: agentRow.level, score: Math.round(agentRow.score || 0) }
294
296
  : { level: agentRow.level }) : null;
295
- const charity = db.prepare(`SELECT prestige_score, badge_tier, wishes_fulfilled, wishes_made FROM charity_reputation WHERE user_id = ?`).get(target.id);
297
+ const charity = await dbOne(`SELECT prestige_score, badge_tier, wishes_fulfilled, wishes_made FROM charity_reputation WHERE user_id = ?`, [target.id]);
296
298
  let verifier;
297
299
  try {
298
- verifier = db.prepare(`SELECT tier FROM verifier_whitelist WHERE user_id = ?`).get(target.id);
300
+ verifier = await dbOne(`SELECT tier FROM verifier_whitelist WHERE user_id = ?`, [target.id]);
299
301
  }
300
302
  catch { }
301
303
  res.json({
@@ -1,3 +1,4 @@
1
+ import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
1
2
  /** options → canonical key(sort + join)— 防同规格组合两次入库 */
2
3
  function canonicalOptionsKey(options) {
3
4
  return Object.keys(options).sort().map(k => `${k}=${String(options[k])}`).join('|');
@@ -5,13 +6,13 @@ function canonicalOptionsKey(options) {
5
6
  export function registerVariantsRoutes(app, deps) {
6
7
  const { db, generateId, auth } = deps;
7
8
  // 公开列出(含 buyer 下单页查可选项)
8
- app.get('/api/products/:product_id/variants', (req, res) => {
9
- const rows = db.prepare(`
9
+ app.get('/api/products/:product_id/variants', async (req, res) => {
10
+ const rows = await dbAll(`
10
11
  SELECT id, sku, options_json, price_override, stock, images_json, is_active, created_at
11
12
  FROM product_variants
12
13
  WHERE product_id = ? AND is_active = 1
13
14
  ORDER BY created_at ASC LIMIT 100
14
- `).all(req.params.product_id);
15
+ `, [req.params.product_id]);
15
16
  const items = rows.map(r => {
16
17
  let options = {};
17
18
  let images = [];
@@ -28,11 +29,11 @@ export function registerVariantsRoutes(app, deps) {
28
29
  });
29
30
  res.json({ items });
30
31
  });
31
- app.post('/api/products/:product_id/variants', (req, res) => {
32
+ app.post('/api/products/:product_id/variants', async (req, res) => {
32
33
  const user = auth(req, res);
33
34
  if (!user)
34
35
  return;
35
- const p = db.prepare('SELECT id, seller_id FROM products WHERE id = ?').get(req.params.product_id);
36
+ const p = await dbOne('SELECT id, seller_id FROM products WHERE id = ?', [req.params.product_id]);
36
37
  if (!p)
37
38
  return void res.status(404).json({ error: '商品不存在' });
38
39
  if (p.seller_id !== user.id)
@@ -46,8 +47,7 @@ export function registerVariantsRoutes(app, deps) {
46
47
  if (priceN != null && (priceN <= 0 || priceN > 1_000_000))
47
48
  return void res.status(400).json({ error: 'price_override 无效' });
48
49
  const optKey = canonicalOptionsKey(options);
49
- const dup = db.prepare(`SELECT id FROM product_variants WHERE product_id = ? AND options_key = ? AND is_active = 1`)
50
- .get(req.params.product_id, optKey);
50
+ const dup = await dbOne(`SELECT id FROM product_variants WHERE product_id = ? AND options_key = ? AND is_active = 1`, [req.params.product_id, optKey]);
51
51
  if (dup)
52
52
  return void res.status(409).json({ error: '该规格组合已存在', existing_id: dup.id });
53
53
  const id = generateId('pv');
@@ -66,11 +66,11 @@ export function registerVariantsRoutes(app, deps) {
66
66
  })();
67
67
  res.json({ success: true, id });
68
68
  });
69
- app.patch('/api/products/:product_id/variants/:variant_id', (req, res) => {
69
+ app.patch('/api/products/:product_id/variants/:variant_id', async (req, res) => {
70
70
  const user = auth(req, res);
71
71
  if (!user)
72
72
  return;
73
- const v = db.prepare(`SELECT v.*, p.seller_id FROM product_variants v JOIN products p ON p.id = v.product_id WHERE v.id = ? AND v.product_id = ?`).get(req.params.variant_id, req.params.product_id);
73
+ const v = await dbOne(`SELECT v.*, p.seller_id FROM product_variants v JOIN products p ON p.id = v.product_id WHERE v.id = ? AND v.product_id = ?`, [req.params.variant_id, req.params.product_id]);
74
74
  if (!v)
75
75
  return void res.status(404).json({ error: 'variant 不存在' });
76
76
  if (v.seller_id !== user.id)
@@ -87,8 +87,7 @@ export function registerVariantsRoutes(app, deps) {
87
87
  return void res.status(400).json({ error: 'options 不能为空' });
88
88
  }
89
89
  const newKey = canonicalOptionsKey(options);
90
- const dup = db.prepare(`SELECT id FROM product_variants WHERE product_id = ? AND options_key = ? AND id != ? AND is_active = 1`)
91
- .get(req.params.product_id, newKey, req.params.variant_id);
90
+ const dup = await dbOne(`SELECT id FROM product_variants WHERE product_id = ? AND options_key = ? AND id != ? AND is_active = 1`, [req.params.product_id, newKey, req.params.variant_id]);
92
91
  if (dup)
93
92
  return void res.status(409).json({ error: '该规格组合已存在', existing_id: dup.id });
94
93
  sets.push('options_json = ?');
@@ -129,11 +128,11 @@ export function registerVariantsRoutes(app, deps) {
129
128
  })();
130
129
  res.json({ success: true });
131
130
  });
132
- app.delete('/api/products/:product_id/variants/:variant_id', (req, res) => {
131
+ app.delete('/api/products/:product_id/variants/:variant_id', async (req, res) => {
133
132
  const user = auth(req, res);
134
133
  if (!user)
135
134
  return;
136
- const v = db.prepare(`SELECT v.id, v.stock, p.seller_id FROM product_variants v JOIN products p ON p.id = v.product_id WHERE v.id = ? AND v.product_id = ?`).get(req.params.variant_id, req.params.product_id);
135
+ const v = await dbOne(`SELECT v.id, v.stock, p.seller_id FROM product_variants v JOIN products p ON p.id = v.product_id WHERE v.id = ? AND v.product_id = ?`, [req.params.variant_id, req.params.product_id]);
137
136
  if (!v)
138
137
  return void res.status(404).json({ error: 'variant 不存在' });
139
138
  if (v.seller_id !== user.id)
@@ -1,4 +1,7 @@
1
+ import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam (dbRun: appeal single-write)
1
2
  export function registerVerifierUserRoutes(app, deps) {
3
+ // 只读/单写站点走 RFC-016 异步 seam;db 保留:apply/withdraw 是 stake 资金路径,
4
+ // 状态翻转 + 钱包扣/退必须原子(db.transaction + CAS),Phase 3 迁 pg 行锁。
2
5
  const { db, generateId, auth, errorRes, checkVerifierEligibility, getVerifierState, resetDailyQuotaIfNeeded, TIER_QUOTAS, VERIFIER_STAKE_REQUIRED, APP_REJECT_COOLDOWN_DAYS, } = deps;
3
6
  app.get('/api/verifier/eligibility', (req, res) => {
4
7
  const user = auth(req, res);
@@ -26,7 +29,7 @@ export function registerVerifierUserRoutes(app, deps) {
26
29
  stake_amount: Number(wl?.stake_amount ?? 0),
27
30
  });
28
31
  });
29
- app.post('/api/verifier/apply', (req, res) => {
32
+ app.post('/api/verifier/apply', async (req, res) => {
30
33
  const user = auth(req, res);
31
34
  if (!user)
32
35
  return;
@@ -35,14 +38,14 @@ export function registerVerifierUserRoutes(app, deps) {
35
38
  if (user.role !== 'buyer') {
36
39
  return void errorRes(res, 403, 'ROLE_NOT_BUYER', '外部审核员仅 buyer 角色可申请(卖家 / 受信角色请联系管理员)');
37
40
  }
38
- const wl = db.prepare("SELECT 1 FROM verifier_whitelist WHERE user_id = ?").get(userId);
41
+ const wl = await dbOne("SELECT 1 FROM verifier_whitelist WHERE user_id = ?", [userId]);
39
42
  if (wl)
40
43
  return void res.json({ error: '你已经是审核员,无需重新申请' });
41
- const pending = db.prepare("SELECT 1 FROM verifier_applications WHERE user_id = ? AND status = 'pending'").get(userId);
44
+ const pending = await dbOne("SELECT 1 FROM verifier_applications WHERE user_id = ? AND status = 'pending'", [userId]);
42
45
  if (pending)
43
46
  return void res.json({ error: '你已有待审申请' });
44
47
  // 30d 拒绝冷却
45
- const lastReject = db.prepare("SELECT reviewed_at FROM verifier_applications WHERE user_id = ? AND status = 'rejected' ORDER BY reviewed_at DESC LIMIT 1").get(userId);
48
+ const lastReject = await dbOne("SELECT reviewed_at FROM verifier_applications WHERE user_id = ? AND status = 'rejected' ORDER BY reviewed_at DESC LIMIT 1", [userId]);
46
49
  if (lastReject?.reviewed_at) {
47
50
  const cooldownEnd = new Date(new Date(lastReject.reviewed_at).getTime() + APP_REJECT_COOLDOWN_DAYS * 86400_000);
48
51
  if (cooldownEnd > new Date()) {
@@ -53,34 +56,72 @@ export function registerVerifierUserRoutes(app, deps) {
53
56
  if (!elig.eligible) {
54
57
  return void res.json({ error: '信誉指标未达标', eligibility: elig });
55
58
  }
59
+ // 友好预检查(读):真正的守恒门在事务内(WHERE balance >= stake)。
56
60
  if (VERIFIER_STAKE_REQUIRED > 0) {
57
- const wallet = db.prepare("SELECT balance, staked FROM wallets WHERE user_id = ?").get(userId);
61
+ const wallet = await dbOne("SELECT balance FROM wallets WHERE user_id = ?", [userId]);
58
62
  if (!wallet || wallet.balance < VERIFIER_STAKE_REQUIRED) {
59
63
  return void res.json({ error: `质押需 ${VERIFIER_STAKE_REQUIRED} WAZ,钱包余额不足` });
60
64
  }
61
- db.prepare("UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ?")
62
- .run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, userId);
63
65
  }
64
- db.prepare(`INSERT INTO verifier_applications (id, user_id, status, snapshot) VALUES (?,?,?,?)`)
65
- .run(generateId('vapp'), userId, 'pending', JSON.stringify(elig.items));
66
+ const appId = generateId('vapp');
67
+ // stake 原子段:重检 whitelist/pending(防并发双申请双质押)+ 钱包扣减(守恒 guard)+ INSERT 申请
68
+ try {
69
+ db.transaction(() => {
70
+ if (db.prepare("SELECT 1 FROM verifier_whitelist WHERE user_id = ?").get(userId))
71
+ throw new Error('VER_ALREADY');
72
+ if (db.prepare("SELECT 1 FROM verifier_applications WHERE user_id = ? AND status = 'pending'").get(userId))
73
+ throw new Error('VER_PENDING');
74
+ if (VERIFIER_STAKE_REQUIRED > 0) {
75
+ const debit = db.prepare("UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ? AND balance >= ?")
76
+ .run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, userId, VERIFIER_STAKE_REQUIRED);
77
+ if (debit.changes === 0)
78
+ throw new Error('VER_INSUFFICIENT');
79
+ }
80
+ db.prepare(`INSERT INTO verifier_applications (id, user_id, status, snapshot) VALUES (?,?,?,?)`)
81
+ .run(appId, userId, 'pending', JSON.stringify(elig.items));
82
+ })();
83
+ }
84
+ catch (e) {
85
+ const msg = e.message;
86
+ if (msg === 'VER_ALREADY')
87
+ return void res.json({ error: '你已经是审核员,无需重新申请' });
88
+ if (msg === 'VER_PENDING')
89
+ return void res.json({ error: '你已有待审申请' });
90
+ if (msg === 'VER_INSUFFICIENT')
91
+ return void res.json({ error: `质押需 ${VERIFIER_STAKE_REQUIRED} WAZ,钱包余额不足` });
92
+ console.error('[verifier apply tx]', msg);
93
+ return void res.status(500).json({ error: '申请失败,请重试' });
94
+ }
66
95
  res.json({ success: true, stake_locked: VERIFIER_STAKE_REQUIRED });
67
96
  });
68
- app.post('/api/verifier/withdraw-application', (req, res) => {
97
+ app.post('/api/verifier/withdraw-application', async (req, res) => {
69
98
  const user = auth(req, res);
70
99
  if (!user)
71
100
  return;
72
101
  const userId = user.id;
73
- const pending = db.prepare("SELECT id FROM verifier_applications WHERE user_id = ? AND status = 'pending' LIMIT 1").get(userId);
102
+ const pending = await dbOne("SELECT id FROM verifier_applications WHERE user_id = ? AND status = 'pending' LIMIT 1", [userId]);
74
103
  if (!pending)
75
104
  return void res.json({ error: '没有待审申请' });
76
- db.prepare("UPDATE verifier_applications SET status = 'withdrawn', reviewed_at = datetime('now') WHERE id = ?").run(pending.id);
77
- if (VERIFIER_STAKE_REQUIRED > 0) {
78
- db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?")
79
- .run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, userId);
105
+ // 原子段:CAS 翻转 pending→withdrawn(防并发/admin 抢跑双退)+ 退质押仅在本请求真翻转时
106
+ try {
107
+ db.transaction(() => {
108
+ const cas = db.prepare("UPDATE verifier_applications SET status = 'withdrawn', reviewed_at = datetime('now') WHERE id = ? AND status = 'pending'").run(pending.id);
109
+ if (cas.changes === 0)
110
+ throw new Error('VER_RACE');
111
+ if (VERIFIER_STAKE_REQUIRED > 0) {
112
+ db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?").run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, userId);
113
+ }
114
+ })();
115
+ }
116
+ catch (e) {
117
+ if (e.message === 'VER_RACE')
118
+ return void res.status(409).json({ error: '申请状态已变化,请刷新' });
119
+ console.error('[verifier withdraw tx]', e.message);
120
+ return void res.status(500).json({ error: '撤回失败,请重试' });
80
121
  }
81
122
  res.json({ success: true });
82
123
  });
83
- app.post('/api/verifier/appeal', (req, res) => {
124
+ app.post('/api/verifier/appeal', async (req, res) => {
84
125
  const user = auth(req, res);
85
126
  if (!user)
86
127
  return;
@@ -90,18 +131,17 @@ export function registerVerifierUserRoutes(app, deps) {
90
131
  if (reason.length > 500)
91
132
  return void res.json({ error: '申诉理由过长(>500 字)' });
92
133
  // 必须当前 suspended
93
- const stats = db.prepare("SELECT suspended_until FROM verifier_stats WHERE user_id = ?").get(user.id);
134
+ const stats = await dbOne("SELECT suspended_until FROM verifier_stats WHERE user_id = ?", [user.id]);
94
135
  if (!stats?.suspended_until || new Date(stats.suspended_until).getTime() <= Date.now()) {
95
136
  return void res.json({ error: '当前未处于暂停状态,无需申诉' });
96
137
  }
97
138
  // 近 30 天有申诉过即重复
98
- const recent = db.prepare(`SELECT 1 FROM verifier_appeals WHERE user_id = ? AND created_at > datetime('now','-30 day')`).get(user.id);
139
+ const recent = await dbOne(`SELECT 1 FROM verifier_appeals WHERE user_id = ? AND created_at > datetime('now','-30 day')`, [user.id]);
99
140
  if (recent)
100
141
  return void res.json({ error: '近期已申诉过,每次处罚只能申诉一次' });
101
142
  const evidenceArr = Array.isArray(evidence_urls) ? evidence_urls.slice(0, 3) : [];
102
- db.prepare(`INSERT INTO verifier_appeals (id, user_id, task_id, submission_id, reason, evidence_urls)
103
- VALUES (?,?,?,?,?,?)`)
104
- .run(generateId('vapl'), user.id, task_id || null, submission_id || null, reason.trim(), JSON.stringify(evidenceArr));
143
+ await dbRun(`INSERT INTO verifier_appeals (id, user_id, task_id, submission_id, reason, evidence_urls)
144
+ VALUES (?,?,?,?,?,?)`, [generateId('vapl'), user.id, task_id || null, submission_id || null, reason.trim(), JSON.stringify(evidenceArr)]);
105
145
  res.json({ success: true });
106
146
  });
107
147
  }