most-box 0.1.9 → 0.2.0

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 (157) hide show
  1. package/README.md +16 -3
  2. package/out/admin/index.html +0 -0
  3. package/out/app/index.html +0 -0
  4. package/out/assets/AppShell-CQhg6DJU.js +1 -0
  5. package/out/assets/ChatUi-BepWs-ZU.js +1 -0
  6. package/out/assets/LanguageToggle-CtzCCAYv.js +1 -0
  7. package/out/assets/{LogoIcon-CYQ7cHd5.js → LogoIcon-Dxto3Sb4.js} +1 -1
  8. package/out/assets/{MarketingLayout-BTIbv4fW.js → MarketingLayout-BQw0IS2i.js} +1 -1
  9. package/out/assets/MarketingThemeToggle-DBaC9bjz.js +1 -0
  10. package/out/assets/MilkdownEditor-BqJzntYE.js +1054 -0
  11. package/out/assets/MoveModal-4D9n11Kw.js +1 -0
  12. package/out/assets/Nav-9MDdvgNs.js +1 -0
  13. package/out/assets/NoteSidebar-C-rIt32H.js +1 -0
  14. package/out/assets/OpenSidebarButton-Dd0JmKuE.js +1 -0
  15. package/out/assets/PemBlock-C8dEIzu-.js +1 -0
  16. package/out/assets/SidebarAccount-ClS-N0lq.js +1 -0
  17. package/out/assets/arrow-right-urE9Rd7j.js +1 -0
  18. package/out/assets/channelApi-BwQU0-h1.js +1 -0
  19. package/out/assets/check-DUNsD2t6.js +1 -0
  20. package/out/assets/chevron-down-D6mpsfv4.js +1 -0
  21. package/out/assets/{circle-alert-CqEQz6P4.js → circle-alert-W0iyN4sC.js} +1 -1
  22. package/out/assets/cloud-BMyOoC2x.js +1 -0
  23. package/out/assets/code-B1Cb_Icm.js +1 -0
  24. package/out/assets/{copy-CM-qWlbv.js → copy-C1MttOli.js} +1 -1
  25. package/out/assets/{dist-CvatM4u8.js → dist-8QDHqrPN.js} +1 -1
  26. package/out/assets/{dist-BmtYO1GG.js → dist-B0JrbG7f.js} +5 -5
  27. package/out/assets/{dist-DBpw-A8y.js → dist-BFSyuOuw.js} +1 -1
  28. package/out/assets/{dist-D5Cf0hK8.js → dist-BPs0Xns9.js} +2 -2
  29. package/out/assets/{dist-BQV5zYG_.js → dist-Br_5lLVO.js} +9 -9
  30. package/out/assets/{dist-CrzjJUOw.js → dist-BzqqCPi2.js} +1 -1
  31. package/out/assets/dist-C3lbe8SW.js +9 -0
  32. package/out/assets/{dist-CWJ3323z.js → dist-CfU9fwWz.js} +1 -1
  33. package/out/assets/{dist-BdmTLuCI.js → dist-DCX0ws1K.js} +1 -1
  34. package/out/assets/{dist-DrumcFOX.js → dist-DJdv-Ma3.js} +1 -1
  35. package/out/assets/{dist-C5HibLEW.js → dist-chOCTzB2.js} +1 -1
  36. package/out/assets/{download-0rM8xVCe.js → download-y7SZXu6E.js} +1 -1
  37. package/out/assets/downloadValidation-B0p9Ai_9.js +1 -0
  38. package/out/assets/filePreview-UI9NH34f.js +1 -0
  39. package/out/assets/format-CR8oUWq6.js +1 -0
  40. package/out/assets/game-CdU3xnZo.js +1 -0
  41. package/out/assets/{hard-drive-CCdIvSap.js → hard-drive-D13Qbobu.js} +1 -1
  42. package/out/assets/image-DJCA16l_.js +1 -0
  43. package/out/assets/index-BdaFEQG-.css +1 -0
  44. package/out/assets/index-QxXZzOUL.js +33 -0
  45. package/out/assets/index.lazy-BBTTFanX.js +1 -0
  46. package/out/assets/index.lazy-BG4ZylHD.js +2 -0
  47. package/out/assets/index.lazy-Bi-6ZXZX.js +1 -0
  48. package/out/assets/index.lazy-BixWVr0B.js +1 -0
  49. package/out/assets/index.lazy-BjFwNYy5.js +3 -0
  50. package/out/assets/index.lazy-C8EIQsXY.js +2 -0
  51. package/out/assets/index.lazy-CarNe2uu.js +1 -0
  52. package/out/assets/index.lazy-DEuGu3H3.js +1 -0
  53. package/out/assets/index.lazy-GPyILCA7.js +3 -0
  54. package/out/assets/index.lazy-I8ofndXl.js +1 -0
  55. package/out/assets/index.lazy-TxhWsA7y.js +1 -0
  56. package/out/assets/index.lazy-azfky8k7.js +1 -0
  57. package/out/assets/{key-round-tIqGrtt_.js → key-round-CZniN9lv.js} +1 -1
  58. package/out/assets/lock-D5OSNhep.js +1 -0
  59. package/out/assets/log-out-B6phyZ5z.js +1 -0
  60. package/out/assets/{music-BkZKq879.js → music-CbUskKgg.js} +1 -1
  61. package/out/assets/{notebook-pen-B4VSbweh.js → notebook-pen-DqKDQ6MJ.js} +1 -1
  62. package/out/assets/play-BIl8q9eU.js +1 -0
  63. package/out/assets/plus-BxxbpH6Q.js +1 -0
  64. package/out/assets/{save-BzjzC3eV.js → save-DkH1n_Ov.js} +1 -1
  65. package/out/assets/search-BQi5Z0E-.js +1 -0
  66. package/out/assets/{send-DtQInX0y.js → send-Cl6NtD2T.js} +1 -1
  67. package/out/assets/{trash-2-BhMrUgGM.js → trash-2-BBjpgK_f.js} +1 -1
  68. package/out/assets/triangle-alert-l98G8u9O.js +1 -0
  69. package/out/assets/upload-ByP6Ydde.js +1 -0
  70. package/out/assets/{useChannelMessages-Bs1hEJyd.js → useChannelMessages-BgbYfF2c.js} +2 -2
  71. package/out/assets/useGameRoom-DPmweWwe.js +1 -0
  72. package/out/assets/{wallet-YxbxCi7C.js → wallet-c7zIhNSM.js} +1 -1
  73. package/out/assets/{wifi-v3JpPCNm.js → wifi-Bm4biAjc.js} +1 -1
  74. package/out/chat/index.html +0 -0
  75. package/out/chat/join/index.html +0 -0
  76. package/out/demo/index.html +0 -0
  77. package/out/download/index.html +6 -2
  78. package/out/game/gandengyan/index.html +0 -0
  79. package/out/game/index.html +0 -0
  80. package/out/game/zhajinhua/index.html +0 -0
  81. package/out/index.html +6 -2
  82. package/out/note/index.html +0 -0
  83. package/out/ping/index.html +6 -2
  84. package/out/web3/index.html +0 -0
  85. package/package.json +2 -2
  86. package/server/index.js +9 -0
  87. package/server/src/core/channelAttachment.js +7 -3
  88. package/server/src/core/channelIdentity.js +50 -0
  89. package/server/src/core/cid.js +6 -1
  90. package/server/src/core/cidTopic.js +18 -4
  91. package/server/src/core/displayPath.js +10 -0
  92. package/server/src/core/gameRoom.js +2 -2
  93. package/server/src/core/mostLink.js +45 -25
  94. package/server/src/core/ownerMetadata.js +34 -0
  95. package/server/src/core/userSyncKeys.js +36 -0
  96. package/server/src/http/app.js +71 -149
  97. package/server/src/http/dataPath.js +26 -0
  98. package/server/src/http/errors.js +8 -4
  99. package/server/src/http/nodeStatus.js +13 -13
  100. package/server/src/http/rateLimit.js +39 -0
  101. package/server/src/http/routePolicy.js +43 -0
  102. package/server/src/index.js +1909 -759
  103. package/server/src/node/offlineSwarm.js +20 -0
  104. package/server/src/utils/api.js +1 -15
  105. package/server/src/utils/downloadMessages.js +17 -18
  106. package/server/src/utils/errors.js +3 -1
  107. package/server/src/utils/noteUtils.js +27 -3
  108. package/out/assets/AppShell-DmZQwVA9.js +0 -1
  109. package/out/assets/ChatUi-CVGqjFdx.js +0 -1
  110. package/out/assets/MilkdownEditor-BL8xE7u9.js +0 -1054
  111. package/out/assets/MoveModal-BKkVBvrS.js +0 -1
  112. package/out/assets/Nav-BDGeJnbC.js +0 -1
  113. package/out/assets/NoteSidebar-BIJ8_m5K.js +0 -1
  114. package/out/assets/OpenSidebarButton-Di62DGiu.js +0 -1
  115. package/out/assets/PemBlock-Dxx6k9MH.js +0 -1
  116. package/out/assets/SidebarAccount-CCHZLGdP.js +0 -1
  117. package/out/assets/admin-BepWGXWG.js +0 -2
  118. package/out/assets/app-3D79fY3w.js +0 -1
  119. package/out/assets/arrow-right-D0sGC8QA.js +0 -1
  120. package/out/assets/channelApi-CL7YsIQ-.js +0 -1
  121. package/out/assets/chat-B56sk6od.js +0 -1
  122. package/out/assets/check-DdfnsLKm.js +0 -1
  123. package/out/assets/chevron-down-Xlb3wTxd.js +0 -1
  124. package/out/assets/circle-check-CwAH4dgJ.js +0 -1
  125. package/out/assets/cloud-CcPRoob1.js +0 -1
  126. package/out/assets/code-Dr6STnCn.js +0 -1
  127. package/out/assets/database-DQ7ZtUT9.js +0 -1
  128. package/out/assets/dateTime-D1koKRQU.js +0 -1
  129. package/out/assets/demo-B_6rlIjn.js +0 -3
  130. package/out/assets/dist-BGtXa07s.js +0 -9
  131. package/out/assets/download-BLPU-Kzq.js +0 -1
  132. package/out/assets/downloadMessages-7Xbd-HhS.js +0 -1
  133. package/out/assets/ed25519-BEctXF0E.js +0 -1
  134. package/out/assets/filePreview-BvbHWUTG.js +0 -1
  135. package/out/assets/folder-CcbCxm-k.js +0 -1
  136. package/out/assets/game-B0zuqnOh.js +0 -1
  137. package/out/assets/gandengyan-DbQC7hCK.js +0 -1
  138. package/out/assets/index-BLhmAher.css +0 -1
  139. package/out/assets/index-Cf23WD2V.js +0 -29
  140. package/out/assets/join-DQHXjlfH.js +0 -1
  141. package/out/assets/note-DmWqGSS2.js +0 -2
  142. package/out/assets/ping-JILckfMu.js +0 -1
  143. package/out/assets/play-BIl5vwqS.js +0 -1
  144. package/out/assets/plus-DHvLpuuw.js +0 -1
  145. package/out/assets/routes-Dyckj88f.js +0 -1
  146. package/out/assets/search-C-EpsDNl.js +0 -1
  147. package/out/assets/sun-C3IUQTpa.js +0 -1
  148. package/out/assets/tools-BEctXF0E.js +0 -1
  149. package/out/assets/triangle-alert-DUODU79n.js +0 -1
  150. package/out/assets/upload-CpDM23UH.js +0 -1
  151. package/out/assets/useGameRoom-C6UgmIGG.js +0 -1
  152. package/out/assets/web3-CRX1YFmw.js +0 -3
  153. package/out/assets/zhajinhua-QDmSZbOp.js +0 -1
  154. package/out/web3/ed25519/index.html +0 -0
  155. package/out/web3/tools/index.html +0 -0
  156. /package/out/assets/{gandengyan-8eWJAjpY.css → index-8eWJAjpY.css} +0 -0
  157. /package/out/assets/{zhajinhua-BZc4blbW.css → index-BZc4blbW.css} +0 -0
@@ -26,6 +26,7 @@ import {
26
26
  remoteInviteConfigured,
27
27
  } from './access.js'
28
28
  import { badRequestOrAppError, errorJson } from './errors.js'
29
+ import { resolveDataPathForSave } from './dataPath.js'
29
30
  import { listFilteredNodeLogs } from './nodeLogs.js'
30
31
  import {
31
32
  buildNodeStatus,
@@ -33,11 +34,16 @@ import {
33
34
  getNetworkAddresses,
34
35
  getPackageVersion,
35
36
  } from './nodeStatus.js'
37
+ import { createRateLimitMiddleware } from './rateLimit.js'
38
+ import {
39
+ validationErrorPayload,
40
+ isPublicFileDownloadPath,
41
+ requiresUserAuth,
42
+ isAdminApi,
43
+ } from './routePolicy.js'
36
44
  import { parseMultipartBusboy } from './uploads.js'
37
45
  import { getMimeType, registerStaticRoutes } from './staticFiles.js'
38
46
 
39
- const RATE_LIMIT_WINDOW = 60 * 1000
40
- const RATE_LIMIT_MAX_REQUESTS = 120
41
47
  export { UPLOAD_TMP_DIR } from './uploads.js'
42
48
 
43
49
  // --- 配置 ---
@@ -46,38 +52,10 @@ const CONFIG_DIR = defaultConfigStore.configDir
46
52
  const PORT = DEFAULT_NODE_PORT
47
53
  const HOST = DEFAULT_NODE_HOST
48
54
 
49
-
50
-
51
55
  export function getDataPath(configStore = defaultConfigStore) {
52
56
  return configStore.getDataPath()
53
57
  }
54
58
 
55
- function resolveDataPathForSave(inputPath) {
56
- let dataPath = String(inputPath || '').trim()
57
- let basePath = dataPath
58
-
59
- if (!dataPath) {
60
- return { dataPath: '' }
61
- }
62
-
63
- if (dataPath.match(/^[A-Za-z]:\\$/)) {
64
- basePath = dataPath
65
- dataPath = path.join(dataPath, 'most-data')
66
- }
67
-
68
- if (!fs.existsSync(basePath)) {
69
- return { error: '目录不存在' }
70
- }
71
-
72
- if (!fs.existsSync(dataPath)) {
73
- fs.mkdirSync(dataPath, { recursive: true })
74
- }
75
-
76
- return { dataPath }
77
- }
78
-
79
-
80
-
81
59
  // --- Hono 应用工厂 ---
82
60
  export function createApp(engine, options = {}) {
83
61
  const appPort = options.port || PORT
@@ -95,40 +73,6 @@ export function createApp(engine, options = {}) {
95
73
  return new Set(invites)
96
74
  }
97
75
 
98
- // 速率限制(每个 app 实例独立)
99
- const rateLimitMap = new Map()
100
- function checkRateLimit(clientIp) {
101
- const now = Date.now()
102
- if (!rateLimitMap.has(clientIp)) {
103
- rateLimitMap.set(clientIp, [])
104
- }
105
- const requests = rateLimitMap.get(clientIp)
106
- while (requests.length > 0 && requests[0] < now - RATE_LIMIT_WINDOW) {
107
- requests.shift()
108
- }
109
- if (requests.length === 0) {
110
- rateLimitMap.delete(clientIp)
111
- }
112
- if (requests.length >= RATE_LIMIT_MAX_REQUESTS) {
113
- return false
114
- }
115
- requests.push(now)
116
- return true
117
- }
118
-
119
- function rateLimitMiddleware() {
120
- return async (c, next) => {
121
- const clientIp =
122
- c.req.header('x-forwarded-for') ||
123
- c.env?.incoming?.socket?.remoteAddress ||
124
- 'unknown'
125
- if (!checkRateLimit(clientIp)) {
126
- return c.json({ error: 'Too many requests' }, 429)
127
- }
128
- await next()
129
- }
130
- }
131
-
132
76
  function isValidInvite(c) {
133
77
  const invite = String(c.req.header('x-mostbox-invite') || '').trim()
134
78
  return hasValidInvite(getRemoteInviteSet(), invite)
@@ -143,43 +87,6 @@ export function createApp(engine, options = {}) {
143
87
  })
144
88
  }
145
89
 
146
- function isPublicFileDownloadPath(path) {
147
- return /^\/api\/files\/[^/]+\/download$/.test(path)
148
- }
149
-
150
- function requiresUserAuth(path) {
151
- if (isPublicFileDownloadPath(path)) {
152
- return false
153
- }
154
-
155
- return (
156
- path === '/api/files' ||
157
- path === '/api/publish' ||
158
- path === '/api/download/check' ||
159
- path === '/api/download' ||
160
- path === '/api/download/cancel' ||
161
- path === '/api/user/export' ||
162
- path === '/api/user/import/check' ||
163
- path === '/api/user/import' ||
164
- path === '/api/trash' ||
165
- path === '/api/move' ||
166
- path === '/api/folder/rename' ||
167
- path.startsWith('/api/files/') ||
168
- path.startsWith('/api/trash/') ||
169
- path.startsWith('/api/channels')
170
- )
171
- }
172
-
173
- function isAdminApi(path) {
174
- return (
175
- path.startsWith('/api/admin/') ||
176
- path === '/api/node/config' ||
177
- path === '/api/node/policy' ||
178
- path === '/api/node/logs' ||
179
- path === '/api/shutdown'
180
- )
181
- }
182
-
183
90
  function authMiddleware() {
184
91
  return async (c, next) => {
185
92
  const path = getRequestPath(c)
@@ -356,7 +263,7 @@ export function createApp(engine, options = {}) {
356
263
  )
357
264
 
358
265
  // 速率限制中间件
359
- app.use('/api/*', rateLimitMiddleware())
266
+ app.use('/api/*', createRateLimitMiddleware())
360
267
  app.use('/api/*', authMiddleware())
361
268
 
362
269
  // 全局错误处理
@@ -550,22 +457,17 @@ export function createApp(engine, options = {}) {
550
457
  return c.json({ users: engine.listUsers() })
551
458
  })
552
459
 
553
- app.get('/api/user/export', c => {
554
- try {
555
- return c.json(engine.exportUserMetadata(c.get('userAddress')))
556
- } catch (err) {
557
- return errorJson(c, err)
558
- }
559
- })
560
-
561
- app.post('/api/user/import/check', async c => {
460
+ app.post('/api/user/sync/start', async c => {
562
461
  try {
563
462
  const body = await c.req.json()
564
- const timeout =
565
- body.timeout === undefined ? undefined : Number(body.timeout)
566
- const result = await engine.checkUserImport(body.package || body, {
567
- ownerAddress: c.get('userAddress'),
568
- timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
463
+ const result = await engine.startUserSync(c.get('userAddress'), body)
464
+ appendNodeLog({
465
+ event: 'node:user-sync:started',
466
+ message: 'User sync started',
467
+ data: {
468
+ ownerAddress: result.ownerAddress,
469
+ syncName: result.syncName,
470
+ },
569
471
  })
570
472
  return c.json({ success: true, ...result })
571
473
  } catch (err) {
@@ -573,27 +475,9 @@ export function createApp(engine, options = {}) {
573
475
  }
574
476
  })
575
477
 
576
- app.post('/api/user/import', async c => {
478
+ app.get('/api/user/sync/status', c => {
577
479
  try {
578
- const body = await c.req.json()
579
- const timeout =
580
- body.timeout === undefined ? undefined : Number(body.timeout)
581
- const result = await engine.importUserMetadata(body.package || body, {
582
- ownerAddress: c.get('userAddress'),
583
- checkId: body.checkId,
584
- timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
585
- })
586
- appendNodeLog({
587
- event: 'node:user-data:imported',
588
- message: 'User data imported',
589
- data: {
590
- ownerAddress: result.ownerAddress,
591
- importedFiles: result.importedFiles,
592
- failedFiles: result.failedFiles.length,
593
- },
594
- })
595
- await broadcastNodeStatus()
596
- return c.json(result)
480
+ return c.json(engine.getUserSyncStatus(c.get('userAddress')))
597
481
  } catch (err) {
598
482
  return errorJson(c, err)
599
483
  }
@@ -720,8 +604,11 @@ export function createApp(engine, options = {}) {
720
604
  }
721
605
 
722
606
  const parsed = parseMostLink(body.link)
723
- if (parsed.error) {
724
- return c.json({ error: parsed.error }, 400)
607
+ if (parsed.errorCode) {
608
+ return c.json(
609
+ validationErrorPayload(parsed.errorCode, parsed.details),
610
+ 400
611
+ )
725
612
  }
726
613
 
727
614
  const localAvailability = await engine.getLocalCidAvailability(body.link, {
@@ -770,8 +657,11 @@ export function createApp(engine, options = {}) {
770
657
  const taskId = `dl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
771
658
 
772
659
  const parsed = parseMostLink(body.link)
773
- if (parsed.error) {
774
- return c.json({ error: parsed.error }, 400)
660
+ if (parsed.errorCode) {
661
+ return c.json(
662
+ validationErrorPayload(parsed.errorCode, parsed.details),
663
+ 400
664
+ )
775
665
  }
776
666
 
777
667
  const localAvailability = await engine.getLocalCidAvailability(body.link, {
@@ -827,7 +717,7 @@ export function createApp(engine, options = {}) {
827
717
  const cid = c.req.param('cid')
828
718
  const cidValidation = validateCidString(cid)
829
719
  if (!cidValidation.valid) {
830
- return c.json({ error: cidValidation.error }, 400)
720
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
831
721
  }
832
722
  const result = await engine.deletePublishedFile(cid, {
833
723
  ownerAddress: c.get('userAddress'),
@@ -835,6 +725,27 @@ export function createApp(engine, options = {}) {
835
725
  return c.json(result)
836
726
  })
837
727
 
728
+ app.post('/api/files/:cid/cache', async c => {
729
+ const cid = c.req.param('cid')
730
+ const cidValidation = validateCidString(cid)
731
+ if (!cidValidation.valid) {
732
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
733
+ }
734
+ try {
735
+ const body = await c.req.json().catch(() => ({}))
736
+ const timeout =
737
+ body.timeout === undefined ? undefined : Number(body.timeout)
738
+ const result = await engine.cacheFile(cid, {
739
+ ownerAddress: c.get('userAddress'),
740
+ timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : undefined,
741
+ taskId: body.taskId,
742
+ })
743
+ return c.json({ success: true, ...result })
744
+ } catch (err) {
745
+ return badRequestOrAppError(c, err)
746
+ }
747
+ })
748
+
838
749
  app.post('/api/move', async c => {
839
750
  const body = await c.req.json()
840
751
  if (!body.cid || !body.newFileName) {
@@ -842,7 +753,7 @@ export function createApp(engine, options = {}) {
842
753
  }
843
754
  const cidValidation = validateCidString(body.cid)
844
755
  if (!cidValidation.valid) {
845
- return c.json({ error: cidValidation.error }, 400)
756
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
846
757
  }
847
758
  const cleanFileName = sanitizeFilename(body.newFileName)
848
759
  if (
@@ -866,7 +777,7 @@ export function createApp(engine, options = {}) {
866
777
  const cid = c.req.param('cid')
867
778
  const cidValidation = validateCidString(cid)
868
779
  if (!cidValidation.valid) {
869
- return c.json({ error: cidValidation.error }, 400)
780
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
870
781
  }
871
782
 
872
783
  const rangeHeader = c.req.header('range')
@@ -928,7 +839,7 @@ export function createApp(engine, options = {}) {
928
839
  const cid = c.req.param('cid')
929
840
  const cidValidation = validateCidString(cid)
930
841
  if (!cidValidation.valid) {
931
- return c.json({ error: cidValidation.error }, 400)
842
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
932
843
  }
933
844
  try {
934
845
  const result = await engine.restoreTrashFile(cid, {
@@ -944,7 +855,7 @@ export function createApp(engine, options = {}) {
944
855
  const cid = c.req.param('cid')
945
856
  const cidValidation = validateCidString(cid)
946
857
  if (!cidValidation.valid) {
947
- return c.json({ error: cidValidation.error }, 400)
858
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
948
859
  }
949
860
  const result = await engine.permanentDeleteTrashFile(cid, {
950
861
  ownerAddress: c.get('userAddress'),
@@ -964,7 +875,7 @@ export function createApp(engine, options = {}) {
964
875
  const cid = c.req.param('cid')
965
876
  const cidValidation = validateCidString(cid)
966
877
  if (!cidValidation.valid) {
967
- return c.json({ error: cidValidation.error }, 400)
878
+ return c.json(validationErrorPayload(cidValidation.errorCode), 400)
968
879
  }
969
880
  try {
970
881
  const result = engine.toggleStarred(cid, {
@@ -1011,9 +922,12 @@ export function createApp(engine, options = {}) {
1011
922
  ownerAddress: c.get('userAddress'),
1012
923
  displayName: body.displayName,
1013
924
  avatar: body.avatar,
925
+ channelKey: body.channelKey,
926
+ fingerprint: body.fingerprint,
927
+ discover: true,
1014
928
  }
1015
929
  )
1016
- return c.json({ success: true, ...result })
930
+ return c.json({ success: !result.conflict, ...result })
1017
931
  } catch (err) {
1018
932
  return c.json({ error: err.message }, 400)
1019
933
  }
@@ -1029,8 +943,11 @@ export function createApp(engine, options = {}) {
1029
943
  )
1030
944
  })
1031
945
 
1032
- app.delete('/api/channels/:name', async c => {
1033
- const name = c.req.param('name')
946
+ const leaveChannelForRequest = async (c, channelIdentifier) => {
947
+ const name = String(channelIdentifier || '').trim()
948
+ if (!name) {
949
+ return c.json({ error: '频道标识不能为空' }, 400)
950
+ }
1034
951
  try {
1035
952
  const result = await engine.leaveChannel(name, {
1036
953
  ownerAddress: c.get('userAddress'),
@@ -1039,6 +956,11 @@ export function createApp(engine, options = {}) {
1039
956
  } catch (err) {
1040
957
  return c.json({ error: err.message }, 400)
1041
958
  }
959
+ }
960
+
961
+ app.delete('/api/channels', async c => {
962
+ const body = await c.req.json().catch(() => ({}))
963
+ return leaveChannelForRequest(c, body.channelKey || body.name)
1042
964
  })
1043
965
 
1044
966
  app.get('/api/channels/:name/messages', async c => {
@@ -0,0 +1,26 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ export function resolveDataPathForSave(inputPath) {
5
+ let dataPath = String(inputPath || '').trim()
6
+ let basePath = dataPath
7
+
8
+ if (!dataPath) {
9
+ return { dataPath: '' }
10
+ }
11
+
12
+ if (dataPath.match(/^[A-Za-z]:\\$/)) {
13
+ basePath = dataPath
14
+ dataPath = path.join(dataPath, 'most-data')
15
+ }
16
+
17
+ if (!fs.existsSync(basePath)) {
18
+ return { error: '目录不存在' }
19
+ }
20
+
21
+ if (!fs.existsSync(dataPath)) {
22
+ fs.mkdirSync(dataPath, { recursive: true })
23
+ }
24
+
25
+ return { dataPath }
26
+ }
@@ -20,11 +20,15 @@ export function getApiErrorStatus(err) {
20
20
  }
21
21
 
22
22
  export function errorJson(c, err) {
23
+ const payload = {
24
+ error: err.message,
25
+ code: err.code || 'UNKNOWN',
26
+ }
27
+ if (err.errorCode) payload.errorCode = err.errorCode
28
+ if (err.details) payload.details = err.details
29
+
23
30
  return c.json(
24
- {
25
- error: err.message,
26
- code: err.code || 'UNKNOWN',
27
- },
31
+ payload,
28
32
  getApiErrorStatus(err)
29
33
  )
30
34
  }
@@ -137,22 +137,16 @@ export function buildOpenApiSpec(appPort) {
137
137
  responses: { 200: { description: 'Pull task result' } },
138
138
  },
139
139
  },
140
- '/api/user/export': {
141
- get: {
142
- summary: 'Export authenticated user metadata',
143
- responses: { 200: { description: 'User metadata export' } },
144
- },
145
- },
146
- '/api/user/import/check': {
140
+ '/api/user/sync/start': {
147
141
  post: {
148
- summary: 'Validate a user metadata import package',
149
- responses: { 200: { description: 'Import readiness result' } },
142
+ summary: 'Start hidden authenticated user metadata sync',
143
+ responses: { 200: { description: 'User sync status' } },
150
144
  },
151
145
  },
152
- '/api/user/import': {
153
- post: {
154
- summary: 'Replace authenticated user metadata and pull files by CID',
155
- responses: { 200: { description: 'Import result' } },
146
+ '/api/user/sync/status': {
147
+ get: {
148
+ summary: 'Read authenticated user metadata sync status',
149
+ responses: { 200: { description: 'User sync status' } },
156
150
  },
157
151
  },
158
152
  '/api/files': {
@@ -161,6 +155,12 @@ export function buildOpenApiSpec(appPort) {
161
155
  responses: { 200: { description: 'Published file list' } },
162
156
  },
163
157
  },
158
+ '/api/files/{cid}/cache': {
159
+ post: {
160
+ summary: 'Pull a synced directory file into this node cache',
161
+ responses: { 200: { description: 'Cache pull result' } },
162
+ },
163
+ },
164
164
  '/api/publish': {
165
165
  post: {
166
166
  summary: 'Publish a file and start seeding by CID',
@@ -0,0 +1,39 @@
1
+ const DEFAULT_RATE_LIMIT_WINDOW = 60 * 1000
2
+ const DEFAULT_RATE_LIMIT_MAX_REQUESTS = 120
3
+
4
+ export function createRateLimitMiddleware({
5
+ windowMs = DEFAULT_RATE_LIMIT_WINDOW,
6
+ maxRequests = DEFAULT_RATE_LIMIT_MAX_REQUESTS,
7
+ } = {}) {
8
+ const rateLimitMap = new Map()
9
+
10
+ function checkRateLimit(clientIp) {
11
+ const now = Date.now()
12
+ if (!rateLimitMap.has(clientIp)) {
13
+ rateLimitMap.set(clientIp, [])
14
+ }
15
+ const requests = rateLimitMap.get(clientIp)
16
+ while (requests.length > 0 && requests[0] < now - windowMs) {
17
+ requests.shift()
18
+ }
19
+ if (requests.length === 0) {
20
+ rateLimitMap.delete(clientIp)
21
+ }
22
+ if (requests.length >= maxRequests) {
23
+ return false
24
+ }
25
+ requests.push(now)
26
+ return true
27
+ }
28
+
29
+ return async (c, next) => {
30
+ const clientIp =
31
+ c.req.header('x-forwarded-for') ||
32
+ c.env?.incoming?.socket?.remoteAddress ||
33
+ 'unknown'
34
+ if (!checkRateLimit(clientIp)) {
35
+ return c.json({ error: 'Too many requests' }, 429)
36
+ }
37
+ await next()
38
+ }
39
+ }
@@ -0,0 +1,43 @@
1
+ export function validationErrorPayload(errorCode, details = undefined) {
2
+ return {
3
+ errorCode,
4
+ code: 'VALIDATION_ERROR',
5
+ ...(details ? { details } : {}),
6
+ }
7
+ }
8
+
9
+ export function isPublicFileDownloadPath(path) {
10
+ return /^\/api\/files\/[^/]+\/download$/.test(path)
11
+ }
12
+
13
+ export function requiresUserAuth(path) {
14
+ if (isPublicFileDownloadPath(path)) {
15
+ return false
16
+ }
17
+
18
+ return (
19
+ path === '/api/files' ||
20
+ path === '/api/publish' ||
21
+ path === '/api/download/check' ||
22
+ path === '/api/download' ||
23
+ path === '/api/download/cancel' ||
24
+ path === '/api/user/sync/start' ||
25
+ path === '/api/user/sync/status' ||
26
+ path === '/api/trash' ||
27
+ path === '/api/move' ||
28
+ path === '/api/folder/rename' ||
29
+ path.startsWith('/api/files/') ||
30
+ path.startsWith('/api/trash/') ||
31
+ path.startsWith('/api/channels')
32
+ )
33
+ }
34
+
35
+ export function isAdminApi(path) {
36
+ return (
37
+ path.startsWith('/api/admin/') ||
38
+ path === '/api/node/config' ||
39
+ path === '/api/node/policy' ||
40
+ path === '/api/node/logs' ||
41
+ path === '/api/shutdown'
42
+ )
43
+ }