@seasonkoh/webaz 0.1.7 → 0.1.9

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.
Files changed (153) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +156 -20
  3. package/dist/layer0-foundation/L0-1-database/schema.js +5 -4
  4. package/dist/layer0-foundation/L0-2-state-machine/engine.js +228 -7
  5. package/dist/layer0-foundation/L0-2-state-machine/order-chain.js +156 -0
  6. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +53 -12
  7. package/dist/layer0-foundation/L0-5-manifest/manifest.js +14 -1
  8. package/dist/layer1-agent/L1-1-mcp-server/auth.js +1 -1
  9. package/dist/layer1-agent/L1-1-mcp-server/server.js +3691 -714
  10. package/dist/layer1-agent/L1-2-external-anchor/anchor-engine.js +324 -0
  11. package/dist/layer1-agent/L1-2-identity/agent-passport.js +100 -0
  12. package/dist/layer2-business/L2-6-notifications/notification-engine.js +72 -5
  13. package/dist/layer2-business/L2-7-snf/snf-engine.js +287 -0
  14. package/dist/layer2-business/L2-anchor-registry/anchor-registry.js +396 -0
  15. package/dist/layer2-business/L2-notes/note-photo-storage.js +133 -0
  16. package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +6 -6
  17. package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +246 -0
  18. package/dist/layer4-economics/L4-3-reputation/reputation-engine.js +95 -1
  19. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +31 -2
  20. package/dist/layer4-economics/L4-4-skill-market/skill-listing-engine.js +358 -0
  21. package/dist/pwa/public/app.js +31947 -0
  22. package/dist/pwa/public/i18n.js +5751 -0
  23. package/dist/pwa/public/icon.svg +11 -0
  24. package/dist/pwa/public/index.html +21 -0
  25. package/dist/pwa/public/manifest.json +48 -0
  26. package/dist/pwa/public/openapi.json +5946 -0
  27. package/dist/pwa/public/style.css +535 -0
  28. package/dist/pwa/public/sw.js +63 -0
  29. package/dist/pwa/public/vendor/jsQR.js +10102 -0
  30. package/dist/pwa/public/webaz-logo.png +0 -0
  31. package/dist/pwa/routes/account-deletion.js +53 -0
  32. package/dist/pwa/routes/addresses.js +105 -0
  33. package/dist/pwa/routes/admin-admins.js +151 -0
  34. package/dist/pwa/routes/admin-analytics.js +253 -0
  35. package/dist/pwa/routes/admin-atomic.js +21 -0
  36. package/dist/pwa/routes/admin-catalog.js +64 -0
  37. package/dist/pwa/routes/admin-editor-picks.js +45 -0
  38. package/dist/pwa/routes/admin-events.js +60 -0
  39. package/dist/pwa/routes/admin-health.js +66 -0
  40. package/dist/pwa/routes/admin-moderation.js +120 -0
  41. package/dist/pwa/routes/admin-ops.js +179 -0
  42. package/dist/pwa/routes/admin-protocol-params.js +79 -0
  43. package/dist/pwa/routes/admin-reports.js +154 -0
  44. package/dist/pwa/routes/admin-tokenomics.js +113 -0
  45. package/dist/pwa/routes/admin-users-lifecycle.js +237 -0
  46. package/dist/pwa/routes/admin-users-query.js +390 -0
  47. package/dist/pwa/routes/admin-verifier-flow.js +126 -0
  48. package/dist/pwa/routes/admin-verifier-whitelist.js +111 -0
  49. package/dist/pwa/routes/admin-wallet-ops.js +66 -0
  50. package/dist/pwa/routes/agent-buy.js +215 -0
  51. package/dist/pwa/routes/agent-governance.js +341 -0
  52. package/dist/pwa/routes/agent-reputation.js +34 -0
  53. package/dist/pwa/routes/ai.js +101 -0
  54. package/dist/pwa/routes/analytics.js +272 -0
  55. package/dist/pwa/routes/anchors.js +169 -0
  56. package/dist/pwa/routes/announcements.js +110 -0
  57. package/dist/pwa/routes/arbitrator.js +117 -0
  58. package/dist/pwa/routes/auction.js +436 -0
  59. package/dist/pwa/routes/auth-login.js +40 -0
  60. package/dist/pwa/routes/auth-read.js +66 -0
  61. package/dist/pwa/routes/auth-register.js +138 -0
  62. package/dist/pwa/routes/auth-sessions.js +62 -0
  63. package/dist/pwa/routes/blocklist.js +60 -0
  64. package/dist/pwa/routes/buyer-feeds.js +224 -0
  65. package/dist/pwa/routes/cart.js +155 -0
  66. package/dist/pwa/routes/charity.js +816 -0
  67. package/dist/pwa/routes/chat.js +318 -0
  68. package/dist/pwa/routes/checkin-tasks.js +122 -0
  69. package/dist/pwa/routes/checkout-helpers.js +85 -0
  70. package/dist/pwa/routes/claim-initiators.js +88 -0
  71. package/dist/pwa/routes/claim-verify.js +615 -0
  72. package/dist/pwa/routes/claim-voting.js +114 -0
  73. package/dist/pwa/routes/claim-withdrawals.js +20 -0
  74. package/dist/pwa/routes/coupons.js +165 -0
  75. package/dist/pwa/routes/dashboards.js +99 -0
  76. package/dist/pwa/routes/dispute-cases.js +267 -0
  77. package/dist/pwa/routes/disputes-read.js +358 -0
  78. package/dist/pwa/routes/disputes-write.js +475 -0
  79. package/dist/pwa/routes/evidence.js +86 -0
  80. package/dist/pwa/routes/external-anchors.js +107 -0
  81. package/dist/pwa/routes/feedback.js +270 -0
  82. package/dist/pwa/routes/flash-sales.js +130 -0
  83. package/dist/pwa/routes/follows.js +103 -0
  84. package/dist/pwa/routes/group-buys.js +208 -0
  85. package/dist/pwa/routes/growth.js +199 -0
  86. package/dist/pwa/routes/import-product.js +153 -0
  87. package/dist/pwa/routes/kyc.js +40 -0
  88. package/dist/pwa/routes/leaderboard.js +149 -0
  89. package/dist/pwa/routes/listings.js +281 -0
  90. package/dist/pwa/routes/logistics.js +35 -0
  91. package/dist/pwa/routes/manifests.js +126 -0
  92. package/dist/pwa/routes/me-data.js +101 -0
  93. package/dist/pwa/routes/notifications.js +48 -0
  94. package/dist/pwa/routes/offers.js +96 -0
  95. package/dist/pwa/routes/orders-action.js +285 -0
  96. package/dist/pwa/routes/orders-create.js +339 -0
  97. package/dist/pwa/routes/orders-read.js +180 -0
  98. package/dist/pwa/routes/p2p-products.js +178 -0
  99. package/dist/pwa/routes/payments-governance.js +311 -0
  100. package/dist/pwa/routes/peers.js +34 -0
  101. package/dist/pwa/routes/pin-receipts.js +39 -0
  102. package/dist/pwa/routes/products-aliases.js +119 -0
  103. package/dist/pwa/routes/products-claims.js +60 -0
  104. package/dist/pwa/routes/products-create.js +206 -0
  105. package/dist/pwa/routes/products-crud.js +73 -0
  106. package/dist/pwa/routes/products-links.js +129 -0
  107. package/dist/pwa/routes/products-list.js +424 -0
  108. package/dist/pwa/routes/products-meta.js +155 -0
  109. package/dist/pwa/routes/products-update.js +125 -0
  110. package/dist/pwa/routes/profile-credentials.js +105 -0
  111. package/dist/pwa/routes/profile-identity.js +174 -0
  112. package/dist/pwa/routes/profile-location.js +35 -0
  113. package/dist/pwa/routes/profile-placement.js +70 -0
  114. package/dist/pwa/routes/profile-prefs.js +93 -0
  115. package/dist/pwa/routes/promoter.js +208 -0
  116. package/dist/pwa/routes/public-utils.js +170 -0
  117. package/dist/pwa/routes/push.js +54 -0
  118. package/dist/pwa/routes/ratings.js +220 -0
  119. package/dist/pwa/routes/recover-key.js +100 -0
  120. package/dist/pwa/routes/referral.js +58 -0
  121. package/dist/pwa/routes/reputation.js +34 -0
  122. package/dist/pwa/routes/returns.js +493 -0
  123. package/dist/pwa/routes/reviews.js +81 -0
  124. package/dist/pwa/routes/rfqs.js +443 -0
  125. package/dist/pwa/routes/search.js +172 -0
  126. package/dist/pwa/routes/secondhand.js +278 -0
  127. package/dist/pwa/routes/seller-quota.js +225 -0
  128. package/dist/pwa/routes/share-redirects.js +164 -0
  129. package/dist/pwa/routes/shareables-interactions.js +212 -0
  130. package/dist/pwa/routes/shareables.js +470 -0
  131. package/dist/pwa/routes/shops.js +98 -0
  132. package/dist/pwa/routes/signaling.js +43 -0
  133. package/dist/pwa/routes/skill-market.js +173 -0
  134. package/dist/pwa/routes/skills.js +174 -0
  135. package/dist/pwa/routes/snf.js +126 -0
  136. package/dist/pwa/routes/tags.js +47 -0
  137. package/dist/pwa/routes/trial.js +333 -0
  138. package/dist/pwa/routes/trusted-kpi.js +87 -0
  139. package/dist/pwa/routes/url-claim.js +113 -0
  140. package/dist/pwa/routes/users-public.js +317 -0
  141. package/dist/pwa/routes/variants.js +156 -0
  142. package/dist/pwa/routes/verifier-user.js +107 -0
  143. package/dist/pwa/routes/verify-tasks.js +120 -0
  144. package/dist/pwa/routes/waitlist.js +65 -0
  145. package/dist/pwa/routes/wallet-read.js +218 -0
  146. package/dist/pwa/routes/wallet-write.js +273 -0
  147. package/dist/pwa/routes/webauthn.js +188 -0
  148. package/dist/pwa/routes/webhooks.js +162 -0
  149. package/dist/pwa/routes/welcome.js +226 -0
  150. package/dist/pwa/routes/wishlist-qa.js +135 -0
  151. package/dist/pwa/security/ssrf.js +110 -0
  152. package/dist/pwa/server.js +9679 -698
  153. package/package.json +11 -4
@@ -0,0 +1,212 @@
1
+ export function registerShareablesInteractionsRoutes(app, deps) {
2
+ const { db, auth, generateId, rateLimitOk, piiSanitize, detectFraud, commentBlocklistHit, llmModerateComment, parseMentions, notifyMentions } = deps;
3
+ app.post('/api/shareables/:id/click', (req, res) => {
4
+ // 点击计数(不要求 auth — 任何人点击外链都计数)
5
+ db.prepare("UPDATE shareables SET click_count = click_count + 1 WHERE id = ? AND status = 'active'").run(req.params.id);
6
+ res.json({ ok: true });
7
+ });
8
+ // LIKE 系统:toggle 点赞(每用户对每 shareable 一票;不能给自己点)
9
+ app.post('/api/shareables/:id/like', (req, res) => {
10
+ const user = auth(req, res);
11
+ if (!user)
12
+ return;
13
+ if (!rateLimitOk(`like:${user.id}`, 60, 60_000))
14
+ return void res.status(429).json({ error: '点赞过于频繁' });
15
+ const sh = db.prepare("SELECT id, owner_id, related_product_id, status FROM shareables WHERE id = ?").get(req.params.id);
16
+ if (!sh)
17
+ return void res.status(404).json({ error: 'shareable 不存在' });
18
+ if (sh.status !== 'active')
19
+ return void res.json({ error: 'shareable 已下架' });
20
+ if (sh.owner_id === user.id)
21
+ return void res.json({ error: '不能给自己点赞' });
22
+ // P1 Sybil 软门槛:至少完成过 1 笔订单(不限购买该商品,只需活跃用户)
23
+ const completed = db.prepare("SELECT COUNT(1) as n FROM orders WHERE buyer_id = ? AND status = 'completed'").get(user.id).n;
24
+ if (completed < 1)
25
+ return void res.json({ error: '完成首笔购买后才能点赞(防止刷赞)' });
26
+ // P0 fix:SELECT existing 进 transaction
27
+ let liked = false;
28
+ db.transaction(() => {
29
+ const existing = db.prepare('SELECT id FROM shareable_likes WHERE shareable_id = ? AND user_id = ?').get(req.params.id, user.id);
30
+ if (existing) {
31
+ db.prepare('DELETE FROM shareable_likes WHERE id = ?').run(existing.id);
32
+ db.prepare('UPDATE shareables SET like_count = MAX(0, like_count - 1) WHERE id = ?').run(req.params.id);
33
+ if (sh.related_product_id)
34
+ db.prepare('UPDATE products SET total_likes = MAX(0, total_likes - 1) WHERE id = ?').run(sh.related_product_id);
35
+ liked = false;
36
+ }
37
+ else {
38
+ db.prepare('INSERT INTO shareable_likes (id, shareable_id, user_id) VALUES (?,?,?)').run(generateId('lk'), req.params.id, user.id);
39
+ db.prepare('UPDATE shareables SET like_count = like_count + 1 WHERE id = ?').run(req.params.id);
40
+ if (sh.related_product_id)
41
+ db.prepare('UPDATE products SET total_likes = total_likes + 1 WHERE id = ?').run(sh.related_product_id);
42
+ liked = true;
43
+ }
44
+ })();
45
+ const newCount = db.prepare('SELECT like_count FROM shareables WHERE id = ?').get(req.params.id).like_count;
46
+ // 通知 owner(仅新增点赞,避免取消时打扰)
47
+ if (liked) {
48
+ try {
49
+ db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, created_at)
50
+ VALUES (?,?,'shareable_like',?,?,datetime('now'))`)
51
+ .run(generateId('ntf'), sh.owner_id, `❤️ 收到点赞`, `分享 #${req.params.id.slice(-8)} 被点赞(累计 ${newCount})`);
52
+ }
53
+ catch { }
54
+ }
55
+ res.json({ liked, like_count: newCount });
56
+ });
57
+ // W6 笔记评论 — 楼中楼 1 层(root + replies)
58
+ app.get('/api/shareables/:id/comments', (req, res) => {
59
+ const sh = db.prepare(`SELECT id FROM shareables WHERE id = ?`).get(req.params.id);
60
+ if (!sh)
61
+ return void res.status(404).json({ error: 'shareable 不存在' });
62
+ const limit = Math.min(100, Math.max(10, Number(req.query.limit) || 50));
63
+ const sort = String(req.query.sort || 'newest');
64
+ const orderBy = sort === 'top' ? 'c.likes DESC, c.created_at DESC' : 'c.created_at DESC';
65
+ const roots = db.prepare(`
66
+ SELECT c.*, u.handle, u.name, u.role
67
+ FROM shareable_comments c LEFT JOIN users u ON u.id = c.commenter_id
68
+ WHERE c.shareable_id = ? AND c.parent_id IS NULL AND c.flagged = 0
69
+ ORDER BY ${orderBy} LIMIT ?
70
+ `).all(sh.id, limit);
71
+ const rootIds = roots.map(r => r.id);
72
+ const replies = rootIds.length > 0 ? db.prepare(`
73
+ SELECT c.*, u.handle, u.name, u.role
74
+ FROM shareable_comments c LEFT JOIN users u ON u.id = c.commenter_id
75
+ WHERE c.parent_id IN (${rootIds.map(() => '?').join(',')}) AND c.flagged = 0
76
+ ORDER BY c.created_at ASC
77
+ `).all(...rootIds) : [];
78
+ const replyMap = new Map();
79
+ for (const r of replies) {
80
+ const pid = String(r.parent_id);
81
+ const arr = replyMap.get(pid) || [];
82
+ arr.push(r);
83
+ replyMap.set(pid, arr);
84
+ }
85
+ const items = roots.map(r => ({ ...r, replies: replyMap.get(r.id) || [] }));
86
+ const total = db.prepare(`SELECT COUNT(*) as n FROM shareable_comments WHERE shareable_id = ? AND flagged = 0`).get(sh.id).n;
87
+ res.json({ items, total, sort });
88
+ });
89
+ app.post('/api/shareables/:id/comments', async (req, res) => {
90
+ const user = auth(req, res);
91
+ if (!user)
92
+ return;
93
+ const sh = db.prepare(`SELECT id, owner_id, status FROM shareables WHERE id = ?`).get(req.params.id);
94
+ if (!sh)
95
+ return void res.status(404).json({ error: 'shareable 不存在' });
96
+ if (sh.status !== 'active')
97
+ return void res.status(400).json({ error: 'shareable 已下架' });
98
+ const parentId = req.body?.parent_id ? String(req.body.parent_id) : null;
99
+ if (parentId) {
100
+ const parent = db.prepare(`SELECT id, parent_id FROM shareable_comments WHERE id = ? AND shareable_id = ?`).get(parentId, sh.id);
101
+ if (!parent)
102
+ return void res.status(404).json({ error: '父评论不存在' });
103
+ if (parent.parent_id)
104
+ return void res.status(400).json({ error: '只能回复顶层评论' });
105
+ }
106
+ const rawBody = String(req.body?.body || '').trim();
107
+ const minLen = parentId ? 2 : 5;
108
+ const maxLen = parentId ? 300 : 500;
109
+ if (rawBody.length < minLen)
110
+ return void res.status(400).json({ error: `内容至少 ${minLen} 字` });
111
+ if (rawBody.length > maxLen)
112
+ return void res.status(400).json({ error: `内容最多 ${maxLen} 字` });
113
+ const blocked = commentBlocklistHit(rawBody);
114
+ if (blocked)
115
+ return void res.status(400).json({ error: blocked, error_code: 'COMMENT_BLOCKED' });
116
+ const body = piiSanitize(rawBody);
117
+ const llm = await llmModerateComment(body);
118
+ if (!llm.ok)
119
+ return void res.status(400).json({ error: llm.reason || '内容不符合社区规范', error_code: 'COMMENT_MODERATED' });
120
+ // 同仲裁评论:flagged 给管理员,flag_reasons 给反诈;用 rawBody
121
+ const reasons = detectFraud(rawBody);
122
+ const cid = generateId('scom');
123
+ db.prepare(`INSERT INTO shareable_comments (id, shareable_id, commenter_id, parent_id, body, flag_reasons) VALUES (?,?,?,?,?,?)`)
124
+ .run(cid, sh.id, user.id, parentId, body, reasons.length ? JSON.stringify(reasons) : null);
125
+ // 通知作者(自己评论自己除外)+ W9 action
126
+ if (sh.owner_id !== user.id) {
127
+ try {
128
+ const actions = JSON.stringify([{ kind: 'navigate', label: '查看笔记', href: `#note/${sh.id}`, style: 'primary' }]);
129
+ db.prepare(`INSERT INTO notifications (id, user_id, type, title, body, order_id, actions) VALUES (?,?,?,?,?,?,?)`)
130
+ .run(generateId('ntf'), sh.owner_id, 'note_comment', parentId ? '💬 笔记评论新回复' : '💬 笔记新评论', body.slice(0, 80), null, actions);
131
+ }
132
+ catch (e) {
133
+ console.warn('[notif note_comment]', e.message);
134
+ }
135
+ }
136
+ // 2026-05-22 audit P1:评论 @ 提及 → notifications(含笔记 owner 避免重复)
137
+ const commentMentions = parseMentions(body).filter(m => m.user_id !== sh.owner_id);
138
+ notifyMentions(commentMentions, user.id, 'comment', sh.id, body.slice(0, 100));
139
+ res.json({ success: true, id: cid, flag_reasons: reasons, mentions: commentMentions.map(m => m.handle) });
140
+ });
141
+ // 查询单个 shareable 我是否点赞过(用于 UI 状态)
142
+ app.get('/api/shareables/:id/like-status', (req, res) => {
143
+ const user = auth(req, res);
144
+ if (!user)
145
+ return;
146
+ const row = db.prepare('SELECT id FROM shareable_likes WHERE shareable_id = ? AND user_id = ?').get(req.params.id, user.id);
147
+ const count = db.prepare('SELECT like_count FROM shareables WHERE id = ?').get(req.params.id)?.like_count ?? 0;
148
+ res.json({ liked: !!row, like_count: count });
149
+ });
150
+ // ─── 收藏 Bookmarks(小红书风格"收藏" tab)── 2026-05-22 audit ─────
151
+ // POST 切换:未收藏 → 加 / 已收藏 → 删(toggle 模式)
152
+ app.post('/api/shareables/:id/bookmark', (req, res) => {
153
+ const user = auth(req, res);
154
+ if (!user)
155
+ return;
156
+ const id = String(req.params.id);
157
+ // 确认 shareable 存在 + active
158
+ const sh = db.prepare("SELECT id FROM shareables WHERE id = ? AND status = 'active'").get(id);
159
+ if (!sh)
160
+ return void res.status(404).json({ error: 'not_found' });
161
+ const existing = db.prepare('SELECT id FROM shareable_bookmarks WHERE shareable_id = ? AND user_id = ?').get(id, user.id);
162
+ if (existing) {
163
+ db.prepare('DELETE FROM shareable_bookmarks WHERE id = ?').run(existing.id);
164
+ return void res.json({ bookmarked: false });
165
+ }
166
+ db.prepare('INSERT INTO shareable_bookmarks (id, shareable_id, user_id) VALUES (?, ?, ?)').run(generateId('bm'), id, user.id);
167
+ res.json({ bookmarked: true });
168
+ });
169
+ // 查 bookmark 状态
170
+ app.get('/api/shareables/:id/bookmark-status', (req, res) => {
171
+ const user = auth(req, res);
172
+ if (!user)
173
+ return;
174
+ const row = db.prepare('SELECT id FROM shareable_bookmarks WHERE shareable_id = ? AND user_id = ?').get(req.params.id, user.id);
175
+ res.json({ bookmarked: !!row });
176
+ });
177
+ // 我收藏过的 shareables(仅 owner 自己可见)
178
+ app.get('/api/users/:id/bookmarked-shareables', (req, res) => {
179
+ const me = auth(req, res);
180
+ if (!me)
181
+ return;
182
+ const ref = String(req.params.id || '').trim();
183
+ let ownerId = null;
184
+ if (ref === 'me' || ref === me.id)
185
+ ownerId = me.id;
186
+ if (!ownerId)
187
+ return void res.status(403).json({ error: 'only owner can view bookmarks' });
188
+ const rows = db.prepare(`
189
+ SELECT s.id, s.owner_id, s.owner_code, s.type, s.external_url, s.external_platform,
190
+ s.thumbnail_url, s.title, s.description, s.photo_hashes, s.related_product_id, s.related_anchor,
191
+ s.click_count, s.like_count, s.created_at,
192
+ p.title AS product_title,
193
+ b.created_at as bookmarked_at
194
+ FROM shareable_bookmarks b
195
+ JOIN shareables s ON s.id = b.shareable_id
196
+ LEFT JOIN products p ON p.id = s.related_product_id
197
+ WHERE b.user_id = ? AND s.status = 'active'
198
+ ORDER BY b.created_at DESC LIMIT 100
199
+ `).all(ownerId);
200
+ for (const r of rows) {
201
+ if (typeof r.photo_hashes === 'string') {
202
+ try {
203
+ r.photo_hashes = JSON.parse(r.photo_hashes);
204
+ }
205
+ catch {
206
+ r.photo_hashes = [];
207
+ }
208
+ }
209
+ }
210
+ res.json({ shareables: rows });
211
+ });
212
+ }