@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,21 +1,23 @@
|
|
|
1
|
+
import { dbOne, dbAll } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
1
2
|
export function registerBuyerFeedsRoutes(app, deps) {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
// db 已走 RFC-016 异步 seam(dbOne/dbAll),不再直接用 deps.db
|
|
4
|
+
const { auth, isTrustedRole, errorRes, getNearbyCellPrecision, getProtocolParam } = deps;
|
|
5
|
+
app.get('/api/recommendations/me', async (req, res) => {
|
|
4
6
|
const user = auth(req, res);
|
|
5
7
|
if (!user)
|
|
6
8
|
return;
|
|
7
9
|
if (isTrustedRole(user))
|
|
8
10
|
return void errorRes(res, 403, 'TRUSTED_ROLE_NO_TRADE', '受信角色无购物功能');
|
|
9
11
|
const limit = Math.min(30, Math.max(5, Number(req.query.limit) || 20));
|
|
10
|
-
const wishlistRows =
|
|
12
|
+
const wishlistRows = await dbAll(`
|
|
11
13
|
SELECT w.product_id, p.category, p.seller_id FROM user_wishlist w
|
|
12
14
|
JOIN products p ON p.id = w.product_id
|
|
13
15
|
WHERE w.user_id = ? AND p.status = 'active' LIMIT 50
|
|
14
|
-
|
|
15
|
-
const purchasedRows =
|
|
16
|
+
`, [user.id]);
|
|
17
|
+
const purchasedRows = await dbAll(`
|
|
16
18
|
SELECT DISTINCT product_id, seller_id FROM orders WHERE buyer_id = ? AND status = 'completed' LIMIT 200
|
|
17
|
-
|
|
18
|
-
const followedRows =
|
|
19
|
+
`, [user.id]);
|
|
20
|
+
const followedRows = await dbAll(`SELECT followee_id FROM follows WHERE follower_id = ?`, [user.id]);
|
|
19
21
|
const wishCats = new Set(wishlistRows.map(r => r.category).filter(Boolean));
|
|
20
22
|
const knownProductIds = new Set([...wishlistRows.map(r => r.product_id), ...purchasedRows.map(r => r.product_id)]);
|
|
21
23
|
const knownSellerIds = new Set([...wishlistRows.map(r => r.seller_id), ...purchasedRows.map(r => r.seller_id)]);
|
|
@@ -31,40 +33,40 @@ export function registerBuyerFeedsRoutes(app, deps) {
|
|
|
31
33
|
let followedProducts = [];
|
|
32
34
|
if (followedSellerIds.length > 0) {
|
|
33
35
|
const ph = followedSellerIds.map(() => '?').join(',');
|
|
34
|
-
followedProducts =
|
|
36
|
+
followedProducts = await dbAll(`
|
|
35
37
|
SELECT ${baseCols}
|
|
36
38
|
FROM products p JOIN users u ON u.id = p.seller_id
|
|
37
39
|
WHERE p.seller_id IN (${ph}) AND p.status = 'active' AND p.stock > 0 ${exclSql}
|
|
38
40
|
ORDER BY p.created_at DESC LIMIT 10
|
|
39
|
-
|
|
41
|
+
`, [...followedSellerIds, ...exclArgs]);
|
|
40
42
|
}
|
|
41
43
|
let categoryProducts = [];
|
|
42
44
|
if (wishCats.size > 0) {
|
|
43
45
|
const ph = [...wishCats].map(() => '?').join(',');
|
|
44
|
-
categoryProducts =
|
|
46
|
+
categoryProducts = await dbAll(`
|
|
45
47
|
SELECT ${baseCols}
|
|
46
48
|
FROM products p JOIN users u ON u.id = p.seller_id
|
|
47
49
|
WHERE p.category IN (${ph}) AND p.status = 'active' AND p.stock > 0 ${exclSql}
|
|
48
50
|
ORDER BY sales_count DESC LIMIT 10
|
|
49
|
-
|
|
51
|
+
`, [...wishCats, ...exclArgs]);
|
|
50
52
|
}
|
|
51
53
|
let pastSellerProducts = [];
|
|
52
54
|
const pastSellers = [...knownSellerIds].filter(s => !followedSellerIds.includes(s));
|
|
53
55
|
if (pastSellers.length > 0) {
|
|
54
56
|
const ph = pastSellers.map(() => '?').join(',');
|
|
55
|
-
pastSellerProducts =
|
|
57
|
+
pastSellerProducts = await dbAll(`
|
|
56
58
|
SELECT ${baseCols}
|
|
57
59
|
FROM products p JOIN users u ON u.id = p.seller_id
|
|
58
60
|
WHERE p.seller_id IN (${ph}) AND p.status = 'active' AND p.stock > 0 ${exclSql}
|
|
59
61
|
ORDER BY p.created_at DESC LIMIT 10
|
|
60
|
-
|
|
62
|
+
`, [...pastSellers, ...exclArgs]);
|
|
61
63
|
}
|
|
62
|
-
const fallback =
|
|
64
|
+
const fallback = await dbAll(`
|
|
63
65
|
SELECT ${baseCols}
|
|
64
66
|
FROM products p JOIN users u ON u.id = p.seller_id
|
|
65
67
|
WHERE p.status = 'active' AND p.stock > 0 ${exclSql}
|
|
66
68
|
ORDER BY sales_count DESC, p.created_at DESC LIMIT 10
|
|
67
|
-
|
|
69
|
+
`, exclArgs);
|
|
68
70
|
const seen = new Set();
|
|
69
71
|
const labeled = (bucket, arr) => arr.filter(it => {
|
|
70
72
|
const id = String(it.id);
|
|
@@ -88,7 +90,7 @@ export function registerBuyerFeedsRoutes(app, deps) {
|
|
|
88
90
|
},
|
|
89
91
|
});
|
|
90
92
|
});
|
|
91
|
-
app.get('/api/feed', (req, res) => {
|
|
93
|
+
app.get('/api/feed', async (req, res) => {
|
|
92
94
|
const user = auth(req, res);
|
|
93
95
|
if (!user)
|
|
94
96
|
return;
|
|
@@ -137,13 +139,13 @@ export function registerBuyerFeedsRoutes(app, deps) {
|
|
|
137
139
|
WHERE ts IS NOT NULL
|
|
138
140
|
ORDER BY ts DESC LIMIT 50
|
|
139
141
|
`;
|
|
140
|
-
const events =
|
|
142
|
+
const events = await dbAll(sql, params);
|
|
141
143
|
res.json({ events, scope });
|
|
142
144
|
});
|
|
143
145
|
// 雷达扫描 MVP (2026-05-29):scope 范围档 + window 时间窗,k≥3 守护贯穿
|
|
144
146
|
// scope: cell(本格) / neighbors(周边 3×3) / region(同城) / global(全网)
|
|
145
147
|
// window: 24h / 7d / 30d
|
|
146
|
-
app.get('/api/nearby', (req, res) => {
|
|
148
|
+
app.get('/api/nearby', async (req, res) => {
|
|
147
149
|
const user = auth(req, res);
|
|
148
150
|
if (!user)
|
|
149
151
|
return;
|
|
@@ -153,7 +155,7 @@ export function registerBuyerFeedsRoutes(app, deps) {
|
|
|
153
155
|
const days = windowKey === '24h' ? 1 : windowKey === '30d' ? 30 : 7;
|
|
154
156
|
const { precision_deg, approx_km } = getNearbyCellPrecision();
|
|
155
157
|
const K = getProtocolParam('nearby_k_anonymity', 3);
|
|
156
|
-
const u =
|
|
158
|
+
const u = (await dbOne("SELECT geo_lat, geo_lng, geo_updated_at, region FROM users WHERE id = ?", [user.id]));
|
|
157
159
|
const needsGeo = scope === 'cell' || scope === 'neighbors';
|
|
158
160
|
if (needsGeo && (u?.geo_lat == null || u?.geo_lng == null)) {
|
|
159
161
|
// 本格/周边需定位;同城/全网不需 → 前端可引导切到更大范围
|
|
@@ -186,24 +188,24 @@ export function registerBuyerFeedsRoutes(app, deps) {
|
|
|
186
188
|
scopeLabel = '全网';
|
|
187
189
|
}
|
|
188
190
|
const dayClause = `o.updated_at > datetime('now', '-${days} day')`;
|
|
189
|
-
const totals =
|
|
191
|
+
const totals = (await dbOne(`
|
|
190
192
|
SELECT COUNT(DISTINCT o.buyer_id) as au, COUNT(*) as orders
|
|
191
193
|
FROM orders o JOIN users u ON u.id = o.buyer_id
|
|
192
194
|
WHERE ${where} AND o.status = 'completed' AND ${dayClause}
|
|
193
|
-
|
|
195
|
+
`, args));
|
|
194
196
|
const sufficient = Number(totals.au) >= K;
|
|
195
|
-
const topProducts = sufficient ?
|
|
197
|
+
const topProducts = sufficient ? await dbAll(`
|
|
196
198
|
SELECT p.id, p.title, p.price, p.category, p.images, COUNT(DISTINCT o.buyer_id) as buyers
|
|
197
199
|
FROM orders o JOIN users u ON u.id = o.buyer_id JOIN products p ON p.id = o.product_id
|
|
198
200
|
WHERE ${where} AND o.status = 'completed' AND ${dayClause}
|
|
199
201
|
GROUP BY p.id HAVING buyers >= ? ORDER BY buyers DESC LIMIT 10
|
|
200
|
-
|
|
201
|
-
const topCategories = sufficient ?
|
|
202
|
+
`, [...args, K]) : [];
|
|
203
|
+
const topCategories = sufficient ? await dbAll(`
|
|
202
204
|
SELECT p.category, COUNT(*) as orders, COUNT(DISTINCT o.buyer_id) as buyers
|
|
203
205
|
FROM orders o JOIN users u ON u.id = o.buyer_id JOIN products p ON p.id = o.product_id
|
|
204
206
|
WHERE ${where} AND o.status = 'completed' AND ${dayClause} AND p.category IS NOT NULL
|
|
205
207
|
GROUP BY p.category HAVING buyers >= ? ORDER BY orders DESC LIMIT 6
|
|
206
|
-
|
|
208
|
+
`, [...args, K]) : [];
|
|
207
209
|
const staleDays = u?.geo_updated_at
|
|
208
210
|
? Math.floor((Date.now() - new Date(u.geo_updated_at.replace(' ', 'T') + 'Z').getTime()) / 86400_000)
|
|
209
211
|
: null;
|
package/dist/pwa/routes/cart.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { transition } from '../../layer0-foundation/L0-2-state-machine/engine.js';
|
|
2
2
|
import { notifyTransition } from '../../layer2-business/L2-6-notifications/notification-engine.js';
|
|
3
|
+
import { dbOne, dbAll, dbRun } from '../../layer0-foundation/L0-1-database/db.js'; // RFC-016 异步 DB seam
|
|
3
4
|
export function registerCartRoutes(app, deps) {
|
|
4
5
|
const { db, generateId, auth, isTrustedRole, errorRes, broadcastSystemEvent, checkStockAndMaybeDelist, addHours } = deps;
|
|
5
|
-
app.get('/api/cart', (req, res) => {
|
|
6
|
+
app.get('/api/cart', async (req, res) => {
|
|
6
7
|
const user = auth(req, res);
|
|
7
8
|
if (!user)
|
|
8
9
|
return;
|
|
9
|
-
const items =
|
|
10
|
+
const items = await dbAll(`
|
|
10
11
|
SELECT c.product_id, c.qty, c.added_at,
|
|
11
12
|
p.title, p.price, p.category, p.commission_rate, p.stock, p.status as product_status,
|
|
12
13
|
u.name as seller_name
|
|
@@ -15,10 +16,10 @@ export function registerCartRoutes(app, deps) {
|
|
|
15
16
|
JOIN users u ON u.id = p.seller_id
|
|
16
17
|
WHERE c.user_id = ?
|
|
17
18
|
ORDER BY c.added_at DESC
|
|
18
|
-
|
|
19
|
+
`, [user.id]);
|
|
19
20
|
res.json({ items });
|
|
20
21
|
});
|
|
21
|
-
app.post('/api/cart', (req, res) => {
|
|
22
|
+
app.post('/api/cart', async (req, res) => {
|
|
22
23
|
const user = auth(req, res);
|
|
23
24
|
if (!user)
|
|
24
25
|
return;
|
|
@@ -26,29 +27,29 @@ export function registerCartRoutes(app, deps) {
|
|
|
26
27
|
const q = Math.max(1, Math.min(99, Number(qty) || 1));
|
|
27
28
|
if (!product_id)
|
|
28
29
|
return void res.json({ error: 'product_id 必填' });
|
|
29
|
-
const product =
|
|
30
|
+
const product = await dbOne("SELECT id, status FROM products WHERE id = ?", [product_id]);
|
|
30
31
|
if (!product)
|
|
31
32
|
return void res.json({ error: '商品不存在' });
|
|
32
33
|
if (product.status !== 'active')
|
|
33
34
|
return void res.json({ error: '商品已下架' });
|
|
34
|
-
|
|
35
|
+
await dbRun(`
|
|
35
36
|
INSERT INTO cart_items (user_id, product_id, qty) VALUES (?, ?, ?)
|
|
36
37
|
ON CONFLICT(user_id, product_id) DO UPDATE SET qty = MIN(99, cart_items.qty + ?)
|
|
37
|
-
|
|
38
|
+
`, [user.id, product_id, q, q]);
|
|
38
39
|
res.json({ ok: true });
|
|
39
40
|
});
|
|
40
|
-
app.patch('/api/cart/:product_id', (req, res) => {
|
|
41
|
+
app.patch('/api/cart/:product_id', async (req, res) => {
|
|
41
42
|
const user = auth(req, res);
|
|
42
43
|
if (!user)
|
|
43
44
|
return;
|
|
44
45
|
const q = Math.max(1, Math.min(99, Number(req.body.qty) || 1));
|
|
45
|
-
const r =
|
|
46
|
+
const r = await dbRun("UPDATE cart_items SET qty = ? WHERE user_id = ? AND product_id = ?", [q, user.id, req.params.product_id]);
|
|
46
47
|
if (r.changes === 0)
|
|
47
48
|
return void res.json({ error: '购物车中没有此商品' });
|
|
48
49
|
res.json({ ok: true, qty: q });
|
|
49
50
|
});
|
|
50
51
|
// C-1: 购物车批量下单(按 seller 自动分订单)
|
|
51
|
-
app.post('/api/cart/checkout', (req, res) => {
|
|
52
|
+
app.post('/api/cart/checkout', async (req, res) => {
|
|
52
53
|
const user = auth(req, res);
|
|
53
54
|
if (!user)
|
|
54
55
|
return;
|
|
@@ -59,11 +60,11 @@ export function registerCartRoutes(app, deps) {
|
|
|
59
60
|
const { shipping_address, notes } = req.body || {};
|
|
60
61
|
if (!shipping_address)
|
|
61
62
|
return void res.status(400).json({ error: '请填写收货地址' });
|
|
62
|
-
const items =
|
|
63
|
+
const items = await dbAll(`
|
|
63
64
|
SELECT c.product_id, c.qty, p.title, p.price, p.stock, p.seller_id, p.has_variants, p.status
|
|
64
65
|
FROM cart_items c JOIN products p ON p.id = c.product_id
|
|
65
66
|
WHERE c.user_id = ?
|
|
66
|
-
|
|
67
|
+
`, [user.id]);
|
|
67
68
|
if (items.length === 0)
|
|
68
69
|
return void res.status(400).json({ error: '购物车为空' });
|
|
69
70
|
const skipped = [];
|
|
@@ -93,7 +94,7 @@ export function registerCartRoutes(app, deps) {
|
|
|
93
94
|
if (ok.length === 0) {
|
|
94
95
|
return void res.status(400).json({ error: '购物车中无可下单商品', skipped });
|
|
95
96
|
}
|
|
96
|
-
const wallet =
|
|
97
|
+
const wallet = await dbOne('SELECT balance FROM wallets WHERE user_id = ?', [user.id]);
|
|
97
98
|
if (!wallet)
|
|
98
99
|
return void res.status(500).json({ error: '钱包记录缺失' });
|
|
99
100
|
if (wallet.balance < totalNeed)
|
|
@@ -145,11 +146,11 @@ export function registerCartRoutes(app, deps) {
|
|
|
145
146
|
total_paid: created.reduce((s, c) => s + c.total, 0),
|
|
146
147
|
});
|
|
147
148
|
});
|
|
148
|
-
app.delete('/api/cart/:product_id', (req, res) => {
|
|
149
|
+
app.delete('/api/cart/:product_id', async (req, res) => {
|
|
149
150
|
const user = auth(req, res);
|
|
150
151
|
if (!user)
|
|
151
152
|
return;
|
|
152
|
-
|
|
153
|
+
await dbRun("DELETE FROM cart_items WHERE user_id = ? AND product_id = ?", [user.id, req.params.product_id]);
|
|
153
154
|
res.json({ ok: true });
|
|
154
155
|
});
|
|
155
156
|
}
|