@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,173 @@
1
+ import { publishListing, updateListing, delistListing, resubmitListing, listMarket, getMarketDetail, getMyListings, purchaseListing, readContent, getMyLibrary, listPendingAudit, auditListing, } from '../../layer4-economics/L4-4-skill-market/skill-listing-engine.js';
2
+ export function registerSkillMarketRoutes(app, deps) {
3
+ const { db, generateId, auth, getUser, requireContentAdmin, getProtocolParam } = deps;
4
+ const feeRate = () => getProtocolParam('skill_fee_rate', 0.05);
5
+ const notify = (userId, title, body) => {
6
+ try {
7
+ db.prepare('INSERT INTO notifications (id, user_id, title, body, order_id) VALUES (?,?,?,?,?)')
8
+ .run(generateId('ntf'), userId, title, body, null);
9
+ }
10
+ catch { /* notifications best-effort */ }
11
+ };
12
+ // ─── 公开列表 ───────────────────────────────────────────────
13
+ app.get('/api/skill-market', (req, res) => {
14
+ const user = getUser(req);
15
+ res.json(listMarket(db, {
16
+ category: req.query.category,
17
+ skillKind: req.query.kind,
18
+ billingMode: req.query.billing,
19
+ query: req.query.q,
20
+ viewerId: user?.id,
21
+ limit: 30,
22
+ }));
23
+ });
24
+ // ─── 我发布的(须在 /:id 之前注册)───────────────────────────
25
+ app.get('/api/skill-market/mine', (req, res) => {
26
+ const user = auth(req, res);
27
+ if (!user)
28
+ return;
29
+ res.json(getMyListings(db, user.id));
30
+ });
31
+ // ─── 我的技能库 ─────────────────────────────────────────────
32
+ app.get('/api/skill-market/library', (req, res) => {
33
+ const user = auth(req, res);
34
+ if (!user)
35
+ return;
36
+ res.json(getMyLibrary(db, user.id));
37
+ });
38
+ // ─── 公开详情 ───────────────────────────────────────────────
39
+ app.get('/api/skill-market/:id', (req, res) => {
40
+ const user = getUser(req);
41
+ const detail = getMarketDetail(db, req.params.id, user?.id);
42
+ if (!detail)
43
+ return void res.status(404).json({ error: '技能不存在或未上架' });
44
+ res.json(detail);
45
+ });
46
+ // ─── 发布(任意登录用户)────────────────────────────────────
47
+ app.post('/api/skill-market', (req, res) => {
48
+ const user = auth(req, res);
49
+ if (!user)
50
+ return;
51
+ const b = req.body;
52
+ try {
53
+ const listing = publishListing(db, {
54
+ authorId: user.id,
55
+ title: String(b.title ?? ''),
56
+ summary: b.summary != null ? String(b.summary) : undefined,
57
+ preview: b.preview != null ? String(b.preview) : undefined,
58
+ content: String(b.content ?? ''),
59
+ category: b.category != null ? String(b.category) : undefined,
60
+ skillKind: b.skill_kind,
61
+ billingMode: b.billing_mode,
62
+ price: b.price != null ? Number(b.price) : 0,
63
+ });
64
+ res.json({ success: true, listing });
65
+ }
66
+ catch (err) {
67
+ res.status(400).json({ error: err.message });
68
+ }
69
+ });
70
+ // ─── 修改 ───────────────────────────────────────────────────
71
+ app.patch('/api/skill-market/:id', (req, res) => {
72
+ const user = auth(req, res);
73
+ if (!user)
74
+ return;
75
+ const b = req.body;
76
+ try {
77
+ const listing = updateListing(db, req.params.id, user.id, {
78
+ title: b.title != null ? String(b.title) : undefined,
79
+ summary: b.summary != null ? String(b.summary) : undefined,
80
+ preview: b.preview != null ? String(b.preview) : undefined,
81
+ content: b.content != null ? String(b.content) : undefined,
82
+ category: b.category != null ? String(b.category) : undefined,
83
+ skillKind: b.skill_kind,
84
+ billingMode: b.billing_mode,
85
+ price: b.price != null ? Number(b.price) : undefined,
86
+ });
87
+ res.json({ success: true, listing });
88
+ }
89
+ catch (err) {
90
+ res.status(400).json({ error: err.message });
91
+ }
92
+ });
93
+ // ─── 下架 ───────────────────────────────────────────────────
94
+ app.post('/api/skill-market/:id/delist', (req, res) => {
95
+ const user = auth(req, res);
96
+ if (!user)
97
+ return;
98
+ try {
99
+ delistListing(db, req.params.id, user.id);
100
+ res.json({ success: true });
101
+ }
102
+ catch (err) {
103
+ res.status(400).json({ error: err.message });
104
+ }
105
+ });
106
+ // ─── 重新提交审核 ───────────────────────────────────────────
107
+ app.post('/api/skill-market/:id/resubmit', (req, res) => {
108
+ const user = auth(req, res);
109
+ if (!user)
110
+ return;
111
+ try {
112
+ resubmitListing(db, req.params.id, user.id);
113
+ res.json({ success: true });
114
+ }
115
+ catch (err) {
116
+ res.status(400).json({ error: err.message });
117
+ }
118
+ });
119
+ // ─── 购买 / 解锁(free | one_time)──────────────────────────
120
+ app.post('/api/skill-market/:id/purchase', (req, res) => {
121
+ const user = auth(req, res);
122
+ if (!user)
123
+ return;
124
+ try {
125
+ res.json(purchaseListing(db, user.id, req.params.id, feeRate()));
126
+ }
127
+ catch (err) {
128
+ res.status(400).json({ error: err.message });
129
+ }
130
+ });
131
+ // ─── 读取正文(per_use 按次扣费)────────────────────────────
132
+ app.post('/api/skill-market/:id/read', (req, res) => {
133
+ const user = auth(req, res);
134
+ if (!user)
135
+ return;
136
+ try {
137
+ res.json(readContent(db, user.id, req.params.id, feeRate()));
138
+ }
139
+ catch (err) {
140
+ res.status(400).json({ error: err.message });
141
+ }
142
+ });
143
+ // ─── Admin:待审列表 ────────────────────────────────────────
144
+ app.get('/api/admin/skill-market/pending', (req, res) => {
145
+ const admin = requireContentAdmin(req, res);
146
+ if (!admin)
147
+ return;
148
+ res.json({ items: listPendingAudit(db) });
149
+ });
150
+ // ─── Admin:审核 ────────────────────────────────────────────
151
+ app.post('/api/admin/skill-market/:id/audit', (req, res) => {
152
+ const admin = requireContentAdmin(req, res);
153
+ if (!admin)
154
+ return;
155
+ const { decision, note } = req.body;
156
+ if (decision !== 'approve' && decision !== 'reject') {
157
+ return void res.status(400).json({ error: 'decision 必须是 approve 或 reject' });
158
+ }
159
+ try {
160
+ const listing = auditListing(db, req.params.id, admin.id, decision, note);
161
+ if (decision === 'approve') {
162
+ notify(listing.author_id, '✓ 技能审核通过', `「${listing.title}」已上架技能市场`);
163
+ }
164
+ else {
165
+ notify(listing.author_id, '✗ 技能审核未通过', `「${listing.title}」被退回:${note ?? ''}`);
166
+ }
167
+ res.json({ success: true, listing });
168
+ }
169
+ catch (err) {
170
+ res.status(400).json({ error: err.message });
171
+ }
172
+ });
173
+ }
@@ -0,0 +1,174 @@
1
+ import { publishSkill, listSkills, getMySkills, subscribeSkill, unsubscribeSkill, getMySubscriptions, } from '../../layer4-economics/L4-4-skill-market/skill-engine.js';
2
+ const SKILL_TRUST_REQ = {
3
+ price_negotiation: 'quality',
4
+ quality_guarantee: 'quality',
5
+ catalog_sync: 'trusted',
6
+ auto_accept: 'new',
7
+ instant_ship: 'new',
8
+ };
9
+ const LEVEL_ORDER = ['new', 'trusted', 'quality', 'legend'];
10
+ export function registerSkillsRoutes(app, deps) {
11
+ const { db, auth, getUser } = deps;
12
+ // 公开浏览
13
+ app.get('/api/skills', (req, res) => {
14
+ const user = getUser(req);
15
+ const skills = listSkills(db, {
16
+ skillType: req.query.type,
17
+ query: req.query.q,
18
+ subscriberId: user?.id,
19
+ limit: 30,
20
+ });
21
+ res.json(skills);
22
+ });
23
+ app.get('/api/skills/mine', (req, res) => {
24
+ const user = auth(req, res);
25
+ if (!user)
26
+ return;
27
+ res.json(getMySkills(db, user.id));
28
+ });
29
+ app.get('/api/skills/subscriptions', (req, res) => {
30
+ const user = auth(req, res);
31
+ if (!user)
32
+ return;
33
+ res.json(getMySubscriptions(db, user.id));
34
+ });
35
+ // 发布
36
+ app.post('/api/skills', (req, res) => {
37
+ const user = auth(req, res);
38
+ if (!user)
39
+ return;
40
+ if (user.role !== 'seller')
41
+ return void res.json({ error: '只有卖家才能发布 Skill' });
42
+ const { name, description, category, skill_type, config } = req.body;
43
+ if (!name || !description || !skill_type)
44
+ return void res.json({ error: '请填写 name、description、skill_type' });
45
+ // trust level 门槛
46
+ const required = SKILL_TRUST_REQ[skill_type] || 'new';
47
+ if (required !== 'new') {
48
+ const rep = db.prepare(`SELECT level FROM agent_reputation WHERE api_key = ?`).get(user.api_key);
49
+ const myLevel = rep?.level || 'new';
50
+ if (LEVEL_ORDER.indexOf(myLevel) < LEVEL_ORDER.indexOf(required)) {
51
+ return void res.status(403).json({
52
+ error: `发布 ${skill_type} 类型 Skill 需要 ${required}+ 等级(你当前 ${myLevel})`,
53
+ error_code: 'SKILL_TRUST_LEVEL_REQUIRED',
54
+ required, current: myLevel,
55
+ });
56
+ }
57
+ }
58
+ // config 边界
59
+ const cfg = (config && typeof config === 'object') ? config : {};
60
+ if (skill_type === 'price_negotiation') {
61
+ const maxDiscount = Number(cfg.max_discount_pct ?? 0);
62
+ if (!Number.isFinite(maxDiscount) || maxDiscount < 0 || maxDiscount > 0.5) {
63
+ return void res.json({ error: 'price_negotiation: max_discount_pct 必须 0-0.5(即 0%-50%)' });
64
+ }
65
+ const minQty = Number(cfg.min_quantity ?? 1);
66
+ if (!Number.isInteger(minQty) || minQty < 1 || minQty > 10000) {
67
+ return void res.json({ error: 'price_negotiation: min_quantity 必须 1-10000 整数' });
68
+ }
69
+ }
70
+ if (skill_type === 'quality_guarantee') {
71
+ const guarantee = Number(cfg.guarantee_amount ?? 0);
72
+ if (!Number.isFinite(guarantee) || guarantee < 0 || guarantee > 100000) {
73
+ return void res.json({ error: 'quality_guarantee: guarantee_amount 必须 0-100000 WAZ' });
74
+ }
75
+ const coverDays = Number(cfg.coverage_days ?? 0);
76
+ if (!Number.isInteger(coverDays) || coverDays < 0 || coverDays > 365) {
77
+ return void res.json({ error: 'quality_guarantee: coverage_days 必须 0-365 整数' });
78
+ }
79
+ }
80
+ if (skill_type === 'instant_ship') {
81
+ const shipHrs = Number(cfg.ship_within_hours ?? 24);
82
+ if (!Number.isInteger(shipHrs) || shipHrs < 1 || shipHrs > 168) {
83
+ return void res.json({ error: 'instant_ship: ship_within_hours 必须 1-168 整数(最多 7 天)' });
84
+ }
85
+ }
86
+ if (skill_type === 'auto_accept') {
87
+ const maxDaily = Number(cfg.max_daily_orders ?? 0);
88
+ if (cfg.max_daily_orders != null && (!Number.isInteger(maxDaily) || maxDaily < 1 || maxDaily > 10000)) {
89
+ return void res.json({ error: 'auto_accept: max_daily_orders 必须 1-10000 整数' });
90
+ }
91
+ }
92
+ try {
93
+ const skill = publishSkill(db, {
94
+ sellerId: user.id,
95
+ name, description, category,
96
+ skillType: skill_type,
97
+ config: cfg,
98
+ });
99
+ res.json({ success: true, skill });
100
+ }
101
+ catch (err) {
102
+ res.json({ error: err.message });
103
+ }
104
+ });
105
+ // 卖家:修改 Skill
106
+ app.patch('/api/skills/:id', (req, res) => {
107
+ const user = auth(req, res);
108
+ if (!user)
109
+ return;
110
+ const skill = db.prepare('SELECT seller_id FROM skills WHERE id = ?').get(req.params.id);
111
+ if (!skill)
112
+ return void res.status(404).json({ error: 'Skill 不存在' });
113
+ if (skill.seller_id !== user.id)
114
+ return void res.status(403).json({ error: '仅 Skill owner 可修改' });
115
+ const body = req.body;
116
+ const updates = [];
117
+ const args = [];
118
+ if (body.config !== undefined) {
119
+ updates.push('config = ?');
120
+ args.push(JSON.stringify(body.config ?? {}));
121
+ }
122
+ if (body.active !== undefined) {
123
+ updates.push('active = ?');
124
+ args.push(body.active ? 1 : 0);
125
+ }
126
+ if (body.name && typeof body.name === 'string') {
127
+ updates.push('name = ?');
128
+ args.push(body.name);
129
+ }
130
+ if (body.description && typeof body.description === 'string') {
131
+ updates.push('description = ?');
132
+ args.push(body.description);
133
+ }
134
+ if (!updates.length)
135
+ return void res.json({ error: '无任何修改' });
136
+ args.push(req.params.id);
137
+ db.prepare(`UPDATE skills SET ${updates.join(', ')} WHERE id = ?`).run(...args);
138
+ res.json({ success: true });
139
+ });
140
+ // 卖家:停用
141
+ app.post('/api/skills/:id/disable', (req, res) => {
142
+ const user = auth(req, res);
143
+ if (!user)
144
+ return;
145
+ const skill = db.prepare('SELECT seller_id FROM skills WHERE id = ?').get(req.params.id);
146
+ if (!skill)
147
+ return void res.status(404).json({ error: 'Skill 不存在' });
148
+ if (skill.seller_id !== user.id)
149
+ return void res.status(403).json({ error: '仅 Skill owner 可停用' });
150
+ db.prepare("UPDATE skills SET active = 0 WHERE id = ?").run(req.params.id);
151
+ res.json({ success: true });
152
+ });
153
+ // 订阅
154
+ app.post('/api/skills/:id/subscribe', (req, res) => {
155
+ const user = auth(req, res);
156
+ if (!user)
157
+ return;
158
+ try {
159
+ const result = subscribeSkill(db, user.id, req.params.id, req.body?.config ?? {});
160
+ res.json(result);
161
+ }
162
+ catch (err) {
163
+ res.json({ error: err.message });
164
+ }
165
+ });
166
+ // 取消订阅
167
+ app.delete('/api/skills/:id/subscribe', (req, res) => {
168
+ const user = auth(req, res);
169
+ if (!user)
170
+ return;
171
+ unsubscribeSkill(db, user.id, req.params.id);
172
+ res.json({ success: true });
173
+ });
174
+ }
@@ -0,0 +1,126 @@
1
+ import { snfSend, snfPullInbox, snfListInbox, snfAck, snfPendingCount, snfVerify, snfDesignate, snfGetDesignation, snfNack, snfListDeadLetter, snfRevive, } from '../../layer2-business/L2-7-snf/snf-engine.js';
2
+ export function registerSnfRoutes(app, deps) {
3
+ const { db, auth } = deps;
4
+ app.post('/api/snf/send', (req, res) => {
5
+ const user = auth(req, res);
6
+ if (!user)
7
+ return;
8
+ const { recipient_id, message_type, payload, related_order_id, priority } = req.body || {};
9
+ if (!recipient_id || !message_type || !payload)
10
+ return void res.status(400).json({ error: '缺少必要字段' });
11
+ if (typeof payload !== 'object')
12
+ return void res.status(400).json({ error: 'payload 必须是 object' });
13
+ try {
14
+ const r = snfSend(db, {
15
+ senderId: user.id,
16
+ recipientId: String(recipient_id),
17
+ messageType: String(message_type),
18
+ payload: payload,
19
+ relatedOrderId: related_order_id ? String(related_order_id) : null,
20
+ priority: priority === 1 ? 1 : 0,
21
+ });
22
+ res.json({ ok: true, id: r.id, signature: r.signature });
23
+ }
24
+ catch (e) {
25
+ res.status(400).json({ error: e.message });
26
+ }
27
+ });
28
+ // 只读列表(不消费)
29
+ app.get('/api/snf/inbox', (req, res) => {
30
+ const user = auth(req, res);
31
+ if (!user)
32
+ return;
33
+ const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 80));
34
+ const sinceDays = Math.min(180, Math.max(1, Number(req.query.since_days) || 30));
35
+ const msgs = snfListInbox(db, user.id, limit, sinceDays);
36
+ res.json({ items: msgs, count: msgs.length });
37
+ });
38
+ // 协议级 pull — 一次性消费,agent / 内部组件用
39
+ app.get('/api/snf/inbox/pull', (req, res) => {
40
+ const user = auth(req, res);
41
+ if (!user)
42
+ return;
43
+ const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 50));
44
+ const msgs = snfPullInbox(db, user.id, limit);
45
+ res.json({ items: msgs, count: msgs.length });
46
+ });
47
+ // Agent 处理失败 → nack 回放(超 5 次自动死信化)
48
+ app.post('/api/snf/nack', (req, res) => {
49
+ const user = auth(req, res);
50
+ if (!user)
51
+ return;
52
+ const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(String).slice(0, 100) : [];
53
+ if (ids.length === 0)
54
+ return void res.json({ error: 'ids 为空' });
55
+ const error = req.body?.error ? String(req.body.error) : undefined;
56
+ const r = snfNack(db, user.id, ids, error);
57
+ res.json({ ok: true, reopened: r.reopened, dead_lettered: r.deadLettered });
58
+ });
59
+ app.get('/api/snf/dead-letter', (req, res) => {
60
+ const user = auth(req, res);
61
+ if (!user)
62
+ return;
63
+ const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 50));
64
+ const items = snfListDeadLetter(db, user.id, limit);
65
+ res.json({ items, count: items.length });
66
+ });
67
+ app.post('/api/snf/revive/:id', (req, res) => {
68
+ const user = auth(req, res);
69
+ if (!user)
70
+ return;
71
+ const r = snfRevive(db, user.id, String(req.params.id));
72
+ if (!r.ok) {
73
+ const status = r.reason === 'not_found' ? 404 : r.reason === 'not_owner' ? 403 : 400;
74
+ return void res.status(status).json({ error: r.reason });
75
+ }
76
+ res.json({ ok: true });
77
+ });
78
+ // 显式 ack(无 ids → ack 全部未读)
79
+ app.post('/api/snf/ack', (req, res) => {
80
+ const user = auth(req, res);
81
+ if (!user)
82
+ return;
83
+ const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(String).slice(0, 200) : null;
84
+ if (ids && ids.length > 0) {
85
+ const r = snfAck(db, user.id, ids);
86
+ return void res.json({ ok: true, acked: r.acked });
87
+ }
88
+ const all = snfListInbox(db, user.id, 200, 365).filter(m => !m.delivered_at).map(m => m.id);
89
+ const r = snfAck(db, user.id, all);
90
+ res.json({ ok: true, acked: r.acked });
91
+ });
92
+ app.get('/api/snf/pending', (req, res) => {
93
+ const user = auth(req, res);
94
+ if (!user)
95
+ return;
96
+ res.json({ pending: snfPendingCount(db, user.id) });
97
+ });
98
+ // 验签(仅当事人或 arbitrator/admin)
99
+ app.get('/api/snf/:id/verify', (req, res) => {
100
+ const user = auth(req, res);
101
+ if (!user)
102
+ return;
103
+ const r = db.prepare(`SELECT sender_id, recipient_id FROM snf_messages WHERE id = ?`).get(req.params.id);
104
+ if (!r)
105
+ return void res.status(404).json({ error: '消息不存在' });
106
+ const uid = user.id;
107
+ if (uid !== r.sender_id && uid !== r.recipient_id && user.role !== 'arbitrator' && user.role !== 'admin') {
108
+ return void res.status(403).json({ error: '无权验证' });
109
+ }
110
+ res.json(snfVerify(db, req.params.id));
111
+ });
112
+ app.post('/api/snf/designate', (req, res) => {
113
+ const user = auth(req, res);
114
+ if (!user)
115
+ return;
116
+ const peers = Array.isArray(req.body?.peers) ? req.body.peers.map(String).slice(0, 5) : [];
117
+ snfDesignate(db, user.id, peers);
118
+ res.json({ ok: true, peers });
119
+ });
120
+ app.get('/api/snf/designate', (req, res) => {
121
+ const user = auth(req, res);
122
+ if (!user)
123
+ return;
124
+ res.json({ peers: snfGetDesignation(db, user.id), server_implicit: true });
125
+ });
126
+ }
@@ -0,0 +1,47 @@
1
+ export function registerTagsRoutes(app, deps) {
2
+ const { db } = deps;
3
+ app.get('/api/tags/:tag/notes', (req, res) => {
4
+ const tag = String(req.params.tag || '').trim().toLowerCase();
5
+ if (!tag || tag.length > 30)
6
+ return void res.status(400).json({ error: 'tag invalid' });
7
+ const limit = Math.min(50, Math.max(1, Number(req.query.limit) || 30));
8
+ const rows = db.prepare(`
9
+ SELECT s.id, s.owner_id, s.owner_code, s.type, s.title, s.native_text,
10
+ s.related_product_id, s.related_anchor, s.photo_hashes,
11
+ s.click_count, s.like_count, s.created_at,
12
+ p.title AS product_title,
13
+ u.handle as owner_handle, u.name as owner_name,
14
+ t.created_at as tagged_at
15
+ FROM shareable_tags t
16
+ JOIN shareables s ON s.id = t.shareable_id
17
+ LEFT JOIN products p ON p.id = s.related_product_id
18
+ LEFT JOIN users u ON u.id = s.owner_id
19
+ WHERE t.tag = ? AND s.status = 'active'
20
+ ORDER BY s.created_at DESC LIMIT ?
21
+ `).all(tag, limit);
22
+ for (const r of rows) {
23
+ if (typeof r.photo_hashes === 'string') {
24
+ try {
25
+ r.photo_hashes = JSON.parse(r.photo_hashes);
26
+ }
27
+ catch {
28
+ r.photo_hashes = [];
29
+ }
30
+ }
31
+ }
32
+ const stat = db.prepare(`SELECT COUNT(*) as count FROM shareable_tags WHERE tag = ?`).get(tag);
33
+ res.json({ tag, count: stat.count, items: rows });
34
+ });
35
+ // 热门标签:24h + 总数综合排序
36
+ app.get('/api/tags/trending', (_req, res) => {
37
+ const rows = db.prepare(`
38
+ SELECT tag, COUNT(*) as total,
39
+ SUM(CASE WHEN created_at > datetime('now', '-1 day') THEN 1 ELSE 0 END) as recent_24h
40
+ FROM shareable_tags
41
+ GROUP BY tag
42
+ HAVING total >= 1
43
+ ORDER BY recent_24h DESC, total DESC LIMIT 20
44
+ `).all();
45
+ res.json({ items: rows });
46
+ });
47
+ }