@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,4 +1,7 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerArbitratorRoutes(app, deps) {
|
|
3
|
+
// 只读/单写站点走 RFC-016 异步 seam;db 保留:apply/withdraw/approve/reject 是
|
|
4
|
+
// stake 资金路径,状态翻转 + 钱包扣/退必须原子(db.transaction + CAS),Phase 3 迁 pg 行锁。
|
|
2
5
|
const { db, generateId, auth, requireArbitrationAdmin, checkArbitratorEligibility, getArbitratorState, errorRes, logAdminAction, ARB_STAKE_REQUIRED, ARB_APP_REJECT_COOLDOWN_DAYS, } = deps;
|
|
3
6
|
app.get('/api/arbitrator/eligibility', (req, res) => {
|
|
4
7
|
const user = auth(req, res);
|
|
@@ -12,7 +15,7 @@ export function registerArbitratorRoutes(app, deps) {
|
|
|
12
15
|
return;
|
|
13
16
|
res.json(getArbitratorState(user.id));
|
|
14
17
|
});
|
|
15
|
-
app.post('/api/arbitrator/apply', (req, res) => {
|
|
18
|
+
app.post('/api/arbitrator/apply', async (req, res) => {
|
|
16
19
|
const user = auth(req, res);
|
|
17
20
|
if (!user)
|
|
18
21
|
return;
|
|
@@ -20,13 +23,13 @@ export function registerArbitratorRoutes(app, deps) {
|
|
|
20
23
|
return void errorRes(res, 403, 'ROLE_NOT_BUYER', '外部仲裁员仅 buyer 角色可申请(卖家 / 受信角色请联系管理员)');
|
|
21
24
|
}
|
|
22
25
|
const userId = user.id;
|
|
23
|
-
const wl =
|
|
26
|
+
const wl = await dbOne("SELECT 1 FROM arbitrator_whitelist WHERE user_id = ?", [userId]);
|
|
24
27
|
if (wl)
|
|
25
28
|
return void res.json({ error: '你已经是仲裁员,无需重新申请' });
|
|
26
|
-
const pending =
|
|
29
|
+
const pending = await dbOne("SELECT 1 FROM arbitrator_applications WHERE user_id = ? AND status = 'pending'", [userId]);
|
|
27
30
|
if (pending)
|
|
28
31
|
return void res.json({ error: '你已有待审申请' });
|
|
29
|
-
const lastReject =
|
|
32
|
+
const lastReject = await dbOne("SELECT reviewed_at FROM arbitrator_applications WHERE user_id = ? AND status = 'rejected' ORDER BY reviewed_at DESC LIMIT 1", [userId]);
|
|
30
33
|
if (lastReject?.reviewed_at) {
|
|
31
34
|
const cooldownEnd = new Date(new Date(lastReject.reviewed_at).getTime() + ARB_APP_REJECT_COOLDOWN_DAYS * 86400_000);
|
|
32
35
|
if (cooldownEnd > new Date()) {
|
|
@@ -36,80 +39,143 @@ export function registerArbitratorRoutes(app, deps) {
|
|
|
36
39
|
const elig = checkArbitratorEligibility(userId);
|
|
37
40
|
if (!elig.eligible)
|
|
38
41
|
return void res.json({ error: '信誉指标未达标', eligibility: elig });
|
|
42
|
+
// 友好预检查(读):真正的守恒门在事务内(WHERE balance >= stake)。
|
|
39
43
|
if (ARB_STAKE_REQUIRED > 0) {
|
|
40
|
-
const wallet =
|
|
44
|
+
const wallet = await dbOne("SELECT balance FROM wallets WHERE user_id = ?", [userId]);
|
|
41
45
|
if (!wallet || wallet.balance < ARB_STAKE_REQUIRED) {
|
|
42
46
|
return void res.json({ error: `质押需 ${ARB_STAKE_REQUIRED} WAZ,钱包余额不足` });
|
|
43
47
|
}
|
|
44
|
-
db.prepare("UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ?")
|
|
45
|
-
.run(ARB_STAKE_REQUIRED, ARB_STAKE_REQUIRED, userId);
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const appId = generateId('aapp');
|
|
50
|
+
// stake 原子段:重检 whitelist/pending(防并发双申请双质押)+ 钱包扣减(守恒 guard)+ INSERT 申请
|
|
51
|
+
try {
|
|
52
|
+
db.transaction(() => {
|
|
53
|
+
if (db.prepare("SELECT 1 FROM arbitrator_whitelist WHERE user_id = ?").get(userId))
|
|
54
|
+
throw new Error('ARB_ALREADY');
|
|
55
|
+
if (db.prepare("SELECT 1 FROM arbitrator_applications WHERE user_id = ? AND status = 'pending'").get(userId))
|
|
56
|
+
throw new Error('ARB_PENDING');
|
|
57
|
+
if (ARB_STAKE_REQUIRED > 0) {
|
|
58
|
+
const debit = db.prepare("UPDATE wallets SET balance = balance - ?, staked = staked + ? WHERE user_id = ? AND balance >= ?")
|
|
59
|
+
.run(ARB_STAKE_REQUIRED, ARB_STAKE_REQUIRED, userId, ARB_STAKE_REQUIRED);
|
|
60
|
+
if (debit.changes === 0)
|
|
61
|
+
throw new Error('ARB_INSUFFICIENT');
|
|
62
|
+
}
|
|
63
|
+
db.prepare("INSERT INTO arbitrator_applications (id, user_id, status, snapshot) VALUES (?,?,?,?)")
|
|
64
|
+
.run(appId, userId, 'pending', JSON.stringify(elig.items));
|
|
65
|
+
})();
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
const msg = e.message;
|
|
69
|
+
if (msg === 'ARB_ALREADY')
|
|
70
|
+
return void res.json({ error: '你已经是仲裁员,无需重新申请' });
|
|
71
|
+
if (msg === 'ARB_PENDING')
|
|
72
|
+
return void res.json({ error: '你已有待审申请' });
|
|
73
|
+
if (msg === 'ARB_INSUFFICIENT')
|
|
74
|
+
return void res.json({ error: `质押需 ${ARB_STAKE_REQUIRED} WAZ,钱包余额不足` });
|
|
75
|
+
console.error('[arbitrator apply tx]', msg);
|
|
76
|
+
return void res.status(500).json({ error: '申请失败,请重试' });
|
|
77
|
+
}
|
|
49
78
|
res.json({ success: true, stake_locked: ARB_STAKE_REQUIRED });
|
|
50
79
|
});
|
|
51
|
-
app.post('/api/arbitrator/withdraw-application', (req, res) => {
|
|
80
|
+
app.post('/api/arbitrator/withdraw-application', async (req, res) => {
|
|
52
81
|
const user = auth(req, res);
|
|
53
82
|
if (!user)
|
|
54
83
|
return;
|
|
55
84
|
const userId = user.id;
|
|
56
|
-
const pending =
|
|
85
|
+
const pending = await dbOne("SELECT id FROM arbitrator_applications WHERE user_id = ? AND status = 'pending' LIMIT 1", [userId]);
|
|
57
86
|
if (!pending)
|
|
58
87
|
return void res.json({ error: '没有待审申请' });
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
db.
|
|
62
|
-
.
|
|
88
|
+
// 原子段:CAS 翻转 pending→withdrawn(防并发/admin 抢跑双退)+ 退质押仅在本请求真翻转时
|
|
89
|
+
try {
|
|
90
|
+
db.transaction(() => {
|
|
91
|
+
const cas = db.prepare("UPDATE arbitrator_applications SET status='withdrawn', reviewed_at=datetime('now') WHERE id = ? AND status = 'pending'").run(pending.id);
|
|
92
|
+
if (cas.changes === 0)
|
|
93
|
+
throw new Error('ARB_RACE');
|
|
94
|
+
if (ARB_STAKE_REQUIRED > 0) {
|
|
95
|
+
db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?").run(ARB_STAKE_REQUIRED, ARB_STAKE_REQUIRED, userId);
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
if (e.message === 'ARB_RACE')
|
|
101
|
+
return void res.status(409).json({ error: '申请状态已变化,请刷新' });
|
|
102
|
+
console.error('[arbitrator withdraw tx]', e.message);
|
|
103
|
+
return void res.status(500).json({ error: '撤回失败,请重试' });
|
|
63
104
|
}
|
|
64
105
|
res.json({ success: true });
|
|
65
106
|
});
|
|
66
107
|
// Admin
|
|
67
|
-
app.get('/api/admin/arbitrator-applications', (req, res) => {
|
|
108
|
+
app.get('/api/admin/arbitrator-applications', async (req, res) => {
|
|
68
109
|
const admin = requireArbitrationAdmin(req, res);
|
|
69
110
|
if (!admin)
|
|
70
111
|
return;
|
|
71
112
|
const status = String(req.query.status || 'pending');
|
|
72
|
-
const items =
|
|
113
|
+
const items = await dbAll(`
|
|
73
114
|
SELECT aa.*, u.name as user_name, u.handle, u.region
|
|
74
115
|
FROM arbitrator_applications aa
|
|
75
116
|
JOIN users u ON u.id = aa.user_id
|
|
76
117
|
WHERE aa.status = ?
|
|
77
118
|
ORDER BY aa.applied_at DESC LIMIT 100
|
|
78
|
-
|
|
119
|
+
`, [status]);
|
|
79
120
|
res.json({ items });
|
|
80
121
|
});
|
|
81
|
-
app.post('/api/admin/arbitrator-applications/:id/approve', (req, res) => {
|
|
122
|
+
app.post('/api/admin/arbitrator-applications/:id/approve', async (req, res) => {
|
|
82
123
|
const admin = requireArbitrationAdmin(req, res);
|
|
83
124
|
if (!admin)
|
|
84
125
|
return;
|
|
85
126
|
const { note } = req.body;
|
|
86
|
-
const appRow =
|
|
127
|
+
const appRow = await dbOne("SELECT id, user_id, status FROM arbitrator_applications WHERE id = ?", [req.params.id]);
|
|
87
128
|
if (!appRow)
|
|
88
129
|
return void res.json({ error: '申请不存在' });
|
|
89
130
|
if (appRow.status !== 'pending')
|
|
90
131
|
return void res.json({ error: '该申请不在待审状态' });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
// 原子段:CAS 翻转 pending→approved + 入白名单仅在本请求真翻转时(防并发双批准)
|
|
133
|
+
try {
|
|
134
|
+
db.transaction(() => {
|
|
135
|
+
const cas = db.prepare("UPDATE arbitrator_applications SET status='approved', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
|
|
136
|
+
.run(admin.id, note || null, appRow.id);
|
|
137
|
+
if (cas.changes === 0)
|
|
138
|
+
throw new Error('ARB_RACE');
|
|
139
|
+
db.prepare(`INSERT OR REPLACE INTO arbitrator_whitelist (user_id, note, is_system, granted_by, stake_amount) VALUES (?,?,0,?,?)`)
|
|
140
|
+
.run(appRow.user_id, note || '外部仲裁员批准', admin.id, ARB_STAKE_REQUIRED);
|
|
141
|
+
})();
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
if (e.message === 'ARB_RACE')
|
|
145
|
+
return void res.json({ error: '该申请不在待审状态' });
|
|
146
|
+
console.error('[arbitrator approve tx]', e.message);
|
|
147
|
+
return void res.status(500).json({ error: '批准失败,请重试' });
|
|
148
|
+
}
|
|
95
149
|
logAdminAction(admin.id, 'approve_arbitrator', 'user', appRow.user_id, { note });
|
|
96
150
|
res.json({ success: true });
|
|
97
151
|
});
|
|
98
|
-
app.post('/api/admin/arbitrator-applications/:id/reject', (req, res) => {
|
|
152
|
+
app.post('/api/admin/arbitrator-applications/:id/reject', async (req, res) => {
|
|
99
153
|
const admin = requireArbitrationAdmin(req, res);
|
|
100
154
|
if (!admin)
|
|
101
155
|
return;
|
|
102
156
|
const { note } = req.body;
|
|
103
|
-
const appRow =
|
|
157
|
+
const appRow = await dbOne("SELECT id, user_id, status FROM arbitrator_applications WHERE id = ?", [req.params.id]);
|
|
104
158
|
if (!appRow)
|
|
105
159
|
return void res.json({ error: '申请不存在' });
|
|
106
160
|
if (appRow.status !== 'pending')
|
|
107
161
|
return void res.json({ error: '该申请不在待审状态' });
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
162
|
+
// 原子段:CAS 翻转 pending→rejected + 退质押仅在本请求真翻转时(防并发双拒双退)
|
|
163
|
+
try {
|
|
164
|
+
db.transaction(() => {
|
|
165
|
+
const cas = db.prepare("UPDATE arbitrator_applications SET status='rejected', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
|
|
166
|
+
.run(admin.id, note || null, appRow.id);
|
|
167
|
+
if (cas.changes === 0)
|
|
168
|
+
throw new Error('ARB_RACE');
|
|
169
|
+
if (ARB_STAKE_REQUIRED > 0) {
|
|
170
|
+
db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?").run(ARB_STAKE_REQUIRED, ARB_STAKE_REQUIRED, appRow.user_id);
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
if (e.message === 'ARB_RACE')
|
|
176
|
+
return void res.json({ error: '该申请不在待审状态' });
|
|
177
|
+
console.error('[arbitrator reject tx]', e.message);
|
|
178
|
+
return void res.status(500).json({ error: '拒绝失败,请重试' });
|
|
113
179
|
}
|
|
114
180
|
logAdminAction(admin.id, 'reject_arbitrator', 'user', appRow.user_id, { note });
|
|
115
181
|
res.json({ success: true });
|