@seasonkoh/webaz 0.1.24 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +165 -64
- 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,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
2
3
|
const MANIFEST_DAILY_LIMIT = 20;
|
|
3
4
|
const THUMB_MAX_BYTES = 12000; // ~12KB base64 ≈ 9KB 原始图
|
|
4
5
|
function verifyManifestSig(hash, ownerId, contentType, byteSize, signedAt, apiKey, signature) {
|
|
@@ -7,8 +8,9 @@ function verifyManifestSig(hash, ownerId, contentType, byteSize, signedAt, apiKe
|
|
|
7
8
|
return expected === signature;
|
|
8
9
|
}
|
|
9
10
|
export function registerManifestsRoutes(app, deps) {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbAll/dbRun),不再直接用 deps.db
|
|
12
|
+
const { auth, safeRoles } = deps;
|
|
13
|
+
app.post('/api/manifests', async (req, res) => {
|
|
12
14
|
const me = auth(req, res);
|
|
13
15
|
if (!me)
|
|
14
16
|
return;
|
|
@@ -29,21 +31,20 @@ export function registerManifestsRoutes(app, deps) {
|
|
|
29
31
|
return void res.json({ error: '签名验证失败' });
|
|
30
32
|
}
|
|
31
33
|
// 日上限
|
|
32
|
-
const todayCount =
|
|
34
|
+
const todayCount = (await dbOne(`SELECT COUNT(*) as n FROM manifest_registry WHERE owner_id = ? AND created_at > datetime('now', '-1 day')`, [me.id])).n;
|
|
33
35
|
if (todayCount >= MANIFEST_DAILY_LIMIT)
|
|
34
36
|
return void res.json({ error: `每日上限 ${MANIFEST_DAILY_LIMIT} 条` });
|
|
35
37
|
if (related_product_id) {
|
|
36
|
-
const p =
|
|
38
|
+
const p = await dbOne("SELECT id FROM products WHERE id = ?", [related_product_id]);
|
|
37
39
|
if (!p)
|
|
38
40
|
return void res.json({ error: '关联商品不存在' });
|
|
39
41
|
}
|
|
40
42
|
try {
|
|
41
|
-
|
|
42
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
|
43
|
-
.run(hash, me.id, content_type, byte_size, title || null, description || null, thumbnail_data_uri || null, signature, signed_at, related_product_id || null, related_anchor || null);
|
|
43
|
+
await dbRun(`INSERT INTO manifest_registry (hash, owner_id, content_type, byte_size, title, description, thumbnail_data_uri, signature, signed_at, related_product_id, related_anchor)
|
|
44
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?)`, [hash, me.id, content_type, byte_size, title || null, description || null, thumbnail_data_uri || null, signature, signed_at, related_product_id || null, related_anchor || null]);
|
|
44
45
|
// 创作者立即注册为 owner peer
|
|
45
|
-
|
|
46
|
-
VALUES (?,?,1,1,datetime('now'))
|
|
46
|
+
await dbRun(`INSERT OR REPLACE INTO peer_directory (peer_id, manifest_hash, is_owner, pin_intent, last_heartbeat)
|
|
47
|
+
VALUES (?,?,1,1,datetime('now'))`, [me.id, hash]);
|
|
47
48
|
res.json({ ok: true, hash });
|
|
48
49
|
}
|
|
49
50
|
catch (e) {
|
|
@@ -53,63 +54,63 @@ export function registerManifestsRoutes(app, deps) {
|
|
|
53
54
|
res.json({ error: '发布失败:' + msg });
|
|
54
55
|
}
|
|
55
56
|
});
|
|
56
|
-
app.get('/api/manifests/me', (req, res) => {
|
|
57
|
+
app.get('/api/manifests/me', async (req, res) => {
|
|
57
58
|
const me = auth(req, res);
|
|
58
59
|
if (!me)
|
|
59
60
|
return;
|
|
60
|
-
const rows =
|
|
61
|
+
const rows = await dbAll(`
|
|
61
62
|
SELECT m.*, p.title as product_title FROM manifest_registry m
|
|
62
63
|
LEFT JOIN products p ON p.id = m.related_product_id
|
|
63
64
|
WHERE m.owner_id = ? AND m.status != 'removed'
|
|
64
65
|
ORDER BY m.created_at DESC LIMIT 100
|
|
65
|
-
|
|
66
|
+
`, [me.id]);
|
|
66
67
|
res.json({ manifests: rows });
|
|
67
68
|
});
|
|
68
|
-
app.get('/api/manifests/:hash', (req, res) => {
|
|
69
|
+
app.get('/api/manifests/:hash', async (req, res) => {
|
|
69
70
|
const me = auth(req, res);
|
|
70
71
|
if (!me)
|
|
71
72
|
return;
|
|
72
|
-
const m =
|
|
73
|
+
const m = await dbOne(`SELECT * FROM manifest_registry WHERE hash = ?`, [req.params.hash]);
|
|
73
74
|
if (!m)
|
|
74
75
|
return void res.status(404).json({ error: 'manifest 不存在' });
|
|
75
76
|
if (m.status === 'removed' || m.status === 'takedown_admin')
|
|
76
77
|
return void res.json({ error: '内容已下架', removed: true, reason: m.takedown_reason || null });
|
|
77
|
-
const peers =
|
|
78
|
+
const peers = await dbAll(`
|
|
78
79
|
SELECT peer_id, is_owner, pin_intent, last_heartbeat FROM peer_directory
|
|
79
80
|
WHERE manifest_hash = ? AND last_heartbeat > datetime('now', '-5 minutes')
|
|
80
81
|
ORDER BY is_owner DESC, last_heartbeat DESC LIMIT 30
|
|
81
|
-
|
|
82
|
+
`, [req.params.hash]);
|
|
82
83
|
res.json({ manifest: m, peers });
|
|
83
84
|
});
|
|
84
|
-
app.get('/api/manifests/by-product/:pid', (req, res) => {
|
|
85
|
+
app.get('/api/manifests/by-product/:pid', async (req, res) => {
|
|
85
86
|
const user = auth(req, res);
|
|
86
87
|
if (!user)
|
|
87
88
|
return;
|
|
88
|
-
const rows =
|
|
89
|
+
const rows = await dbAll(`
|
|
89
90
|
SELECT m.*, u.name as owner_name FROM manifest_registry m
|
|
90
91
|
LEFT JOIN users u ON u.id = m.owner_id
|
|
91
92
|
WHERE m.related_product_id = ? AND m.status = 'active'
|
|
92
93
|
ORDER BY m.created_at DESC LIMIT 20
|
|
93
|
-
|
|
94
|
+
`, [req.params.pid]);
|
|
94
95
|
res.json({ manifests: rows });
|
|
95
96
|
});
|
|
96
|
-
app.get('/api/manifests/by-anchor/:anchor', (req, res) => {
|
|
97
|
+
app.get('/api/manifests/by-anchor/:anchor', async (req, res) => {
|
|
97
98
|
const user = auth(req, res);
|
|
98
99
|
if (!user)
|
|
99
100
|
return;
|
|
100
|
-
const rows =
|
|
101
|
+
const rows = await dbAll(`
|
|
101
102
|
SELECT m.*, u.name as owner_name FROM manifest_registry m
|
|
102
103
|
LEFT JOIN users u ON u.id = m.owner_id
|
|
103
104
|
WHERE m.related_anchor = ? AND m.status = 'active'
|
|
104
105
|
ORDER BY m.created_at DESC LIMIT 50
|
|
105
|
-
|
|
106
|
+
`, [req.params.anchor]);
|
|
106
107
|
res.json({ manifests: rows });
|
|
107
108
|
});
|
|
108
|
-
app.patch('/api/manifests/:hash/takedown', (req, res) => {
|
|
109
|
+
app.patch('/api/manifests/:hash/takedown', async (req, res) => {
|
|
109
110
|
const me = auth(req, res);
|
|
110
111
|
if (!me)
|
|
111
112
|
return;
|
|
112
|
-
const m =
|
|
113
|
+
const m = await dbOne("SELECT owner_id FROM manifest_registry WHERE hash = ?", [req.params.hash]);
|
|
113
114
|
if (!m)
|
|
114
115
|
return void res.status(404).json({ error: 'manifest 不存在' });
|
|
115
116
|
const isAdmin = me.role === 'admin' || safeRoles(me).includes('admin');
|
|
@@ -117,10 +118,9 @@ export function registerManifestsRoutes(app, deps) {
|
|
|
117
118
|
if (!isOwner && !isAdmin)
|
|
118
119
|
return void res.json({ error: '无权下架' });
|
|
119
120
|
const reason = (req.body?.reason || '').toString().slice(0, 200);
|
|
120
|
-
|
|
121
|
-
.run(isAdmin && !isOwner ? 'takedown_admin' : 'removed', reason, me.id, req.params.hash);
|
|
121
|
+
await dbRun(`UPDATE manifest_registry SET status = ?, takedown_reason = ?, takedown_at = datetime('now'), takedown_by = ? WHERE hash = ?`, [isAdmin && !isOwner ? 'takedown_admin' : 'removed', reason, me.id, req.params.hash]);
|
|
122
122
|
// 同步清空 peer directory(强制客户端 evict)
|
|
123
|
-
|
|
123
|
+
await dbRun("DELETE FROM peer_directory WHERE manifest_hash = ?", [req.params.hash]);
|
|
124
124
|
res.json({ ok: true });
|
|
125
125
|
});
|
|
126
126
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerMeDataRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
+
// db 已全量走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
|
|
4
|
+
const { auth } = deps;
|
|
3
5
|
// COP 飞轮: 完成订单 7d 引导发笔记
|
|
4
|
-
app.get('/api/me/note-prompts', (req, res) => {
|
|
6
|
+
app.get('/api/me/note-prompts', async (req, res) => {
|
|
5
7
|
const user = auth(req, res);
|
|
6
8
|
if (!user)
|
|
7
9
|
return;
|
|
8
|
-
const rows =
|
|
10
|
+
const rows = await dbAll(`
|
|
9
11
|
SELECT o.id as order_id, o.product_id, o.updated_at as completed_at, o.total_amount,
|
|
10
12
|
p.title as product_title, p.images as product_images
|
|
11
13
|
FROM orders o
|
|
@@ -19,7 +21,7 @@ export function registerMeDataRoutes(app, deps) {
|
|
|
19
21
|
)
|
|
20
22
|
ORDER BY o.updated_at DESC
|
|
21
23
|
LIMIT 10
|
|
22
|
-
|
|
24
|
+
`, [user.id, user.id]);
|
|
23
25
|
const prompts = rows.map(r => {
|
|
24
26
|
let firstImage = null;
|
|
25
27
|
try {
|
|
@@ -40,7 +42,7 @@ export function registerMeDataRoutes(app, deps) {
|
|
|
40
42
|
res.json({ prompts });
|
|
41
43
|
});
|
|
42
44
|
// COP P0-1: 数据导出(用户主权)
|
|
43
|
-
app.get('/api/me/export', (req, res) => {
|
|
45
|
+
app.get('/api/me/export', async (req, res) => {
|
|
44
46
|
const user = auth(req, res);
|
|
45
47
|
if (!user)
|
|
46
48
|
return;
|
|
@@ -51,33 +53,33 @@ export function registerMeDataRoutes(app, deps) {
|
|
|
51
53
|
notice: 'WebAZ COP 承诺:你的数据属于你。可随时导出,可随时迁出。',
|
|
52
54
|
};
|
|
53
55
|
try {
|
|
54
|
-
data.profile =
|
|
55
|
-
data.wallet =
|
|
56
|
-
data.orders =
|
|
57
|
-
data.shareables =
|
|
58
|
-
data.bookmarks =
|
|
59
|
-
data.likes =
|
|
60
|
-
data.follows_following =
|
|
61
|
-
data.follows_followers =
|
|
62
|
-
data.addresses =
|
|
63
|
-
data.kyc =
|
|
56
|
+
data.profile = await dbOne(`SELECT id, name, handle, role, region, bio, search_anchor, email, phone, permanent_code, created_at, reputation FROM users WHERE id = ?`, [uid]);
|
|
57
|
+
data.wallet = await dbOne(`SELECT balance, staked, escrowed, earned FROM wallets WHERE user_id = ?`, [uid]);
|
|
58
|
+
data.orders = await dbAll(`SELECT * FROM orders WHERE buyer_id = ? OR seller_id = ? ORDER BY created_at DESC LIMIT 1000`, [uid, uid]);
|
|
59
|
+
data.shareables = await dbAll(`SELECT * FROM shareables WHERE owner_id = ? AND status != 'removed'`, [uid]);
|
|
60
|
+
data.bookmarks = await dbAll(`SELECT b.*, s.title FROM shareable_bookmarks b LEFT JOIN shareables s ON s.id = b.shareable_id WHERE b.user_id = ?`, [uid]);
|
|
61
|
+
data.likes = await dbAll(`SELECT l.*, s.title FROM shareable_likes l LEFT JOIN shareables s ON s.id = l.shareable_id WHERE l.user_id = ?`, [uid]);
|
|
62
|
+
data.follows_following = await dbAll(`SELECT followee_id, created_at FROM follows WHERE follower_id = ?`, [uid]);
|
|
63
|
+
data.follows_followers = await dbAll(`SELECT follower_id, created_at FROM follows WHERE followee_id = ?`, [uid]);
|
|
64
|
+
data.addresses = await dbAll(`SELECT * FROM user_addresses WHERE user_id = ?`, [uid]);
|
|
65
|
+
data.kyc = await dbOne(`SELECT status, id_type, id_number_last4, submitted_at, reviewed_at FROM kyc_records WHERE user_id = ?`, [uid]);
|
|
64
66
|
// #1017: wallet_history 不存在 — 用 deposits + withdrawals + commissions 复合
|
|
65
67
|
try {
|
|
66
|
-
data.deposits =
|
|
68
|
+
data.deposits = await dbAll(`SELECT * FROM deposit_txns WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`, [uid]);
|
|
67
69
|
}
|
|
68
70
|
catch {
|
|
69
71
|
data.deposits = [];
|
|
70
72
|
}
|
|
71
73
|
try {
|
|
72
|
-
data.withdrawals =
|
|
74
|
+
data.withdrawals = await dbAll(`SELECT * FROM withdrawal_requests WHERE user_id = ? ORDER BY created_at DESC LIMIT 200`, [uid]);
|
|
73
75
|
}
|
|
74
76
|
catch {
|
|
75
77
|
data.withdrawals = [];
|
|
76
78
|
}
|
|
77
|
-
data.commissions =
|
|
78
|
-
data.anchors =
|
|
79
|
-
data.notifications =
|
|
80
|
-
data.error_log =
|
|
79
|
+
data.commissions = await dbAll(`SELECT * FROM commission_records WHERE beneficiary_id = ? ORDER BY created_at DESC LIMIT 500`, [uid]) || [];
|
|
80
|
+
data.anchors = await dbAll(`SELECT anchor, target_kind, target_id, status, created_at FROM anchor_registry WHERE owner_id = ?`, [uid]);
|
|
81
|
+
data.notifications = await dbAll(`SELECT * FROM notifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 500`, [uid]);
|
|
82
|
+
data.error_log = await dbAll(`SELECT id, source, message, created_at FROM error_log WHERE user_id = ? ORDER BY id DESC LIMIT 100`, [uid]) || [];
|
|
81
83
|
}
|
|
82
84
|
catch (e) {
|
|
83
85
|
console.warn('[export] partial:', e.message);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { dbOne } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
import { getNotifications, getUnreadCount, markRead } from '../../layer2-business/L2-6-notifications/notification-engine.js';
|
|
2
3
|
export function registerNotificationsRoutes(app, deps) {
|
|
3
4
|
const { db, auth, sseClients } = deps;
|
|
4
5
|
// SSE 实时推送流(EventSource 不支持自定义 header,URL ?key= 也兼容)
|
|
5
|
-
app.get('/api/notifications/stream', (req, res) => {
|
|
6
|
+
app.get('/api/notifications/stream', async (req, res) => {
|
|
6
7
|
const key = req.query.key ?? req.headers.authorization?.replace('Bearer ', '');
|
|
7
|
-
const user = key ?
|
|
8
|
+
const user = key ? await dbOne('SELECT * FROM users WHERE api_key = ?', [key]) : null;
|
|
8
9
|
if (!user)
|
|
9
10
|
return void res.status(401).end();
|
|
10
11
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
@@ -13,7 +14,7 @@ export function registerNotificationsRoutes(app, deps) {
|
|
|
13
14
|
res.flushHeaders();
|
|
14
15
|
sseClients.set(user.id, res);
|
|
15
16
|
// 连接时推送未读数
|
|
16
|
-
const unread = getUnreadCount(db, user.id);
|
|
17
|
+
const unread = await getUnreadCount(db, user.id);
|
|
17
18
|
res.write(`data: ${JSON.stringify({ type: 'init', unread })}\n\n`);
|
|
18
19
|
// 心跳保活(每 30s)
|
|
19
20
|
const heartbeat = setInterval(() => {
|
|
@@ -29,13 +30,13 @@ export function registerNotificationsRoutes(app, deps) {
|
|
|
29
30
|
clearInterval(heartbeat);
|
|
30
31
|
});
|
|
31
32
|
});
|
|
32
|
-
app.get('/api/notifications', (req, res) => {
|
|
33
|
+
app.get('/api/notifications', async (req, res) => {
|
|
33
34
|
const user = auth(req, res);
|
|
34
35
|
if (!user)
|
|
35
36
|
return;
|
|
36
37
|
const onlyUnread = req.query.unread === '1';
|
|
37
|
-
const notifs = getNotifications(db, user.id, onlyUnread);
|
|
38
|
-
const unread = getUnreadCount(db, user.id);
|
|
38
|
+
const notifs = await getNotifications(db, user.id, onlyUnread);
|
|
39
|
+
const unread = await getUnreadCount(db, user.id);
|
|
39
40
|
res.json({ unread, notifications: notifs });
|
|
40
41
|
});
|
|
41
42
|
app.post('/api/notifications/read', (req, res) => {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { dbOne, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerOffersRoutes(app, deps) {
|
|
3
|
+
// db 仍保留:用于 DELETE /offers 的 db.transaction(撤回 offer + 释放质押守恒,better-sqlite3 事务须同步)。
|
|
4
|
+
// 其余只读/单写站点已走 RFC-016 异步 seam(dbOne/dbRun)。
|
|
2
5
|
const { db, auth, VALID_FULFILLMENT_TYPES } = deps;
|
|
3
|
-
app.patch('/api/offers/:id', (req, res) => {
|
|
6
|
+
app.patch('/api/offers/:id', async (req, res) => {
|
|
4
7
|
const user = auth(req, res);
|
|
5
8
|
if (!user)
|
|
6
9
|
return;
|
|
7
|
-
const offer =
|
|
10
|
+
const offer = await dbOne("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
|
|
8
11
|
if (!offer)
|
|
9
12
|
return void res.status(404).json({ error: 'offer 不存在' });
|
|
10
13
|
if (offer.seller_id !== user.id)
|
|
@@ -48,27 +51,37 @@ export function registerOffersRoutes(app, deps) {
|
|
|
48
51
|
updates.push("updated_at = datetime('now')");
|
|
49
52
|
updates.push("freshness_ts = datetime('now')");
|
|
50
53
|
args.push(req.params.id);
|
|
51
|
-
|
|
54
|
+
await dbRun(`UPDATE products SET ${updates.join(', ')} WHERE id = ?`, args);
|
|
52
55
|
res.json({ success: true });
|
|
53
56
|
});
|
|
54
57
|
// 撤回 offer(status=warehouse + 释放 stake;不真删 product)
|
|
55
|
-
app.delete('/api/offers/:id', (req, res) => {
|
|
58
|
+
app.delete('/api/offers/:id', async (req, res) => {
|
|
56
59
|
const user = auth(req, res);
|
|
57
60
|
if (!user)
|
|
58
61
|
return;
|
|
59
|
-
const offer =
|
|
62
|
+
const offer = await dbOne("SELECT * FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
|
|
60
63
|
if (!offer)
|
|
61
64
|
return void res.status(404).json({ error: 'offer 不存在' });
|
|
62
65
|
if (offer.seller_id !== user.id)
|
|
63
66
|
return void res.status(403).json({ error: '仅卖家本人可撤回' });
|
|
64
|
-
const pending =
|
|
67
|
+
const pending = (await dbOne(`SELECT COUNT(1) as n FROM orders WHERE product_id = ? AND status NOT IN ('completed','cancelled','refunded','expired')`, [req.params.id]));
|
|
65
68
|
if (pending.n > 0)
|
|
66
69
|
return void res.json({ error: `该 offer 有 ${pending.n} 个进行中订单,暂无法撤回` });
|
|
67
70
|
const stake = Number(offer.listing_stake_locked) || 0;
|
|
68
71
|
const tx = db.transaction(() => {
|
|
69
|
-
|
|
72
|
+
// await-gap P1(proactive sweep,与 #239 系列同类):offer 行先 await 读出 stake,再进同步 tx 释放。
|
|
73
|
+
// 并发双撤回会都读到同一 listing_stake_locked 并各退一次 → 双倍退质押(印钱)。CAS 抢占该 offer
|
|
74
|
+
// (仍未撤回 + stake 仍等于读到的值),changes!==1 即并发已撤回 → 抛回滚,先于任何钱写。
|
|
75
|
+
const flip = db.prepare(`UPDATE products SET status = 'warehouse', listing_stake_locked = 0, updated_at = datetime('now') WHERE id = ? AND status != 'warehouse' AND listing_stake_locked = ?`).run(req.params.id, stake);
|
|
76
|
+
if (flip.changes !== 1)
|
|
77
|
+
throw new Error('OFFER_ALREADY_WITHDRAWN');
|
|
70
78
|
if (stake > 0) {
|
|
71
|
-
|
|
79
|
+
// Codex #254 follow-up P2:钱包释放带 staked>=stake 守卫 + changes 校验。若 wallet 行缺失或
|
|
80
|
+
// staked < listing_stake_locked(历史漂移/并发异常/前序 bug),不能在已清零 products.stake 的同时
|
|
81
|
+
// 让 staked 变负或丢质押 —— changes!==1 即抛回滚整笔(products 不清零、listings 不递减、钱包不动)。
|
|
82
|
+
const rel = db.prepare(`UPDATE wallets SET balance = balance + ?, staked = staked - ? WHERE user_id = ? AND staked >= ?`).run(stake, stake, user.id, stake);
|
|
83
|
+
if (rel.changes !== 1)
|
|
84
|
+
throw new Error('OFFER_STAKE_INVARIANT_VIOLATION');
|
|
72
85
|
}
|
|
73
86
|
db.prepare(`UPDATE listings SET total_offers = MAX(0, total_offers - 1) WHERE id = ?`).run(String(offer.listing_id));
|
|
74
87
|
});
|
|
@@ -76,21 +89,26 @@ export function registerOffersRoutes(app, deps) {
|
|
|
76
89
|
tx();
|
|
77
90
|
}
|
|
78
91
|
catch (e) {
|
|
79
|
-
|
|
92
|
+
const m = e.message;
|
|
93
|
+
if (m === 'OFFER_ALREADY_WITHDRAWN')
|
|
94
|
+
return void res.status(409).json({ error: '该 offer 已撤回(请刷新)', error_code: 'OFFER_ALREADY_WITHDRAWN' });
|
|
95
|
+
if (m === 'OFFER_STAKE_INVARIANT_VIOLATION')
|
|
96
|
+
return void res.status(500).json({ error: '质押释放校验失败(资金状态异常,已回滚未做任何变更,请联系支持)', error_code: 'OFFER_STAKE_INVARIANT_VIOLATION' });
|
|
97
|
+
return void res.status(500).json({ error: String(m) });
|
|
80
98
|
}
|
|
81
99
|
res.json({ success: true, stake_released: stake });
|
|
82
100
|
});
|
|
83
101
|
// 刷新 freshness(卖家点 "现货确认")
|
|
84
|
-
app.post('/api/offers/:id/refresh', (req, res) => {
|
|
102
|
+
app.post('/api/offers/:id/refresh', async (req, res) => {
|
|
85
103
|
const user = auth(req, res);
|
|
86
104
|
if (!user)
|
|
87
105
|
return;
|
|
88
|
-
const offer =
|
|
106
|
+
const offer = await dbOne("SELECT seller_id FROM products WHERE id = ? AND listing_id IS NOT NULL", [req.params.id]);
|
|
89
107
|
if (!offer)
|
|
90
108
|
return void res.status(404).json({ error: 'offer 不存在' });
|
|
91
109
|
if (offer.seller_id !== user.id)
|
|
92
110
|
return void res.status(403).json({ error: '仅卖家本人可刷新' });
|
|
93
|
-
|
|
111
|
+
await dbRun(`UPDATE products SET freshness_ts = datetime('now') WHERE id = ?`, [req.params.id]);
|
|
94
112
|
res.json({ success: true });
|
|
95
113
|
});
|
|
96
114
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js';
|
|
1
2
|
export function registerOrdersActionRoutes(app, deps) {
|
|
2
3
|
const { db, auth, isTrustedRole, generateId, transition, notifyTransition, settleOrder, settleFault, detectFraud, createDispute, checkTimeouts, recordViolationReputation, broadcastSystemEvent } = deps;
|
|
3
4
|
// RFC-007 stage 2:卖家主动拒单 reason_code 白名单。
|
|
@@ -11,7 +12,7 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
11
12
|
// 客观-声称理由:链下事实(外部已售/损毁),协议无确定性信号可自动核验 → 临时判责 + 举证窗口(stage 5 仲裁)。
|
|
12
13
|
const OBJECTIVE_DECLINE_REASONS = new Set(['stock_consumed_concurrent', 'stale_price_snapshot', 'force_majeure']);
|
|
13
14
|
// C-4: 卖家批量发货
|
|
14
|
-
app.post('/api/orders/batch-ship', (req, res) => {
|
|
15
|
+
app.post('/api/orders/batch-ship', async (req, res) => {
|
|
15
16
|
const user = auth(req, res);
|
|
16
17
|
if (!user)
|
|
17
18
|
return;
|
|
@@ -22,7 +23,8 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
22
23
|
return void res.status(400).json({ error: '单次最多 100 单' });
|
|
23
24
|
if (!logistics_company_id)
|
|
24
25
|
return void res.status(400).json({ error: 'logistics_company_id 必填' });
|
|
25
|
-
|
|
26
|
+
// RFC-016: 纯校验读 → 异步 seam(物流公司是否存在);循环内的逐单 read+write 仍同步(Phase 3 随订单事务迁)
|
|
27
|
+
const lc = await dbOne("SELECT id FROM users WHERE id = ? AND role = 'logistics'", [logistics_company_id]);
|
|
26
28
|
if (!lc)
|
|
27
29
|
return void res.status(400).json({ error: '物流公司不存在' });
|
|
28
30
|
const results = [];
|
|
@@ -69,11 +71,12 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
69
71
|
res.json({ success: true, shipped, skipped: results.filter(r => r.status === 'skipped').length, results });
|
|
70
72
|
});
|
|
71
73
|
// 买家确认面交完成 → 直接 completed + settleOrder
|
|
72
|
-
app.post('/api/orders/:id/confirm-in-person', (req, res) => {
|
|
74
|
+
app.post('/api/orders/:id/confirm-in-person', async (req, res) => {
|
|
73
75
|
const user = auth(req, res);
|
|
74
76
|
if (!user)
|
|
75
77
|
return;
|
|
76
|
-
|
|
78
|
+
// RFC-016: 校验读 → 异步 seam;下方 completed+history 写仍是同步 db.transaction(Phase 3 迁 pg 事务)
|
|
79
|
+
const order = await dbOne('SELECT * FROM orders WHERE id = ?', [req.params.id]);
|
|
77
80
|
if (!order)
|
|
78
81
|
return void res.status(404).json({ error: '订单不存在' });
|
|
79
82
|
if (order.fulfillment_mode !== 'in_person')
|
|
@@ -105,7 +108,7 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
105
108
|
res.json({ success: true });
|
|
106
109
|
});
|
|
107
110
|
// 通用状态机 action — accept/ship/pickup/transit/deliver/confirm/dispute
|
|
108
|
-
app.post('/api/orders/:id/action', (req, res) => {
|
|
111
|
+
app.post('/api/orders/:id/action', async (req, res) => {
|
|
109
112
|
const user = auth(req, res);
|
|
110
113
|
if (!user)
|
|
111
114
|
return;
|
|
@@ -113,7 +116,8 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
113
116
|
if (isTrustedRole(user))
|
|
114
117
|
return void res.status(403).json({ error: '受信角色不可参与订单流转', error_code: 'TRUSTED_ROLE_NO_TRADE' });
|
|
115
118
|
const { action, notes = '', evidence_description = '', logistics_company_id = '' } = req.body;
|
|
116
|
-
|
|
119
|
+
// RFC-016: 顶层校验读 → 异步 seam;state-machine / settle / decline 写序列仍同步(Phase 3 迁 pg 行锁+事务)
|
|
120
|
+
const order = await dbOne('SELECT * FROM orders WHERE id = ?', [req.params.id]);
|
|
117
121
|
if (!order)
|
|
118
122
|
return void res.status(404).json({ error: '订单不存在' });
|
|
119
123
|
// P0: 路由层 ownership 校验(engine 层只看 role,必须补 ownership)
|
|
@@ -190,16 +194,26 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
190
194
|
});
|
|
191
195
|
}
|
|
192
196
|
// 主观(或窗口=0):立即违约结算(退款买家 + 按 stake_backing 罚没,守恒,绝不印钱)
|
|
197
|
+
// Codex #119 P1:这是资金结算路径,settleFault / completed transition 失败【绝不能】吞掉后仍报 success。
|
|
198
|
+
// 订单此刻已在 fault_seller(上面 transition 已提交)。只有结算 + 推进 completed 都成功才返回 success;
|
|
199
|
+
// 任一失败 → 返回 500 DECLINE_SETTLEMENT_FAILED(订单停在 fault_seller,可重试/人工/cron 终结),不谎称已退款。
|
|
193
200
|
const sysUser = db.prepare("SELECT id FROM users WHERE id = 'sys_protocol'").get();
|
|
194
201
|
try {
|
|
202
|
+
if (!sysUser)
|
|
203
|
+
throw new Error('sys_protocol user missing — cannot finalize decline settlement');
|
|
195
204
|
settleFault(db, req.params.id, 'fault_seller');
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
const rc = transition(db, req.params.id, 'completed', sysUser.id, [], '主动拒单:系统执行违约结算');
|
|
206
|
+
if (!rc?.success)
|
|
207
|
+
throw new Error(`fault_seller→completed transition failed: ${rc?.error || 'unknown'}`);
|
|
208
|
+
notifyTransition(db, req.params.id, 'fault_seller', 'completed');
|
|
200
209
|
}
|
|
201
210
|
catch (e) {
|
|
202
211
|
console.error('[decline settleFault]', e);
|
|
212
|
+
return void res.status(500).json({
|
|
213
|
+
error: '违约结算未完成,订单仍停在 fault_seller,请稍后重试或联系支持(买家尚未退款)',
|
|
214
|
+
error_code: 'DECLINE_SETTLEMENT_FAILED',
|
|
215
|
+
outcome: 'fault_seller',
|
|
216
|
+
});
|
|
203
217
|
}
|
|
204
218
|
return void res.json({
|
|
205
219
|
success: true, outcome: 'fault_seller', decline_reason_code: reasonCode,
|
|
@@ -278,7 +292,8 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
278
292
|
// QA 轮 9.4-retry-v3 P1:post-hoc build breakdown 从 DB,让 agent 看清每分钱去哪
|
|
279
293
|
try {
|
|
280
294
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
281
|
-
|
|
295
|
+
// RFC-016: settleOrder 已完成,以下纯只读 breakdown 查询 → 异步 seam(无写,无原子性要求)
|
|
296
|
+
const ord = await dbOne("SELECT id, total_amount, source, fulfillment_mode, snapshot_commission_rate, l1_uid, l2_uid, l3_uid, logistics_id, seller_id FROM orders WHERE id = ?", [req.params.id]);
|
|
282
297
|
if (ord) {
|
|
283
298
|
const total = Number(ord.total_amount);
|
|
284
299
|
const isSecondhand = ord.source === 'secondhand';
|
|
@@ -289,7 +304,7 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
289
304
|
const logisticsActual = ord.logistics_id ? logisticsFee : 0;
|
|
290
305
|
const commissionRate = Number(ord.snapshot_commission_rate ?? 0.10);
|
|
291
306
|
const commissionPool = round2(total * commissionRate);
|
|
292
|
-
const commRecs =
|
|
307
|
+
const commRecs = await dbAll("SELECT level, amount, beneficiary_id FROM commission_records WHERE order_id = ?", [req.params.id]);
|
|
293
308
|
const commByLevel = {
|
|
294
309
|
1: { amount: 0, to: null }, 2: { amount: 0, to: null }, 3: { amount: 0, to: null },
|
|
295
310
|
};
|
|
@@ -301,9 +316,9 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
301
316
|
const commissionRedirected = round2(commissionPool - commissionDistributed);
|
|
302
317
|
// QA 轮 14.b P2:redirected_total 拆 chain_gap(→charity) vs region_cap(→global_fund)
|
|
303
318
|
// 之前单一数字让 agent 无法分辨钱去哪(global region L2/L3 进 global_fund,不是 charity)
|
|
304
|
-
const charityRow =
|
|
319
|
+
const charityRow = (await dbOne("SELECT COALESCE(SUM(amount),0) AS s FROM charity_fund_txns WHERE related_order_id = ?", [req.params.id]));
|
|
305
320
|
const redirectedToCharity = round2(Number(charityRow.s));
|
|
306
|
-
const fundDepRow =
|
|
321
|
+
const fundDepRow = (await dbOne("SELECT COALESCE(SUM(amount_l3),0) AS s FROM fund_deposits WHERE order_id = ?", [req.params.id]));
|
|
307
322
|
const redirectedToGlobalFund = round2(Number(fundDepRow.s));
|
|
308
323
|
// QA 轮 9.5 P2:payouts 表只 MCP legacy 写,PWA settleOrder 直更 wallet.balance 不写 payouts
|
|
309
324
|
// 改用公式推算 sellerAmount(跟 PWA settleOrder 内部计算一致),更可靠
|
|
@@ -347,11 +362,12 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
347
362
|
});
|
|
348
363
|
});
|
|
349
364
|
// 手动触发超时判责(当事人)
|
|
350
|
-
app.post('/api/orders/:id/force-timeout-check', (req, res) => {
|
|
365
|
+
app.post('/api/orders/:id/force-timeout-check', async (req, res) => {
|
|
351
366
|
const user = auth(req, res);
|
|
352
367
|
if (!user)
|
|
353
368
|
return;
|
|
354
|
-
|
|
369
|
+
// RFC-016: 当事人校验读 → 异步 seam;checkTimeouts(db) 自身仍是同步判责引擎(Phase 3 内部迁)
|
|
370
|
+
const order = await dbOne('SELECT buyer_id, seller_id, logistics_id, status FROM orders WHERE id = ?', [req.params.id]);
|
|
355
371
|
if (!order)
|
|
356
372
|
return void res.status(404).json({ error: '订单不存在' });
|
|
357
373
|
const uid = user.id;
|
|
@@ -360,7 +376,7 @@ export function registerOrdersActionRoutes(app, deps) {
|
|
|
360
376
|
}
|
|
361
377
|
const beforeStatus = order.status;
|
|
362
378
|
const r = checkTimeouts(db);
|
|
363
|
-
const after =
|
|
379
|
+
const after = (await dbOne('SELECT status FROM orders WHERE id = ?', [req.params.id]));
|
|
364
380
|
const touched = r.details.find((d) => d.orderId === req.params.id) || null;
|
|
365
381
|
if (touched) {
|
|
366
382
|
const faultMatch = touched.action.match(/→ (fault_\w+)/);
|