@seasonkoh/webaz 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- 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 +198 -83
- 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 +173 -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 +10 -2
- 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-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-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/contract-fingerprint.js +2 -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 +575 -68
- package/dist/pwa/public/i18n.js +29 -20
- package/dist/pwa/public/index.html +1 -0
- package/dist/pwa/public/openapi.json +2 -2
- 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-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 +26 -29
- package/dist/pwa/routes/admin-ops.js +22 -21
- 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 +54 -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 +7 -5
- 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 +153 -114
- package/dist/pwa/routes/auth-login.js +6 -4
- package/dist/pwa/routes/auth-read.js +11 -9
- package/dist/pwa/routes/auth-register.js +35 -20
- 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 +147 -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 +33 -30
- 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 +33 -17
- 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 +14 -16
- 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 +19 -17
- package/dist/pwa/routes/profile-prefs.js +11 -11
- package/dist/pwa/routes/promoter.js +55 -49
- 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 +30 -30
- package/dist/pwa/routes/recover-key.js +13 -12
- package/dist/pwa/routes/referral.js +37 -32
- 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 +59 -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 +57 -0
- package/dist/pwa/routes/shops.js +20 -18
- 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 +45 -0
- package/dist/pwa/routes/trial.js +69 -51
- 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 -60
- 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 +74 -36
- 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 +237 -81
- package/dist/version.js +1 -1
- package/package.json +47 -2
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerAdminVerifierFlowRoutes(app, deps) {
|
|
3
|
+
// 只读站点走 RFC-016 异步 seam;db 保留:approve/reject/decide 含状态翻转 + 退质押 + 补发奖励,
|
|
4
|
+
// 必须原子(db.transaction + CAS 翻转,退款/奖励仅在本请求真翻转时落),Phase 3 迁 pg 行锁。
|
|
2
5
|
const { db, requireVerifierMgmtAdmin, TIER_QUOTAS, VERIFIER_STAKE_REQUIRED, todayStartISO, logAdminAction } = deps;
|
|
3
|
-
app.get('/api/admin/verifier-applications', (req, res) => {
|
|
6
|
+
app.get('/api/admin/verifier-applications', async (req, res) => {
|
|
4
7
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
5
8
|
if (!admin)
|
|
6
9
|
return;
|
|
7
10
|
const status = req.query.status || 'pending';
|
|
8
|
-
const rows =
|
|
11
|
+
const rows = await dbAll(`
|
|
9
12
|
SELECT va.id, va.user_id, va.status, va.applied_at, va.reviewed_at, va.reviewed_by, va.decision_note, va.snapshot,
|
|
10
13
|
u.name as user_name, u.email
|
|
11
14
|
FROM verifier_applications va
|
|
12
15
|
LEFT JOIN users u ON u.id = va.user_id
|
|
13
16
|
WHERE va.status = ?
|
|
14
17
|
ORDER BY va.applied_at DESC LIMIT 100
|
|
15
|
-
|
|
18
|
+
`, [status]);
|
|
16
19
|
res.json({
|
|
17
20
|
applications: rows.map(r => ({
|
|
18
21
|
...r,
|
|
@@ -25,59 +28,83 @@ export function registerAdminVerifierFlowRoutes(app, deps) {
|
|
|
25
28
|
})),
|
|
26
29
|
});
|
|
27
30
|
});
|
|
28
|
-
app.post('/api/admin/verifier-applications/:id/approve', (req, res) => {
|
|
31
|
+
app.post('/api/admin/verifier-applications/:id/approve', async (req, res) => {
|
|
29
32
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
30
33
|
if (!admin)
|
|
31
34
|
return;
|
|
32
35
|
const { tier, note } = req.body;
|
|
33
36
|
const validTier = ['trial-1', 'trial-2', 'trial-3', 'active-1', 'active-2'].includes(tier) ? tier : 'trial-1';
|
|
34
|
-
const apl =
|
|
37
|
+
const apl = await dbOne("SELECT id, user_id, status FROM verifier_applications WHERE id = ?", [req.params.id]);
|
|
35
38
|
if (!apl)
|
|
36
39
|
return void res.json({ error: '申请不存在' });
|
|
37
40
|
if (apl.status !== 'pending')
|
|
38
41
|
return void res.json({ error: '该申请不在待审状态' });
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// 原子段:CAS 翻转 pending→approved + 入白名单 + 建 stats(防并发双批准)
|
|
43
|
+
try {
|
|
44
|
+
db.transaction(() => {
|
|
45
|
+
const cas = db.prepare("UPDATE verifier_applications SET status='approved', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
|
|
46
|
+
.run(admin.id, note || null, apl.id);
|
|
47
|
+
if (cas.changes === 0)
|
|
48
|
+
throw new Error('APP_RACE');
|
|
49
|
+
db.prepare(`INSERT OR REPLACE INTO verifier_whitelist
|
|
50
|
+
(user_id, note, tier, daily_quota, tasks_today, quota_reset_at, granted_by, stake_amount, is_system)
|
|
51
|
+
VALUES (?,?,?,?,0,?,?,?,0)`)
|
|
52
|
+
.run(apl.user_id, note || `批准为 ${validTier}`, validTier, TIER_QUOTAS[validTier], todayStartISO(), admin.id, VERIFIER_STAKE_REQUIRED);
|
|
53
|
+
db.prepare("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)").run(apl.user_id);
|
|
54
|
+
})();
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
if (e.message === 'APP_RACE')
|
|
58
|
+
return void res.json({ error: '该申请不在待审状态' });
|
|
59
|
+
console.error('[verifier approve tx]', e.message);
|
|
60
|
+
return void res.status(500).json({ error: '批准失败,请重试' });
|
|
61
|
+
}
|
|
46
62
|
logAdminAction(admin.id, 'approve_verifier', 'user', apl.user_id, { tier: validTier, note });
|
|
47
63
|
res.json({ success: true });
|
|
48
64
|
});
|
|
49
|
-
app.post('/api/admin/verifier-applications/:id/reject', (req, res) => {
|
|
65
|
+
app.post('/api/admin/verifier-applications/:id/reject', async (req, res) => {
|
|
50
66
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
51
67
|
if (!admin)
|
|
52
68
|
return;
|
|
53
69
|
const { note } = req.body;
|
|
54
|
-
const apl =
|
|
70
|
+
const apl = await dbOne("SELECT id, user_id, status FROM verifier_applications WHERE id = ?", [req.params.id]);
|
|
55
71
|
if (!apl)
|
|
56
72
|
return void res.json({ error: '申请不存在' });
|
|
57
73
|
if (apl.status !== 'pending')
|
|
58
74
|
return void res.json({ error: '该申请不在待审状态' });
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.
|
|
75
|
+
// 原子段:CAS 翻转 pending→rejected + 退质押仅在本请求真翻转时(防并发双拒双退)
|
|
76
|
+
try {
|
|
77
|
+
db.transaction(() => {
|
|
78
|
+
const cas = db.prepare("UPDATE verifier_applications SET status='rejected', reviewed_at=datetime('now'), reviewed_by=?, decision_note=? WHERE id=? AND status='pending'")
|
|
79
|
+
.run(admin.id, note || null, apl.id);
|
|
80
|
+
if (cas.changes === 0)
|
|
81
|
+
throw new Error('APP_RACE');
|
|
82
|
+
if (VERIFIER_STAKE_REQUIRED > 0) {
|
|
83
|
+
db.prepare("UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ?").run(VERIFIER_STAKE_REQUIRED, VERIFIER_STAKE_REQUIRED, apl.user_id);
|
|
84
|
+
}
|
|
85
|
+
})();
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
if (e.message === 'APP_RACE')
|
|
89
|
+
return void res.json({ error: '该申请不在待审状态' });
|
|
90
|
+
console.error('[verifier reject tx]', e.message);
|
|
91
|
+
return void res.status(500).json({ error: '拒绝失败,请重试' });
|
|
65
92
|
}
|
|
66
93
|
logAdminAction(admin.id, 'reject_verifier', 'user', apl.user_id, { note });
|
|
67
94
|
res.json({ success: true });
|
|
68
95
|
});
|
|
69
|
-
app.get('/api/admin/verifier-appeals', (req, res) => {
|
|
96
|
+
app.get('/api/admin/verifier-appeals', async (req, res) => {
|
|
70
97
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
71
98
|
if (!admin)
|
|
72
99
|
return;
|
|
73
100
|
const status = req.query.status || 'pending';
|
|
74
|
-
const rows =
|
|
101
|
+
const rows = await dbAll(`
|
|
75
102
|
SELECT va.id, va.user_id, va.task_id, va.submission_id, va.reason, va.evidence_urls, va.status,
|
|
76
103
|
va.admin_note, va.reviewed_by, va.reviewed_at, va.created_at, u.name as user_name
|
|
77
104
|
FROM verifier_appeals va LEFT JOIN users u ON u.id = va.user_id
|
|
78
105
|
WHERE va.status = ?
|
|
79
106
|
ORDER BY va.created_at DESC LIMIT 100
|
|
80
|
-
|
|
107
|
+
`, [status]);
|
|
81
108
|
res.json({
|
|
82
109
|
appeals: rows.map(r => ({
|
|
83
110
|
...r,
|
|
@@ -90,35 +117,49 @@ export function registerAdminVerifierFlowRoutes(app, deps) {
|
|
|
90
117
|
})),
|
|
91
118
|
});
|
|
92
119
|
});
|
|
93
|
-
app.post('/api/admin/verifier-appeals/:id/decide', (req, res) => {
|
|
120
|
+
app.post('/api/admin/verifier-appeals/:id/decide', async (req, res) => {
|
|
94
121
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
95
122
|
if (!admin)
|
|
96
123
|
return;
|
|
97
124
|
const { decision, note } = req.body; // 'accepted' | 'rejected'
|
|
98
125
|
if (!['accepted', 'rejected'].includes(decision))
|
|
99
126
|
return void res.json({ error: 'decision 无效' });
|
|
100
|
-
const appeal =
|
|
127
|
+
const appeal = await dbOne("SELECT id, user_id, status FROM verifier_appeals WHERE id = ?", [req.params.id]);
|
|
101
128
|
if (!appeal)
|
|
102
129
|
return void res.json({ error: '申诉不存在' });
|
|
103
130
|
if (appeal.status !== 'pending')
|
|
104
131
|
return void res.json({ error: '该申诉已处理' });
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
db.prepare("
|
|
119
|
-
|
|
132
|
+
// 原子段:CAS 翻转 appeal pending→decision + (accepted 时)解封/重审/补发奖励 一起落,
|
|
133
|
+
// 防并发双裁决导致 verify_rights 多加、verdict 多翻、奖励多发。
|
|
134
|
+
try {
|
|
135
|
+
db.transaction(() => {
|
|
136
|
+
const cas = db.prepare("UPDATE verifier_appeals SET status = ?, admin_note = ?, reviewed_by = ?, reviewed_at = datetime('now') WHERE id = ? AND status = 'pending'")
|
|
137
|
+
.run(decision, note || null, admin.id, appeal.id);
|
|
138
|
+
if (cas.changes === 0)
|
|
139
|
+
throw new Error('APPEAL_RACE');
|
|
140
|
+
if (decision === 'accepted') {
|
|
141
|
+
// 解封 + 验证权 +2 + 错误次数 -1
|
|
142
|
+
db.prepare("UPDATE verifier_stats SET suspended_until = NULL, verify_rights = verify_rights + 2 WHERE user_id = ?").run(appeal.user_id);
|
|
143
|
+
db.prepare("UPDATE verifier_whitelist SET error_count_180d = MAX(0, error_count_180d - 1) WHERE user_id = ?").run(appeal.user_id);
|
|
144
|
+
// 完整重审:翻转该 verifier 在该 task 的 verdict + 补发奖励 + 翻回 stats
|
|
145
|
+
const fullAppeal = db.prepare("SELECT task_id FROM verifier_appeals WHERE id = ?").get(appeal.id);
|
|
146
|
+
if (fullAppeal?.task_id) {
|
|
147
|
+
const sub = db.prepare("SELECT vs.id, vs.verdict, vt.reward_per_verifier FROM verify_submissions vs JOIN verify_tasks vt ON vt.id = vs.task_id WHERE vs.task_id = ? AND vs.verifier_id = ?")
|
|
148
|
+
.get(fullAppeal.task_id, appeal.user_id);
|
|
149
|
+
if (sub && sub.verdict === 'wrong') {
|
|
150
|
+
db.prepare("UPDATE verify_submissions SET verdict = 'correct' WHERE id = ?").run(sub.id);
|
|
151
|
+
db.prepare("UPDATE verifier_stats SET tasks_correct = tasks_correct + 1, tasks_wrong = MAX(0, tasks_wrong - 1), verify_rights = verify_rights + 3 WHERE user_id = ?").run(appeal.user_id);
|
|
152
|
+
db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(sub.reward_per_verifier, appeal.user_id);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
120
155
|
}
|
|
121
|
-
}
|
|
156
|
+
})();
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
if (e.message === 'APPEAL_RACE')
|
|
160
|
+
return void res.json({ error: '该申诉已处理' });
|
|
161
|
+
console.error('[verifier appeal decide tx]', e.message);
|
|
162
|
+
return void res.status(500).json({ error: '裁决失败,请重试' });
|
|
122
163
|
}
|
|
123
164
|
logAdminAction(admin.id, 'decide_appeal', 'user', appeal.user_id, { decision, note: note || null });
|
|
124
165
|
res.json({ success: true });
|
|
@@ -1,40 +1,43 @@
|
|
|
1
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
3
|
+
// 单写白名单管理站点走 RFC-016 异步 seam;db 保留:revoke 是没收/退质押 + 白名单重写的
|
|
4
|
+
// 多写资金路径,必须原子(db.transaction + 防重复没收 guard),Phase 3 迁 pg 行锁。
|
|
2
5
|
const { db, requireVerifierMgmtAdmin, adminCanOperateOn, logAdminAction, INTERNAL_AUDITOR_ID, TIER_QUOTAS, REVOKE_COOLDOWN_DAYS } = deps;
|
|
3
|
-
app.get('/api/admin/verifier-whitelist', (req, res) => {
|
|
6
|
+
app.get('/api/admin/verifier-whitelist', async (req, res) => {
|
|
4
7
|
const user = requireVerifierMgmtAdmin(req, res);
|
|
5
8
|
if (!user)
|
|
6
9
|
return;
|
|
7
|
-
const list =
|
|
10
|
+
const list = await dbAll(`
|
|
8
11
|
SELECT vw.user_id, vw.added_at, vw.note, u.name, u.role
|
|
9
12
|
FROM verifier_whitelist vw
|
|
10
13
|
JOIN users u ON u.id = vw.user_id
|
|
11
14
|
ORDER BY vw.added_at ASC
|
|
12
|
-
`)
|
|
15
|
+
`);
|
|
13
16
|
res.json(list);
|
|
14
17
|
});
|
|
15
|
-
app.post('/api/admin/verifier-whitelist', (req, res) => {
|
|
18
|
+
app.post('/api/admin/verifier-whitelist', async (req, res) => {
|
|
16
19
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
17
20
|
if (!admin)
|
|
18
21
|
return;
|
|
19
22
|
const { user_id, name, note } = req.body;
|
|
20
23
|
let targetId = user_id;
|
|
21
24
|
if (!targetId && name) {
|
|
22
|
-
const found =
|
|
25
|
+
const found = await dbOne('SELECT id FROM users WHERE name = ?', [name]);
|
|
23
26
|
if (!found)
|
|
24
27
|
return void res.json({ error: `用户「${name}」不存在` });
|
|
25
28
|
targetId = found.id;
|
|
26
29
|
}
|
|
27
30
|
if (!targetId)
|
|
28
31
|
return void res.json({ error: '请提供 user_id 或 name' });
|
|
29
|
-
const target =
|
|
32
|
+
const target = await dbOne('SELECT id, name FROM users WHERE id = ?', [targetId]);
|
|
30
33
|
if (!target)
|
|
31
34
|
return void res.json({ error: '用户不存在' });
|
|
32
35
|
if (!adminCanOperateOn(admin, targetId, res))
|
|
33
36
|
return;
|
|
34
|
-
|
|
37
|
+
await dbRun('INSERT OR IGNORE INTO verifier_whitelist (user_id, note) VALUES (?, ?)', [targetId, note ?? null]);
|
|
35
38
|
res.json({ success: true, user_id: targetId, name: target.name });
|
|
36
39
|
});
|
|
37
|
-
app.delete('/api/admin/verifier-whitelist/:userId', (req, res) => {
|
|
40
|
+
app.delete('/api/admin/verifier-whitelist/:userId', async (req, res) => {
|
|
38
41
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
39
42
|
if (!admin)
|
|
40
43
|
return;
|
|
@@ -43,10 +46,10 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
43
46
|
return void res.json({ error: '内部审核员不可移除' });
|
|
44
47
|
if (!adminCanOperateOn(admin, targetId, res))
|
|
45
48
|
return;
|
|
46
|
-
|
|
49
|
+
await dbRun('DELETE FROM verifier_whitelist WHERE user_id = ?', [targetId]);
|
|
47
50
|
res.json({ success: true });
|
|
48
51
|
});
|
|
49
|
-
app.post('/api/admin/verifier-whitelist/:userId/promote', (req, res) => {
|
|
52
|
+
app.post('/api/admin/verifier-whitelist/:userId/promote', async (req, res) => {
|
|
50
53
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
51
54
|
if (!admin)
|
|
52
55
|
return;
|
|
@@ -56,17 +59,16 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
56
59
|
if (!TIER_QUOTAS[tier])
|
|
57
60
|
return void res.json({ error: 'tier 无效' });
|
|
58
61
|
const targetId = req.params.userId;
|
|
59
|
-
const wl =
|
|
62
|
+
const wl = await dbOne("SELECT is_system FROM verifier_whitelist WHERE user_id = ?", [targetId]);
|
|
60
63
|
if (!wl)
|
|
61
64
|
return void res.json({ error: '该用户不在白名单' });
|
|
62
65
|
if (wl.is_system)
|
|
63
66
|
return void res.json({ error: '系统兜底账户不可手动 promote' });
|
|
64
|
-
|
|
65
|
-
.run(tier, TIER_QUOTAS[tier], targetId);
|
|
67
|
+
await dbRun("UPDATE verifier_whitelist SET tier = ?, daily_quota = ? WHERE user_id = ?", [tier, TIER_QUOTAS[tier], targetId]);
|
|
66
68
|
logAdminAction(admin.id, 'promote_verifier', 'user', targetId, { tier });
|
|
67
69
|
res.json({ success: true });
|
|
68
70
|
});
|
|
69
|
-
app.post('/api/admin/verifier-whitelist/:userId/suspend', (req, res) => {
|
|
71
|
+
app.post('/api/admin/verifier-whitelist/:userId/suspend', async (req, res) => {
|
|
70
72
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
71
73
|
if (!admin)
|
|
72
74
|
return;
|
|
@@ -76,12 +78,12 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
76
78
|
const targetId = req.params.userId;
|
|
77
79
|
const n = Number(days) > 0 ? Number(days) : 7;
|
|
78
80
|
const until = new Date(Date.now() + n * 86400_000).toISOString();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
await dbRun("INSERT OR IGNORE INTO verifier_stats (user_id) VALUES (?)", [targetId]);
|
|
82
|
+
await dbRun("UPDATE verifier_stats SET suspended_until = ? WHERE user_id = ?", [until, targetId]);
|
|
81
83
|
logAdminAction(admin.id, 'suspend_verifier', 'user', targetId, { days: n, reason: reason || null, until });
|
|
82
84
|
res.json({ success: true, suspended_until: until });
|
|
83
85
|
});
|
|
84
|
-
app.post('/api/admin/verifier-whitelist/:userId/revoke', (req, res) => {
|
|
86
|
+
app.post('/api/admin/verifier-whitelist/:userId/revoke', async (req, res) => {
|
|
85
87
|
const admin = requireVerifierMgmtAdmin(req, res);
|
|
86
88
|
if (!admin)
|
|
87
89
|
return;
|
|
@@ -89,22 +91,48 @@ export function registerAdminVerifierWhitelistRoutes(app, deps) {
|
|
|
89
91
|
return;
|
|
90
92
|
const { reason } = req.body;
|
|
91
93
|
const targetId = req.params.userId;
|
|
92
|
-
|
|
94
|
+
// 友好预检查(读);真正的守恒 + 防重复没收门在事务内(重读 active 行 + cooldown guard)。
|
|
95
|
+
const wl = await dbOne("SELECT is_system, stake_amount FROM verifier_whitelist WHERE user_id = ?", [targetId]);
|
|
93
96
|
if (!wl)
|
|
94
97
|
return void res.json({ error: '该用户不在白名单' });
|
|
95
98
|
if (wl.is_system)
|
|
96
99
|
return void res.json({ error: '系统兜底账户不可撤销' });
|
|
97
100
|
const cooldownUntil = new Date(Date.now() + REVOKE_COOLDOWN_DAYS * 86400_000).toISOString();
|
|
98
|
-
// 没收 50%
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
// 原子段:重读 active 行 → 没收 50% + 退还另一半 + DELETE active + INSERT cooldown 一起落。
|
|
102
|
+
// cooldown guard 防并发两次 revoke 重复没收/退款。
|
|
103
|
+
let forfeit = 0;
|
|
104
|
+
try {
|
|
105
|
+
forfeit = db.transaction(() => {
|
|
106
|
+
const cur = db.prepare("SELECT is_system, stake_amount, cooldown_until FROM verifier_whitelist WHERE user_id = ?")
|
|
107
|
+
.get(targetId);
|
|
108
|
+
if (!cur)
|
|
109
|
+
throw new Error('REVOKE_GONE');
|
|
110
|
+
if (cur.is_system)
|
|
111
|
+
throw new Error('REVOKE_SYSTEM');
|
|
112
|
+
if (cur.cooldown_until)
|
|
113
|
+
throw new Error('REVOKE_ALREADY'); // 已在撤销冷却,不再没收一次
|
|
114
|
+
const stakeAmt = cur.stake_amount || 0;
|
|
115
|
+
const f = Math.round(stakeAmt * 0.5 * 100) / 100;
|
|
116
|
+
db.prepare("UPDATE wallets SET staked = staked - ? WHERE user_id = ?").run(stakeAmt, targetId);
|
|
117
|
+
if (stakeAmt > f)
|
|
118
|
+
db.prepare("UPDATE wallets SET balance = balance + ? WHERE user_id = ?").run(stakeAmt - f, targetId);
|
|
119
|
+
db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
|
|
120
|
+
db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
|
|
121
|
+
.run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
|
|
122
|
+
return f;
|
|
123
|
+
})();
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
const msg = e.message;
|
|
127
|
+
if (msg === 'REVOKE_GONE')
|
|
128
|
+
return void res.json({ error: '该用户不在白名单' });
|
|
129
|
+
if (msg === 'REVOKE_SYSTEM')
|
|
130
|
+
return void res.json({ error: '系统兜底账户不可撤销' });
|
|
131
|
+
if (msg === 'REVOKE_ALREADY')
|
|
132
|
+
return void res.json({ error: '该用户已在撤销冷却中' });
|
|
133
|
+
console.error('[verifier revoke tx]', msg);
|
|
134
|
+
return void res.status(500).json({ error: '撤销失败,请重试' });
|
|
103
135
|
}
|
|
104
|
-
db.prepare("DELETE FROM verifier_whitelist WHERE user_id = ?").run(targetId);
|
|
105
|
-
// 用 cooldown 记录在 verifier_stats 上(应用层兼容)
|
|
106
|
-
db.prepare(`INSERT INTO verifier_whitelist (user_id, note, tier, daily_quota, cooldown_until, is_system) VALUES (?,?,?,?,?,0)`)
|
|
107
|
-
.run(targetId, `撤销冷却中: ${reason || ''}`, 'trial-1', 0, cooldownUntil);
|
|
108
136
|
logAdminAction(admin.id, 'revoke_verifier', 'user', targetId, { reason: reason || null, forfeit, cooldown_until: cooldownUntil });
|
|
109
137
|
res.json({ success: true, cooldown_until: cooldownUntil, forfeit });
|
|
110
138
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerAdminWalletOpsRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
|
|
4
|
+
const { requireProtocolAdmin, adminAuth, getPublicClient, getUsdcAddr, getUsdcAbi, getHotWalletAddr, wazToUsdc, getIsMainnet, getNetwork, executeWithdrawal } = deps;
|
|
3
5
|
// P2-5: protocol 权限(区域 admin 看不到全局热钱包)
|
|
4
6
|
app.get('/api/admin/hot-wallet/status', async (req, res) => {
|
|
5
7
|
const admin = requireProtocolAdmin(req, res);
|
|
@@ -12,7 +14,7 @@ export function registerAdminWalletOpsRoutes(app, deps) {
|
|
|
12
14
|
address: getUsdcAddr(), abi: getUsdcAbi(), functionName: 'balanceOf', args: [hw],
|
|
13
15
|
});
|
|
14
16
|
const ethBal = await pc.getBalance({ address: hw });
|
|
15
|
-
const pending =
|
|
17
|
+
const pending = (await dbOne("SELECT COALESCE(SUM(amount), 0) as t FROM withdrawal_requests WHERE status = 'pending'"));
|
|
16
18
|
const pendingUsdc = wazToUsdc(Number(pending.t));
|
|
17
19
|
res.json({
|
|
18
20
|
address: hw,
|
|
@@ -45,14 +47,14 @@ export function registerAdminWalletOpsRoutes(app, deps) {
|
|
|
45
47
|
res.json({ address: hw, usdc_balance: null, error: e.message });
|
|
46
48
|
}
|
|
47
49
|
});
|
|
48
|
-
app.get('/api/admin/withdrawals', (req, res) => {
|
|
50
|
+
app.get('/api/admin/withdrawals', async (req, res) => {
|
|
49
51
|
if (!adminAuth(req, res))
|
|
50
52
|
return;
|
|
51
|
-
const list =
|
|
53
|
+
const list = await dbAll(`
|
|
52
54
|
SELECT wr.*, u.name as user_name
|
|
53
55
|
FROM withdrawal_requests wr JOIN users u ON wr.user_id = u.id
|
|
54
56
|
WHERE wr.status = 'pending' ORDER BY wr.created_at ASC
|
|
55
|
-
`)
|
|
57
|
+
`);
|
|
56
58
|
res.json(list);
|
|
57
59
|
});
|
|
58
60
|
app.post('/api/admin/withdrawals/:id/approve', async (req, res) => {
|
|
@@ -163,33 +163,57 @@ ${webazFormatted.length > 0 ? JSON.stringify(webazFormatted.map(p => ({
|
|
|
163
163
|
const now = new Date();
|
|
164
164
|
const expiresAt = new Date(now.getTime() + 10 * 60_000);
|
|
165
165
|
sessionToken = generateId('pst');
|
|
166
|
-
db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
|
|
167
|
-
.run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
|
|
168
|
-
verifiedPrice = product.price;
|
|
169
166
|
const oId = generateId('ord');
|
|
170
167
|
const totalAmount = product.price;
|
|
171
168
|
const seller = db.prepare('SELECT id FROM users WHERE id = ?').get(product.seller_id);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
169
|
+
// 原子核心:余额扣款(balance>=amount 守卫)+ 库存 CAS(stock>=1)+ 建单 + 价格锁,任一 changes!==1 抛回滚整笔。
|
|
170
|
+
// 注:transition() 自带 db.transaction,不能嵌套进来(better-sqlite3 禁套娃);故状态推进 + 通知放 tx 提交后,
|
|
171
|
+
// 与原顺序一致(原本就是 insert(created) → 扣款 → transition(paid))。守卫杜绝并发超卖/超扣 + 半写(Phase 3 pg 安全)。
|
|
172
|
+
let committed = false;
|
|
173
|
+
try {
|
|
174
|
+
db.transaction(() => {
|
|
175
|
+
const deb = db.prepare('UPDATE wallets SET balance = balance - ?, escrowed = escrowed + ? WHERE user_id = ? AND balance >= ?')
|
|
176
|
+
.run(totalAmount, totalAmount, user.id, totalAmount);
|
|
177
|
+
if (deb.changes !== 1)
|
|
178
|
+
throw new Error('AGENTBUY_INSUFFICIENT_BALANCE');
|
|
179
|
+
const dec = db.prepare('UPDATE products SET stock = stock - 1 WHERE id = ? AND stock >= 1').run(product.id);
|
|
180
|
+
if (dec.changes !== 1)
|
|
181
|
+
throw new Error('AGENTBUY_OUT_OF_STOCK');
|
|
182
|
+
db.prepare(`INSERT INTO price_sessions (token, product_id, user_id, price, quantity, created_at, expires_at) VALUES (?,?,?,?,1,?,?)`)
|
|
183
|
+
.run(sessionToken, product.id, user.id, product.price, now.toISOString(), expiresAt.toISOString());
|
|
184
|
+
db.prepare(`INSERT INTO orders (
|
|
185
|
+
id, product_id, buyer_id, seller_id, quantity, unit_price, total_amount, escrow_amount,
|
|
186
|
+
status, shipping_address, notes, pay_deadline, accept_deadline, ship_deadline,
|
|
187
|
+
pickup_deadline, delivery_deadline, confirm_deadline
|
|
188
|
+
) VALUES (?,?,?,?,1,?,?,?,'created',?,?,?,?,?,?,?,?)`).run(oId, product.id, user.id, seller.id, totalAmount, totalAmount, totalAmount, shipping_address, `[智能下单] ${decision.reason}`, addHours(now, 24), addHours(now, 48), addHours(now, 120), addHours(now, 168), addHours(now, 336), addHours(now, 408));
|
|
189
|
+
db.prepare(`UPDATE price_sessions SET used_at = datetime('now') WHERE token = ?`).run(sessionToken);
|
|
190
|
+
committed = true;
|
|
191
|
+
})();
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
const m = e.message;
|
|
195
|
+
if (m !== 'AGENTBUY_INSUFFICIENT_BALANCE' && m !== 'AGENTBUY_OUT_OF_STOCK')
|
|
196
|
+
throw e;
|
|
197
|
+
// 并发售罄 / 余额已变 → 不下单(auto_bought=false),在 reason 里说明
|
|
198
|
+
sessionToken = null;
|
|
199
|
+
decision.reason = (decision.reason || '') + (m === 'AGENTBUY_OUT_OF_STOCK' ? ' · auto_buy 跳过:商品已售罄' : ' · auto_buy 跳过:余额不足');
|
|
200
|
+
}
|
|
201
|
+
if (committed) {
|
|
202
|
+
// tx 提交后:状态推进 + 通知(transition 自带事务,故置于此)
|
|
203
|
+
checkStockAndMaybeDelist(String(product.id));
|
|
204
|
+
transition(db, oId, 'paid', user.id, [], '智能下单:模拟支付完成');
|
|
205
|
+
notifyTransition(db, oId, 'created', 'paid');
|
|
206
|
+
if (shouldAutoAccept(db, oId)) {
|
|
207
|
+
const sys = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
|
|
208
|
+
if (sys) {
|
|
209
|
+
const ar = transition(db, oId, 'accepted', sys.id, [], '⚡ auto_accept Skill 自动接单');
|
|
210
|
+
if (ar.success)
|
|
211
|
+
notifyTransition(db, oId, 'paid', 'accepted');
|
|
212
|
+
}
|
|
190
213
|
}
|
|
214
|
+
verifiedPrice = product.price;
|
|
215
|
+
orderId = oId;
|
|
191
216
|
}
|
|
192
|
-
orderId = oId;
|
|
193
217
|
}
|
|
194
218
|
}
|
|
195
219
|
}
|