@seasonkoh/webaz 0.1.24 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/layer0-foundation/L0-1-database/db-backends/pg-backend.js +51 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-dialect-datetime.js +437 -0
- package/dist/layer0-foundation/L0-1-database/db-backends/sql-placeholders.js +98 -0
- package/dist/layer0-foundation/L0-1-database/db.js +65 -0
- package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +13 -11
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +1 -1
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +13 -11
- package/dist/layer1-agent/L1-1-mcp-server/server.js +288 -208
- package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +14 -12
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +8 -5
- package/dist/layer2-business/L2-7-snf/snf-engine.js +16 -14
- package/dist/layer2-business/L2-8-feedback/build-feedback-engine.js +18 -10
- package/dist/layer2-business/L2-9-contribution/build-reputation-engine.js +37 -23
- package/dist/layer2-business/L2-9-contribution/build-task-agent-metadata-store.js +182 -0
- package/dist/layer2-business/L2-9-contribution/build-task-participation.js +47 -0
- package/dist/layer2-business/L2-9-contribution/build-task-read.js +222 -0
- package/dist/layer2-business/L2-9-contribution/build-tasks-engine.js +11 -3
- package/dist/layer2-business/L2-9-contribution/canonical-contribution-target.js +16 -0
- package/dist/layer2-business/L2-9-contribution/contribution-display-envelope.js +40 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-contract.js +36 -0
- package/dist/layer2-business/L2-9-contribution/contribution-score-evidence.js +61 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/canonical.js +60 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-credential.schema.js +140 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/github-fetch-adapter.js +437 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/self-consistency.js +38 -0
- package/dist/layer2-business/L2-9-contribution/github-credential/verifier.js +231 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-ingestion-engine.js +145 -0
- package/dist/layer2-business/L2-9-contribution/github-credential-store.js +115 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-engine.js +134 -0
- package/dist/layer2-business/L2-9-contribution/identity-binding-store.js +101 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-engine.js +126 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-challenge-store.js +30 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-discovery.js +55 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-engine.js +109 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-fact-precondition.js +22 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-proof-verifier.js +97 -0
- package/dist/layer2-business/L2-9-contribution/identity-claim-read.js +59 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-ai-store.js +99 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-draft.js +191 -0
- package/dist/layer2-business/L2-9-contribution/task-proposal-store.js +129 -0
- package/dist/layer2-business/L2-notes/note-photo-storage.js +4 -2
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +17 -15
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +11 -8
- package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +9 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +11 -8
- package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +22 -16
- package/dist/pwa/acp-feed.js +13 -1
- package/dist/pwa/admin-bearer-auth.js +21 -0
- package/dist/pwa/contract-fingerprint.js +2 -0
- package/dist/pwa/email-delivery.js +127 -0
- package/dist/pwa/endpoint-actions.js +5 -1
- package/dist/pwa/goal-index.js +8 -8
- package/dist/pwa/human-presence.js +62 -0
- package/dist/pwa/public/app.js +1485 -283
- package/dist/pwa/public/i18n.js +297 -59
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +5 -5
- package/dist/pwa/public/whitepaper/en/index.html +153 -0
- package/dist/pwa/public/whitepaper/zh-CN/index.html +153 -0
- package/dist/pwa/rate-limit.js +22 -0
- package/dist/pwa/routes/account-deletion.js +15 -13
- package/dist/pwa/routes/addresses.js +10 -9
- package/dist/pwa/routes/admin-admins.js +13 -14
- package/dist/pwa/routes/admin-analytics.js +109 -69
- package/dist/pwa/routes/admin-atomic.js +10 -4
- package/dist/pwa/routes/admin-catalog.js +13 -11
- package/dist/pwa/routes/admin-editor-picks.js +15 -10
- package/dist/pwa/routes/admin-events.js +5 -3
- package/dist/pwa/routes/admin-health.js +2 -1
- package/dist/pwa/routes/admin-moderation.js +50 -29
- package/dist/pwa/routes/admin-ops.js +35 -23
- package/dist/pwa/routes/admin-protocol-params.js +16 -19
- package/dist/pwa/routes/admin-reports.js +23 -21
- package/dist/pwa/routes/admin-tokenomics.js +26 -25
- package/dist/pwa/routes/admin-users-lifecycle.js +37 -40
- package/dist/pwa/routes/admin-users-query.js +65 -53
- package/dist/pwa/routes/admin-verifier-flow.js +82 -41
- package/dist/pwa/routes/admin-verifier-whitelist.js +55 -27
- package/dist/pwa/routes/admin-wallet-ops.js +32 -7
- package/dist/pwa/routes/agent-buy.js +46 -22
- package/dist/pwa/routes/agent-governance.js +52 -56
- package/dist/pwa/routes/ai.js +7 -5
- package/dist/pwa/routes/analytics.js +43 -41
- package/dist/pwa/routes/anchors.js +19 -20
- package/dist/pwa/routes/announcements.js +13 -13
- package/dist/pwa/routes/arbitrator.js +97 -31
- package/dist/pwa/routes/auction.js +157 -116
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +21 -10
- package/dist/pwa/routes/auth-register.js +111 -26
- package/dist/pwa/routes/auth-sessions.js +12 -11
- package/dist/pwa/routes/blocklist.js +16 -15
- package/dist/pwa/routes/build-feedback.js +10 -9
- package/dist/pwa/routes/build-reputation.js +6 -2
- package/dist/pwa/routes/build-tasks.js +45 -13
- package/dist/pwa/routes/buyer-feeds.js +27 -25
- package/dist/pwa/routes/cart.js +16 -15
- package/dist/pwa/routes/charity.js +212 -150
- package/dist/pwa/routes/chat.js +42 -43
- package/dist/pwa/routes/checkin-tasks.js +10 -9
- package/dist/pwa/routes/checkout-helpers.js +12 -10
- package/dist/pwa/routes/claim-initiators.js +34 -14
- package/dist/pwa/routes/claim-verify.js +86 -53
- package/dist/pwa/routes/claim-voting.js +43 -18
- package/dist/pwa/routes/contribution-identity.js +164 -0
- package/dist/pwa/routes/contribution-score.js +19 -0
- package/dist/pwa/routes/coupons.js +19 -16
- package/dist/pwa/routes/dashboards.js +18 -16
- package/dist/pwa/routes/dispute-cases.js +25 -24
- package/dist/pwa/routes/disputes-read.js +45 -51
- package/dist/pwa/routes/disputes-write.js +124 -61
- package/dist/pwa/routes/evidence.js +9 -9
- package/dist/pwa/routes/external-anchors.js +13 -12
- package/dist/pwa/routes/feedback.js +29 -33
- package/dist/pwa/routes/flash-sales.js +18 -16
- package/dist/pwa/routes/follows.js +25 -24
- package/dist/pwa/routes/governance-auto-deactivate.js +21 -9
- package/dist/pwa/routes/governance-onboarding.js +70 -59
- package/dist/pwa/routes/group-buys.js +22 -22
- package/dist/pwa/routes/growth.js +34 -31
- package/dist/pwa/routes/import-product.js +12 -10
- package/dist/pwa/routes/kyc.js +9 -8
- package/dist/pwa/routes/leaderboard.js +20 -18
- package/dist/pwa/routes/listings.js +23 -22
- package/dist/pwa/routes/logistics.js +10 -8
- package/dist/pwa/routes/manifests.js +27 -27
- package/dist/pwa/routes/me-data.js +23 -21
- package/dist/pwa/routes/notifications.js +7 -6
- package/dist/pwa/routes/offers.js +30 -12
- package/dist/pwa/routes/orders-action.js +51 -29
- package/dist/pwa/routes/orders-create.js +75 -20
- package/dist/pwa/routes/orders-read.js +21 -20
- package/dist/pwa/routes/p2p-products.js +30 -18
- package/dist/pwa/routes/payments-governance.js +61 -56
- package/dist/pwa/routes/peers.js +9 -8
- package/dist/pwa/routes/pin-receipts.js +13 -13
- package/dist/pwa/routes/products-aliases.js +12 -10
- package/dist/pwa/routes/products-claims.js +36 -17
- package/dist/pwa/routes/products-create.js +53 -38
- package/dist/pwa/routes/products-crud.js +17 -16
- package/dist/pwa/routes/products-links.js +49 -26
- package/dist/pwa/routes/products-list.js +6 -4
- package/dist/pwa/routes/products-meta.js +40 -39
- package/dist/pwa/routes/products-update.js +19 -5
- package/dist/pwa/routes/profile-credentials.js +20 -19
- package/dist/pwa/routes/profile-identity.js +14 -13
- package/dist/pwa/routes/profile-location.js +7 -6
- package/dist/pwa/routes/profile-placement.js +20 -19
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +58 -66
- package/dist/pwa/routes/public-build-tasks.js +19 -0
- package/dist/pwa/routes/public-utils.js +108 -46
- package/dist/pwa/routes/push.js +16 -15
- package/dist/pwa/routes/ratings.js +92 -32
- package/dist/pwa/routes/recover-key.js +66 -26
- package/dist/pwa/routes/referral.js +37 -52
- package/dist/pwa/routes/reputation.js +3 -2
- package/dist/pwa/routes/returns.js +76 -73
- package/dist/pwa/routes/reviews.js +41 -18
- package/dist/pwa/routes/rewards-apply.js +16 -15
- package/dist/pwa/routes/rewards-auto-downgrade.js +9 -7
- package/dist/pwa/routes/rewards-escrow-expire.js +7 -5
- package/dist/pwa/routes/rfqs.js +163 -85
- package/dist/pwa/routes/search.js +16 -14
- package/dist/pwa/routes/secondhand.js +25 -22
- package/dist/pwa/routes/seller-quota.js +24 -26
- package/dist/pwa/routes/share-redirects.js +60 -55
- package/dist/pwa/routes/shareables-interactions.js +34 -35
- package/dist/pwa/routes/shareables.js +55 -51
- package/dist/pwa/routes/shop-referral.js +58 -0
- package/dist/pwa/routes/shops.js +25 -20
- package/dist/pwa/routes/signaling.js +10 -9
- package/dist/pwa/routes/skill-market.js +16 -16
- package/dist/pwa/routes/skills.js +15 -14
- package/dist/pwa/routes/snf.js +14 -13
- package/dist/pwa/routes/tags.js +10 -9
- package/dist/pwa/routes/task-proposals.js +121 -0
- package/dist/pwa/routes/trial.js +72 -52
- package/dist/pwa/routes/trusted-kpi.js +20 -18
- package/dist/pwa/routes/url-claim.js +67 -28
- package/dist/pwa/routes/users-public.js +62 -70
- package/dist/pwa/routes/variants.js +12 -13
- package/dist/pwa/routes/verifier-user.js +61 -21
- package/dist/pwa/routes/verify-tasks.js +49 -25
- package/dist/pwa/routes/waitlist.js +16 -15
- package/dist/pwa/routes/wallet-read.js +75 -37
- package/dist/pwa/routes/wallet-write.js +12 -9
- package/dist/pwa/routes/webauthn.js +25 -26
- package/dist/pwa/routes/webhooks.js +26 -26
- package/dist/pwa/routes/welcome.js +45 -50
- package/dist/pwa/routes/wishlist-qa.js +29 -32
- package/dist/pwa/server.js +304 -90
- package/dist/version.js +1 -1
- package/package.json +76 -3
|
@@ -1,97 +1,102 @@
|
|
|
1
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
const PAYMENT_METHOD_KINDS = new Set(['crypto_onchain', 'bank_wire', 'card', 'mobile_wallet', 'p2p']);
|
|
2
3
|
const PAYMENT_METHOD_STATUSES = new Set(['active', 'preview', 'inactive', 'deprecated']);
|
|
3
4
|
const RPM_DIRECTIONS = new Set(['deposit', 'withdraw', 'both']);
|
|
4
5
|
const RPM_STATUSES = new Set(['active', 'paused', 'blocked']);
|
|
5
6
|
export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
6
|
-
|
|
7
|
+
// db 已全量走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
|
|
8
|
+
const { generateId, requireRootAdmin } = deps;
|
|
7
9
|
// 写支付变更审计日志
|
|
8
|
-
function logPaymentChange(entity_kind, entity_id, action, oldValue, newValue, changed_by, reason) {
|
|
9
|
-
|
|
10
|
+
async function logPaymentChange(entity_kind, entity_id, action, oldValue, newValue, changed_by, reason) {
|
|
11
|
+
await dbRun(`INSERT INTO payment_methods_log (entity_kind, entity_id, action, old_value, new_value, changed_by, reason) VALUES (?,?,?,?,?,?,?)`, [entity_kind, entity_id, action,
|
|
12
|
+
oldValue == null ? null : JSON.stringify(oldValue),
|
|
13
|
+
newValue == null ? null : JSON.stringify(newValue),
|
|
14
|
+
changed_by, reason ?? null]);
|
|
10
15
|
}
|
|
11
16
|
// ─── 治理参数 ────────────────────────────────────────────────
|
|
12
|
-
app.get('/api/governance/params', (_req, res) => {
|
|
13
|
-
const params =
|
|
17
|
+
app.get('/api/governance/params', async (_req, res) => {
|
|
18
|
+
const params = await dbAll(`
|
|
14
19
|
SELECT key, value, type, description, category, default_value, min_value, max_value, updated_at
|
|
15
20
|
FROM protocol_params
|
|
16
21
|
ORDER BY category, key
|
|
17
|
-
`)
|
|
22
|
+
`);
|
|
18
23
|
// 每个参数附最近 5 条变更
|
|
19
24
|
for (const p of params) {
|
|
20
|
-
const recent =
|
|
25
|
+
const recent = await dbAll(`
|
|
21
26
|
SELECT old_value, new_value, action, created_at
|
|
22
27
|
FROM protocol_params_log
|
|
23
28
|
WHERE key = ?
|
|
24
29
|
ORDER BY id DESC LIMIT 5
|
|
25
|
-
|
|
30
|
+
`, [p.key]);
|
|
26
31
|
p.recent_changes = recent;
|
|
27
32
|
}
|
|
28
33
|
res.json({
|
|
29
34
|
notice: 'WebAZ 协议参数公示 — COP 团队自约束:所有参数变更必须可被任何人查询。',
|
|
30
35
|
params,
|
|
31
|
-
last_change:
|
|
36
|
+
last_change: (await dbOne(`SELECT key, old_value, new_value, action, created_at FROM protocol_params_log ORDER BY id DESC LIMIT 1`)) || null,
|
|
32
37
|
});
|
|
33
38
|
});
|
|
34
|
-
app.get('/api/governance/params/:key/history', (req, res) => {
|
|
35
|
-
const param =
|
|
39
|
+
app.get('/api/governance/params/:key/history', async (req, res) => {
|
|
40
|
+
const param = await dbOne(`SELECT * FROM protocol_params WHERE key = ?`, [req.params.key]);
|
|
36
41
|
if (!param)
|
|
37
42
|
return void res.status(404).json({ error: 'param not found' });
|
|
38
|
-
const history =
|
|
43
|
+
const history = await dbAll(`
|
|
39
44
|
SELECT id, old_value, new_value, action, created_at,
|
|
40
45
|
(SELECT name FROM users WHERE id = protocol_params_log.changed_by) as changed_by_name
|
|
41
46
|
FROM protocol_params_log
|
|
42
47
|
WHERE key = ?
|
|
43
48
|
ORDER BY id DESC LIMIT 100
|
|
44
|
-
|
|
49
|
+
`, [req.params.key]);
|
|
45
50
|
res.json({ param, history });
|
|
46
51
|
});
|
|
47
52
|
// ─── 公共支付方法 ───────────────────────────────────────────
|
|
48
|
-
app.get('/api/payment-methods', (_req, res) => {
|
|
49
|
-
const rows =
|
|
50
|
-
FROM payment_methods WHERE status IN ('active','preview') ORDER BY status DESC, kind, asset`)
|
|
53
|
+
app.get('/api/payment-methods', async (_req, res) => {
|
|
54
|
+
const rows = await dbAll(`SELECT id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status, notes
|
|
55
|
+
FROM payment_methods WHERE status IN ('active','preview') ORDER BY status DESC, kind, asset`);
|
|
51
56
|
res.json({ items: rows });
|
|
52
57
|
});
|
|
53
58
|
// 某地区可用方法(fallback 到 global)
|
|
54
|
-
app.get('/api/payment-methods/for-region', (req, res) => {
|
|
59
|
+
app.get('/api/payment-methods/for-region', async (req, res) => {
|
|
55
60
|
const region = String(req.query.region || 'global');
|
|
56
61
|
const direction = String(req.query.direction || ''); // 'deposit' | 'withdraw' | '' (任意)
|
|
57
62
|
if (direction && !RPM_DIRECTIONS.has(direction)) {
|
|
58
63
|
return void res.status(400).json({ error: `direction 必须是 ${[...RPM_DIRECTIONS].join(' / ')}` });
|
|
59
64
|
}
|
|
60
|
-
const rowsRegion =
|
|
65
|
+
const rowsRegion = await dbAll(`
|
|
61
66
|
SELECT rpm.region, rpm.method_id, rpm.direction, rpm.status, rpm.min_amount, rpm.max_amount, rpm.daily_cap, rpm.notes,
|
|
62
67
|
pm.display_name, pm.display_name_en, pm.kind, pm.asset, pm.chain, pm.icon, pm.status as method_status, pm.watcher_status
|
|
63
68
|
FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
|
|
64
69
|
WHERE rpm.region = ? AND rpm.status = 'active' AND pm.status IN ('active','preview')
|
|
65
|
-
|
|
70
|
+
`, [region]);
|
|
66
71
|
const useFallback = rowsRegion.length === 0 && region !== 'global';
|
|
67
72
|
const finalRegion = useFallback ? 'global' : region;
|
|
68
|
-
const rows = useFallback ?
|
|
73
|
+
const rows = useFallback ? await dbAll(`
|
|
69
74
|
SELECT rpm.region, rpm.method_id, rpm.direction, rpm.status, rpm.min_amount, rpm.max_amount, rpm.daily_cap, rpm.notes,
|
|
70
75
|
pm.display_name, pm.display_name_en, pm.kind, pm.asset, pm.chain, pm.icon, pm.status as method_status, pm.watcher_status
|
|
71
76
|
FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
|
|
72
77
|
WHERE rpm.region = 'global' AND rpm.status = 'active' AND pm.status IN ('active','preview')
|
|
73
|
-
`)
|
|
78
|
+
`) : rowsRegion;
|
|
74
79
|
const filtered = direction
|
|
75
80
|
? rows.filter(r => r.direction === direction || r.direction === 'both')
|
|
76
81
|
: rows;
|
|
77
82
|
res.json({ region: finalRegion, fallback_from: useFallback ? region : null, items: filtered });
|
|
78
83
|
});
|
|
79
84
|
// 公共变更审计日志(COP transparency)
|
|
80
|
-
app.get('/api/payment-methods/log', (req, res) => {
|
|
85
|
+
app.get('/api/payment-methods/log', async (req, res) => {
|
|
81
86
|
const limit = Math.min(200, Math.max(10, Number(req.query.limit) || 50));
|
|
82
|
-
const rows =
|
|
83
|
-
FROM payment_methods_log ORDER BY id DESC LIMIT
|
|
87
|
+
const rows = await dbAll(`SELECT id, entity_kind, entity_id, action, old_value, new_value, changed_by, reason, created_at
|
|
88
|
+
FROM payment_methods_log ORDER BY id DESC LIMIT ?`, [limit]);
|
|
84
89
|
res.json({ items: rows });
|
|
85
90
|
});
|
|
86
91
|
// ─── Admin payment_methods CRUD(root admin only · 基础设施变更需根权限)─
|
|
87
|
-
app.get('/api/admin/payment-methods', (req, res) => {
|
|
92
|
+
app.get('/api/admin/payment-methods', async (req, res) => {
|
|
88
93
|
const user = requireRootAdmin(req, res);
|
|
89
94
|
if (!user)
|
|
90
95
|
return;
|
|
91
|
-
const rows =
|
|
96
|
+
const rows = await dbAll(`SELECT * FROM payment_methods ORDER BY status DESC, kind, asset`);
|
|
92
97
|
res.json({ items: rows });
|
|
93
98
|
});
|
|
94
|
-
app.post('/api/admin/payment-methods', (req, res) => {
|
|
99
|
+
app.post('/api/admin/payment-methods', async (req, res) => {
|
|
95
100
|
const user = requireRootAdmin(req, res);
|
|
96
101
|
if (!user)
|
|
97
102
|
return;
|
|
@@ -99,7 +104,7 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
99
104
|
const id = String(b.id || '').trim().toLowerCase();
|
|
100
105
|
if (!/^[a-z0-9_]{3,40}$/.test(id))
|
|
101
106
|
return void res.status(400).json({ error: 'id 必须是 3-40 位 [a-z0-9_](如 usdc_base)' });
|
|
102
|
-
if (
|
|
107
|
+
if (await dbOne(`SELECT 1 FROM payment_methods WHERE id = ?`, [id]))
|
|
103
108
|
return void res.status(409).json({ error: 'id 已存在' });
|
|
104
109
|
const display_name = String(b.display_name || '').trim();
|
|
105
110
|
if (display_name.length < 1 || display_name.length > 60)
|
|
@@ -122,18 +127,18 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
122
127
|
return void res.status(400).json({ error: `status 必须是 ${[...PAYMENT_METHOD_STATUSES].join(' / ')}` });
|
|
123
128
|
const notes = b.notes ? String(b.notes).slice(0, 200) : null;
|
|
124
129
|
const newRow = { id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status: 'unconfigured', notes };
|
|
125
|
-
|
|
130
|
+
await dbRun(`INSERT INTO payment_methods (
|
|
126
131
|
id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, watcher_status, notes, updated_by
|
|
127
|
-
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
128
|
-
logPaymentChange('method', id, 'create', null, newRow, user.id, String(b.reason || ''));
|
|
132
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`, [id, display_name, display_name_en, kind, asset, chain, contract_address, decimals, icon, status, 'unconfigured', notes, user.id]);
|
|
133
|
+
await logPaymentChange('method', id, 'create', null, newRow, user.id, String(b.reason || ''));
|
|
129
134
|
res.json({ ok: true, id });
|
|
130
135
|
});
|
|
131
|
-
app.put('/api/admin/payment-methods/:id', (req, res) => {
|
|
136
|
+
app.put('/api/admin/payment-methods/:id', async (req, res) => {
|
|
132
137
|
const user = requireRootAdmin(req, res);
|
|
133
138
|
if (!user)
|
|
134
139
|
return;
|
|
135
140
|
const id = req.params.id;
|
|
136
|
-
const existing =
|
|
141
|
+
const existing = await dbOne(`SELECT * FROM payment_methods WHERE id = ?`, [id]);
|
|
137
142
|
if (!existing)
|
|
138
143
|
return void res.status(404).json({ error: 'not_found' });
|
|
139
144
|
const b = req.body;
|
|
@@ -175,29 +180,29 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
175
180
|
return void res.status(400).json({ error: '无更新字段' });
|
|
176
181
|
const cols = Object.keys(updates).map(k => `${k} = ?`).join(', ');
|
|
177
182
|
const vals = Object.values(updates);
|
|
178
|
-
|
|
179
|
-
logPaymentChange('method', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
|
|
183
|
+
await dbRun(`UPDATE payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`, [...vals, user.id, id]);
|
|
184
|
+
await logPaymentChange('method', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
|
|
180
185
|
res.json({ ok: true });
|
|
181
186
|
});
|
|
182
|
-
app.delete('/api/admin/payment-methods/:id', (req, res) => {
|
|
187
|
+
app.delete('/api/admin/payment-methods/:id', async (req, res) => {
|
|
183
188
|
const user = requireRootAdmin(req, res);
|
|
184
189
|
if (!user)
|
|
185
190
|
return;
|
|
186
191
|
const id = req.params.id;
|
|
187
192
|
if (id === 'usdc_base')
|
|
188
193
|
return void res.status(400).json({ error: '默认协议方法 usdc_base 不可删除,可改为 deprecated 状态' });
|
|
189
|
-
const existing =
|
|
194
|
+
const existing = await dbOne(`SELECT * FROM payment_methods WHERE id = ?`, [id]);
|
|
190
195
|
if (!existing)
|
|
191
196
|
return void res.status(404).json({ error: 'not_found' });
|
|
192
|
-
const refs =
|
|
197
|
+
const refs = (await dbOne(`SELECT COUNT(*) as n FROM region_payment_methods WHERE method_id = ? AND status = 'active'`, [id])).n;
|
|
193
198
|
if (refs > 0)
|
|
194
199
|
return void res.status(409).json({ error: `还有 ${refs} 条 active 区域映射引用该方法,请先停用` });
|
|
195
|
-
|
|
196
|
-
logPaymentChange('method', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
|
|
200
|
+
await dbRun(`DELETE FROM payment_methods WHERE id = ?`, [id]);
|
|
201
|
+
await logPaymentChange('method', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
|
|
197
202
|
res.json({ ok: true });
|
|
198
203
|
});
|
|
199
204
|
// ─── region_payment_methods CRUD ──────────────────────────
|
|
200
|
-
app.get('/api/admin/region-payment-methods', (req, res) => {
|
|
205
|
+
app.get('/api/admin/region-payment-methods', async (req, res) => {
|
|
201
206
|
const user = requireRootAdmin(req, res);
|
|
202
207
|
if (!user)
|
|
203
208
|
return;
|
|
@@ -212,15 +217,15 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
212
217
|
params.push(String(req.query.method_id));
|
|
213
218
|
}
|
|
214
219
|
const whereSql = where.length ? `WHERE ${where.join(' AND ')}` : '';
|
|
215
|
-
const rows =
|
|
220
|
+
const rows = await dbAll(`
|
|
216
221
|
SELECT rpm.*, pm.display_name, pm.display_name_en, pm.icon, pm.asset, pm.chain
|
|
217
222
|
FROM region_payment_methods rpm JOIN payment_methods pm ON pm.id = rpm.method_id
|
|
218
223
|
${whereSql}
|
|
219
224
|
ORDER BY rpm.region, pm.kind, pm.asset
|
|
220
|
-
|
|
225
|
+
`, params);
|
|
221
226
|
res.json({ items: rows });
|
|
222
227
|
});
|
|
223
|
-
app.post('/api/admin/region-payment-methods', (req, res) => {
|
|
228
|
+
app.post('/api/admin/region-payment-methods', async (req, res) => {
|
|
224
229
|
const user = requireRootAdmin(req, res);
|
|
225
230
|
if (!user)
|
|
226
231
|
return;
|
|
@@ -229,7 +234,7 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
229
234
|
if (!/^[a-z_]{2,20}$/.test(region))
|
|
230
235
|
return void res.status(400).json({ error: 'region 必须是 2-20 位 [a-z_]' });
|
|
231
236
|
const method_id = String(b.method_id || '');
|
|
232
|
-
if (!
|
|
237
|
+
if (!(await dbOne(`SELECT 1 FROM payment_methods WHERE id = ?`, [method_id])))
|
|
233
238
|
return void res.status(404).json({ error: 'method_id 不存在' });
|
|
234
239
|
const direction = String(b.direction || 'both');
|
|
235
240
|
if (!RPM_DIRECTIONS.has(direction))
|
|
@@ -245,24 +250,24 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
245
250
|
const notes = b.notes ? String(b.notes).slice(0, 200) : null;
|
|
246
251
|
const id = generateId('rpm');
|
|
247
252
|
try {
|
|
248
|
-
|
|
253
|
+
await dbRun(`INSERT INTO region_payment_methods (
|
|
249
254
|
id, region, method_id, direction, status, min_amount, max_amount, daily_cap, notes, updated_by
|
|
250
|
-
) VALUES (?,?,?,?,?,?,?,?,?,?)
|
|
255
|
+
) VALUES (?,?,?,?,?,?,?,?,?,?)`, [id, region, method_id, direction, status, min_amount, max_amount, daily_cap, notes, user.id]);
|
|
251
256
|
}
|
|
252
257
|
catch (e) {
|
|
253
258
|
if (String(e).includes('UNIQUE'))
|
|
254
259
|
return void res.status(409).json({ error: '同一 region + method + direction 已存在' });
|
|
255
260
|
throw e;
|
|
256
261
|
}
|
|
257
|
-
logPaymentChange('region_mapping', id, 'create', null, { region, method_id, direction, status, min_amount, max_amount, daily_cap, notes }, user.id, String(b.reason || ''));
|
|
262
|
+
await logPaymentChange('region_mapping', id, 'create', null, { region, method_id, direction, status, min_amount, max_amount, daily_cap, notes }, user.id, String(b.reason || ''));
|
|
258
263
|
res.json({ ok: true, id });
|
|
259
264
|
});
|
|
260
|
-
app.put('/api/admin/region-payment-methods/:id', (req, res) => {
|
|
265
|
+
app.put('/api/admin/region-payment-methods/:id', async (req, res) => {
|
|
261
266
|
const user = requireRootAdmin(req, res);
|
|
262
267
|
if (!user)
|
|
263
268
|
return;
|
|
264
269
|
const id = req.params.id;
|
|
265
|
-
const existing =
|
|
270
|
+
const existing = await dbOne(`SELECT * FROM region_payment_methods WHERE id = ?`, [id]);
|
|
266
271
|
if (!existing)
|
|
267
272
|
return void res.status(404).json({ error: 'not_found' });
|
|
268
273
|
const b = req.body;
|
|
@@ -289,23 +294,23 @@ export function registerPaymentsGovernanceRoutes(app, deps) {
|
|
|
289
294
|
return void res.status(400).json({ error: 'min_amount 不能大于 max_amount' });
|
|
290
295
|
const cols = Object.keys(updates).map(k => `${k} = ?`).join(', ');
|
|
291
296
|
const vals = Object.values(updates);
|
|
292
|
-
|
|
293
|
-
logPaymentChange('region_mapping', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
|
|
297
|
+
await dbRun(`UPDATE region_payment_methods SET ${cols}, updated_at = datetime('now'), updated_by = ? WHERE id = ?`, [...vals, user.id, id]);
|
|
298
|
+
await logPaymentChange('region_mapping', id, 'update', existing, { ...existing, ...updates }, user.id, String(b.reason || ''));
|
|
294
299
|
res.json({ ok: true });
|
|
295
300
|
});
|
|
296
|
-
app.delete('/api/admin/region-payment-methods/:id', (req, res) => {
|
|
301
|
+
app.delete('/api/admin/region-payment-methods/:id', async (req, res) => {
|
|
297
302
|
const user = requireRootAdmin(req, res);
|
|
298
303
|
if (!user)
|
|
299
304
|
return;
|
|
300
305
|
const id = req.params.id;
|
|
301
|
-
const existing =
|
|
306
|
+
const existing = await dbOne(`SELECT * FROM region_payment_methods WHERE id = ?`, [id]);
|
|
302
307
|
if (!existing)
|
|
303
308
|
return void res.status(404).json({ error: 'not_found' });
|
|
304
309
|
if (existing.region === 'global' && existing.method_id === 'usdc_base') {
|
|
305
310
|
return void res.status(400).json({ error: '默认协议映射 global × usdc_base 不可删除' });
|
|
306
311
|
}
|
|
307
|
-
|
|
308
|
-
logPaymentChange('region_mapping', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
|
|
312
|
+
await dbRun(`DELETE FROM region_payment_methods WHERE id = ?`, [id]);
|
|
313
|
+
await logPaymentChange('region_mapping', id, 'delete', existing, null, user.id, String((req.body || {}).reason || ''));
|
|
309
314
|
res.json({ ok: true });
|
|
310
315
|
});
|
|
311
316
|
}
|
package/dist/pwa/routes/peers.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerPeersRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbRun),不再直接用 deps.db
|
|
4
|
+
const { auth } = deps;
|
|
5
|
+
app.post('/api/peers/heartbeat', async (req, res) => {
|
|
4
6
|
const me = auth(req, res);
|
|
5
7
|
if (!me)
|
|
6
8
|
return;
|
|
@@ -11,24 +13,23 @@ export function registerPeersRoutes(app, deps) {
|
|
|
11
13
|
for (const h of hashes) {
|
|
12
14
|
if (typeof h !== 'string' || !/^[a-f0-9]{64}$/.test(h))
|
|
13
15
|
continue;
|
|
14
|
-
const m =
|
|
16
|
+
const m = await dbOne("SELECT owner_id, status FROM manifest_registry WHERE hash = ?", [h]);
|
|
15
17
|
if (!m || m.status !== 'active')
|
|
16
18
|
continue;
|
|
17
19
|
const isOwner = m.owner_id === me.id ? 1 : 0;
|
|
18
20
|
const pinIntent = pinIntents.has(h) ? 1 : 0;
|
|
19
|
-
|
|
21
|
+
await dbRun(`INSERT INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
|
|
20
22
|
VALUES (?,?,?,?,?)
|
|
21
|
-
ON CONFLICT(peer_id, manifest_hash) DO UPDATE SET is_owner=excluded.is_owner, pin_intent=excluded.pin_intent, last_heartbeat=excluded.last_heartbeat
|
|
22
|
-
.run(me.id, h, isOwner, pinIntent, now);
|
|
23
|
+
ON CONFLICT(peer_id, manifest_hash) DO UPDATE SET is_owner=excluded.is_owner, pin_intent=excluded.pin_intent, last_heartbeat=excluded.last_heartbeat`, [me.id, h, isOwner, pinIntent, now]);
|
|
23
24
|
registered++;
|
|
24
25
|
}
|
|
25
26
|
res.json({ ok: true, registered });
|
|
26
27
|
});
|
|
27
|
-
app.delete('/api/peers/:hash', (req, res) => {
|
|
28
|
+
app.delete('/api/peers/:hash', async (req, res) => {
|
|
28
29
|
const me = auth(req, res);
|
|
29
30
|
if (!me)
|
|
30
31
|
return;
|
|
31
|
-
|
|
32
|
+
await dbRun("DELETE FROM peer_directory WHERE peer_id = ? AND manifest_hash = ?", [me.id, req.params.hash]);
|
|
32
33
|
res.json({ ok: true });
|
|
33
34
|
});
|
|
34
35
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerPinReceiptsRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
|
|
4
|
+
const { auth, generateId } = deps;
|
|
5
|
+
app.post('/api/pin-receipts', async (req, res) => {
|
|
4
6
|
const me = auth(req, res);
|
|
5
7
|
if (!me)
|
|
6
8
|
return;
|
|
@@ -13,27 +15,25 @@ export function registerPinReceiptsRoutes(app, deps) {
|
|
|
13
15
|
}
|
|
14
16
|
if (pinner_id === me.id)
|
|
15
17
|
return void res.json({ error: 'pinner 不能是自己' });
|
|
16
|
-
const m =
|
|
18
|
+
const m = await dbOne("SELECT 1 FROM manifest_registry WHERE hash = ? AND status = 'active'", [manifest_hash]);
|
|
17
19
|
if (!m)
|
|
18
20
|
return void res.json({ error: 'manifest 不存在或已下架' });
|
|
19
|
-
const dup =
|
|
21
|
+
const dup = await dbOne(`SELECT id FROM pin_receipts WHERE manifest_hash = ? AND pinner_id = ? AND recipient_id = ? AND served_at > datetime('now', '-1 day')`, [manifest_hash, pinner_id, me.id]);
|
|
20
22
|
if (dup)
|
|
21
23
|
return void res.json({ error: '24 小时内已有相同 pin 回执' });
|
|
22
24
|
const id = generateId('pin');
|
|
23
|
-
|
|
24
|
-
VALUES (?,?,?,?,?,?,?,?)
|
|
25
|
-
|
|
26
|
-
db.prepare(`UPDATE peer_directory SET bytes_served_total = bytes_served_total + ? WHERE peer_id = ? AND manifest_hash = ?`)
|
|
27
|
-
.run(bytes_served, pinner_id, manifest_hash);
|
|
25
|
+
await dbRun(`INSERT INTO pin_receipts (id, manifest_hash, pinner_id, recipient_id, bytes_served, served_at, pinner_sig, recipient_sig)
|
|
26
|
+
VALUES (?,?,?,?,?,?,?,?)`, [id, manifest_hash, pinner_id, me.id, bytes_served, served_at, pinner_sig, recipient_sig]);
|
|
27
|
+
await dbRun(`UPDATE peer_directory SET bytes_served_total = bytes_served_total + ? WHERE peer_id = ? AND manifest_hash = ?`, [bytes_served, pinner_id, manifest_hash]);
|
|
28
28
|
res.json({ ok: true, id });
|
|
29
29
|
});
|
|
30
|
-
app.get('/api/pin-receipts/mine', (req, res) => {
|
|
30
|
+
app.get('/api/pin-receipts/mine', async (req, res) => {
|
|
31
31
|
const me = auth(req, res);
|
|
32
32
|
if (!me)
|
|
33
33
|
return;
|
|
34
|
-
const earned =
|
|
35
|
-
const pending =
|
|
36
|
-
const recent =
|
|
34
|
+
const earned = (await dbOne(`SELECT COALESCE(SUM(rewarded_waz), 0) as total, COUNT(*) as count FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NOT NULL`, [me.id]));
|
|
35
|
+
const pending = (await dbOne(`SELECT COUNT(*) as n FROM pin_receipts WHERE pinner_id = ? AND rewarded_at IS NULL`, [me.id]));
|
|
36
|
+
const recent = await dbAll(`SELECT p.*, m.title as manifest_title FROM pin_receipts p LEFT JOIN manifest_registry m ON m.hash = p.manifest_hash WHERE p.pinner_id = ? ORDER BY p.served_at DESC LIMIT 20`, [me.id]);
|
|
37
37
|
res.json({ earned, pending_count: pending.n, recent });
|
|
38
38
|
});
|
|
39
39
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerProductsAliasesRoutes(app, deps) {
|
|
3
|
+
// db 仍保留:用于 POST /aliases 的 db.transaction(TOCTOU 防护,better-sqlite3 事务须同步)。
|
|
4
|
+
// 其余只读/单写站点已走 RFC-016 异步 seam(dbOne/dbAll/dbRun)。
|
|
2
5
|
const { db, auth, generateId, extractCandidateAliases } = deps;
|
|
3
6
|
// M7.2-5: 从外部原文提取候选 alias
|
|
4
7
|
app.post('/api/products/extract-aliases', (req, res) => {
|
|
@@ -14,24 +17,24 @@ export function registerProductsAliasesRoutes(app, deps) {
|
|
|
14
17
|
res.json({ candidates, hint: '勾选要声明为该商品 alias 的项目(≥ 6 字符;过短或过通用的项会被反作弊机制挑战)' });
|
|
15
18
|
});
|
|
16
19
|
// M7.2-7: alias CRUD(仅商品 owner)
|
|
17
|
-
app.get('/api/products/:id/aliases', (req, res) => {
|
|
20
|
+
app.get('/api/products/:id/aliases', async (req, res) => {
|
|
18
21
|
const user = auth(req, res);
|
|
19
22
|
if (!user)
|
|
20
23
|
return;
|
|
21
|
-
const p =
|
|
24
|
+
const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.id]);
|
|
22
25
|
if (!p)
|
|
23
26
|
return void res.status(404).json({ error: '商品不存在' });
|
|
24
27
|
if (p.seller_id !== user.id)
|
|
25
28
|
return void res.status(403).json({ error: '仅商品 owner 可查看 alias' });
|
|
26
|
-
const rows =
|
|
27
|
-
FROM product_aliases WHERE product_id = ? ORDER BY created_at DESC
|
|
29
|
+
const rows = await dbAll(`SELECT id, alias_type, alias_value, min_match_chars, status, challenged_at, created_at
|
|
30
|
+
FROM product_aliases WHERE product_id = ? ORDER BY created_at DESC`, [req.params.id]);
|
|
28
31
|
res.json({ aliases: rows });
|
|
29
32
|
});
|
|
30
|
-
app.post('/api/products/:id/aliases', (req, res) => {
|
|
33
|
+
app.post('/api/products/:id/aliases', async (req, res) => {
|
|
31
34
|
const user = auth(req, res);
|
|
32
35
|
if (!user)
|
|
33
36
|
return;
|
|
34
|
-
const p =
|
|
37
|
+
const p = await dbOne('SELECT seller_id, title FROM products WHERE id = ?', [req.params.id]);
|
|
35
38
|
if (!p)
|
|
36
39
|
return void res.status(404).json({ error: '商品不存在' });
|
|
37
40
|
if (p.seller_id !== user.id)
|
|
@@ -103,17 +106,16 @@ export function registerProductsAliasesRoutes(app, deps) {
|
|
|
103
106
|
}
|
|
104
107
|
res.json({ inserted: inserted.length, skipped });
|
|
105
108
|
});
|
|
106
|
-
app.delete('/api/products/:id/aliases/:aliasId', (req, res) => {
|
|
109
|
+
app.delete('/api/products/:id/aliases/:aliasId', async (req, res) => {
|
|
107
110
|
const user = auth(req, res);
|
|
108
111
|
if (!user)
|
|
109
112
|
return;
|
|
110
|
-
const p =
|
|
113
|
+
const p = await dbOne('SELECT seller_id FROM products WHERE id = ?', [req.params.id]);
|
|
111
114
|
if (!p)
|
|
112
115
|
return void res.status(404).json({ error: '商品不存在' });
|
|
113
116
|
if (p.seller_id !== user.id)
|
|
114
117
|
return void res.status(403).json({ error: '仅商品 owner 可删除 alias' });
|
|
115
|
-
const r =
|
|
116
|
-
.run(req.params.aliasId, req.params.id);
|
|
118
|
+
const r = await dbRun(`UPDATE product_aliases SET status = 'revoked' WHERE id = ? AND product_id = ?`, [req.params.aliasId, req.params.id]);
|
|
117
119
|
res.json({ success: r.changes > 0 });
|
|
118
120
|
});
|
|
119
121
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerProductsClaimsRoutes(app, deps) {
|
|
3
|
+
// 只读/单写站点走 RFC-016 异步 seam;db 保留:claim 是质押/escrow 资金路径,
|
|
4
|
+
// dup 门 + 钱包扣减 + INSERT 任务必须原子(db.transaction),Phase 3 迁 pg 行锁。
|
|
2
5
|
const { db, auth, isTrustedRole, errorRes, generateId, PRODUCT_CLAIM_TARGETS, PRODUCT_CLAIM_STAKE_DEFAULT, PRODUCT_CLAIM_DEADLINE_HOURS, PRODUCT_CLAIM_VERIFIERS_NEEDED } = deps;
|
|
3
|
-
app.post('/api/products/:id/claim', (req, res) => {
|
|
6
|
+
app.post('/api/products/:id/claim', async (req, res) => {
|
|
4
7
|
const user = auth(req, res);
|
|
5
8
|
if (!user)
|
|
6
9
|
return;
|
|
@@ -8,7 +11,7 @@ export function registerProductsClaimsRoutes(app, deps) {
|
|
|
8
11
|
if (isTrustedRole(user)) {
|
|
9
12
|
return void errorRes(res, 403, 'TRUSTED_ROLE_NO_CLAIM', '受信角色不可发起商品声明');
|
|
10
13
|
}
|
|
11
|
-
const product =
|
|
14
|
+
const product = await dbOne('SELECT * FROM products WHERE id = ?', [req.params.id]);
|
|
12
15
|
if (!product)
|
|
13
16
|
return void res.status(404).json({ error: '商品不存在' });
|
|
14
17
|
if (product.seller_id === user.id)
|
|
@@ -24,29 +27,45 @@ export function registerProductsClaimsRoutes(app, deps) {
|
|
|
24
27
|
return void res.status(400).json({ error: 'claim_text 长度需 6-500 字' });
|
|
25
28
|
}
|
|
26
29
|
const evidence_uri = req.body?.evidence_uri ? String(req.body.evidence_uri).trim().slice(0, 500) : null;
|
|
27
|
-
|
|
30
|
+
// 友好预检查(读):真正的守恒门在事务内(WHERE balance >= stake)。
|
|
31
|
+
const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
|
|
28
32
|
const stake = PRODUCT_CLAIM_STAKE_DEFAULT;
|
|
29
33
|
if (!wallet || wallet.balance < stake) {
|
|
30
34
|
return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ,当前 ${wallet?.balance ?? 0} WAZ` });
|
|
31
35
|
}
|
|
32
|
-
// 同用户对同商品同 target 只能挂一个 open claim
|
|
33
|
-
const dup = db.prepare(`SELECT id FROM product_claim_tasks WHERE product_id = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
|
|
34
|
-
.get(req.params.id, user.id, claim_target);
|
|
35
|
-
if (dup)
|
|
36
|
-
return void res.status(409).json({ error: '你已对此商品的同一项发起过 open 声明' });
|
|
37
36
|
const id = generateId('pct');
|
|
38
37
|
const deadline = new Date(Date.now() + PRODUCT_CLAIM_DEADLINE_HOURS * 3600_000).toISOString();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
// 质押/escrow 原子段(同步事务):dup 门 + 钱包扣减(守恒 guard)+ INSERT 任务。
|
|
39
|
+
try {
|
|
40
|
+
db.transaction(() => {
|
|
41
|
+
const dup = db.prepare(`SELECT id FROM product_claim_tasks WHERE product_id = ? AND claimant_id = ? AND claim_target = ? AND status = 'open'`)
|
|
42
|
+
.get(req.params.id, user.id, claim_target);
|
|
43
|
+
if (dup)
|
|
44
|
+
throw new Error('CLAIM_DUP');
|
|
45
|
+
const debit = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
|
|
46
|
+
.run(stake, stake, user.id, stake);
|
|
47
|
+
if (debit.changes === 0)
|
|
48
|
+
throw new Error('CLAIM_INSUFFICIENT');
|
|
49
|
+
db.prepare(`INSERT INTO product_claim_tasks
|
|
50
|
+
(id, product_id, claimant_id, seller_id, claim_target, claim_text, evidence_uri, stake_claimant, deadline_at, status)
|
|
51
|
+
VALUES (?,?,?,?,?,?,?,?,?,'open')`)
|
|
52
|
+
.run(id, req.params.id, user.id, product.seller_id, claim_target, claim_text, evidence_uri, stake, deadline);
|
|
53
|
+
})();
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
const msg = e.message;
|
|
57
|
+
if (msg === 'CLAIM_DUP')
|
|
58
|
+
return void res.status(409).json({ error: '你已对此商品的同一项发起过 open 声明' });
|
|
59
|
+
if (msg === 'CLAIM_INSUFFICIENT')
|
|
60
|
+
return void res.status(400).json({ error: `余额不足:发起需锁 ${stake} WAZ` });
|
|
61
|
+
console.error('[products-claims tx]', msg);
|
|
62
|
+
return void res.status(500).json({ error: '发起声明失败,请重试' });
|
|
63
|
+
}
|
|
45
64
|
res.json({ success: true, claim_id: id, deadline_at: deadline, stake_locked: stake });
|
|
46
65
|
});
|
|
47
66
|
// 公开:列出某商品的全部声明(含已结算)
|
|
48
|
-
app.get('/api/products/:id/claims', (req, res) => {
|
|
49
|
-
const rows =
|
|
67
|
+
app.get('/api/products/:id/claims', async (req, res) => {
|
|
68
|
+
const rows = await dbAll(`
|
|
50
69
|
SELECT pct.id, pct.claim_target, pct.claim_text, pct.evidence_uri, pct.status, pct.ruling, pct.deadline_at, pct.resolved_at, pct.created_at,
|
|
51
70
|
u.name as claimant_name,
|
|
52
71
|
(SELECT COUNT(*) FROM product_claim_votes WHERE claim_id = pct.id) as votes_count
|
|
@@ -54,7 +73,7 @@ export function registerProductsClaimsRoutes(app, deps) {
|
|
|
54
73
|
JOIN users u ON u.id = pct.claimant_id
|
|
55
74
|
WHERE pct.product_id = ?
|
|
56
75
|
ORDER BY pct.created_at DESC LIMIT 50
|
|
57
|
-
|
|
76
|
+
`, [req.params.id]);
|
|
58
77
|
res.json({ claims: rows, votes_needed: PRODUCT_CLAIM_VERIFIERS_NEEDED });
|
|
59
78
|
});
|
|
60
79
|
}
|