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.
- package/README.md +16 -3
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/AppShell-CQhg6DJU.js +1 -0
- package/out/assets/ChatUi-BepWs-ZU.js +1 -0
- package/out/assets/LanguageToggle-CtzCCAYv.js +1 -0
- package/out/assets/{LogoIcon-CYQ7cHd5.js → LogoIcon-Dxto3Sb4.js} +1 -1
- package/out/assets/{MarketingLayout-BTIbv4fW.js → MarketingLayout-BQw0IS2i.js} +1 -1
- package/out/assets/MarketingThemeToggle-DBaC9bjz.js +1 -0
- package/out/assets/MilkdownEditor-BqJzntYE.js +1054 -0
- package/out/assets/MoveModal-4D9n11Kw.js +1 -0
- package/out/assets/Nav-9MDdvgNs.js +1 -0
- package/out/assets/NoteSidebar-C-rIt32H.js +1 -0
- package/out/assets/OpenSidebarButton-Dd0JmKuE.js +1 -0
- package/out/assets/PemBlock-C8dEIzu-.js +1 -0
- package/out/assets/SidebarAccount-ClS-N0lq.js +1 -0
- package/out/assets/arrow-right-urE9Rd7j.js +1 -0
- package/out/assets/channelApi-BwQU0-h1.js +1 -0
- package/out/assets/check-DUNsD2t6.js +1 -0
- package/out/assets/chevron-down-D6mpsfv4.js +1 -0
- package/out/assets/{circle-alert-CqEQz6P4.js → circle-alert-W0iyN4sC.js} +1 -1
- package/out/assets/cloud-BMyOoC2x.js +1 -0
- package/out/assets/code-B1Cb_Icm.js +1 -0
- package/out/assets/{copy-CM-qWlbv.js → copy-C1MttOli.js} +1 -1
- package/out/assets/{dist-CvatM4u8.js → dist-8QDHqrPN.js} +1 -1
- package/out/assets/{dist-BmtYO1GG.js → dist-B0JrbG7f.js} +5 -5
- package/out/assets/{dist-DBpw-A8y.js → dist-BFSyuOuw.js} +1 -1
- package/out/assets/{dist-D5Cf0hK8.js → dist-BPs0Xns9.js} +2 -2
- package/out/assets/{dist-BQV5zYG_.js → dist-Br_5lLVO.js} +9 -9
- package/out/assets/{dist-CrzjJUOw.js → dist-BzqqCPi2.js} +1 -1
- package/out/assets/dist-C3lbe8SW.js +9 -0
- package/out/assets/{dist-CWJ3323z.js → dist-CfU9fwWz.js} +1 -1
- package/out/assets/{dist-BdmTLuCI.js → dist-DCX0ws1K.js} +1 -1
- package/out/assets/{dist-DrumcFOX.js → dist-DJdv-Ma3.js} +1 -1
- package/out/assets/{dist-C5HibLEW.js → dist-chOCTzB2.js} +1 -1
- package/out/assets/{download-0rM8xVCe.js → download-y7SZXu6E.js} +1 -1
- package/out/assets/downloadValidation-B0p9Ai_9.js +1 -0
- package/out/assets/filePreview-UI9NH34f.js +1 -0
- package/out/assets/format-CR8oUWq6.js +1 -0
- package/out/assets/game-CdU3xnZo.js +1 -0
- package/out/assets/{hard-drive-CCdIvSap.js → hard-drive-D13Qbobu.js} +1 -1
- package/out/assets/image-DJCA16l_.js +1 -0
- package/out/assets/index-BdaFEQG-.css +1 -0
- package/out/assets/index-QxXZzOUL.js +33 -0
- package/out/assets/index.lazy-BBTTFanX.js +1 -0
- package/out/assets/index.lazy-BG4ZylHD.js +2 -0
- package/out/assets/index.lazy-Bi-6ZXZX.js +1 -0
- package/out/assets/index.lazy-BixWVr0B.js +1 -0
- package/out/assets/index.lazy-BjFwNYy5.js +3 -0
- package/out/assets/index.lazy-C8EIQsXY.js +2 -0
- package/out/assets/index.lazy-CarNe2uu.js +1 -0
- package/out/assets/index.lazy-DEuGu3H3.js +1 -0
- package/out/assets/index.lazy-GPyILCA7.js +3 -0
- package/out/assets/index.lazy-I8ofndXl.js +1 -0
- package/out/assets/index.lazy-TxhWsA7y.js +1 -0
- package/out/assets/index.lazy-azfky8k7.js +1 -0
- package/out/assets/{key-round-tIqGrtt_.js → key-round-CZniN9lv.js} +1 -1
- package/out/assets/lock-D5OSNhep.js +1 -0
- package/out/assets/log-out-B6phyZ5z.js +1 -0
- package/out/assets/{music-BkZKq879.js → music-CbUskKgg.js} +1 -1
- package/out/assets/{notebook-pen-B4VSbweh.js → notebook-pen-DqKDQ6MJ.js} +1 -1
- package/out/assets/play-BIl8q9eU.js +1 -0
- package/out/assets/plus-BxxbpH6Q.js +1 -0
- package/out/assets/{save-BzjzC3eV.js → save-DkH1n_Ov.js} +1 -1
- package/out/assets/search-BQi5Z0E-.js +1 -0
- package/out/assets/{send-DtQInX0y.js → send-Cl6NtD2T.js} +1 -1
- package/out/assets/{trash-2-BhMrUgGM.js → trash-2-BBjpgK_f.js} +1 -1
- package/out/assets/triangle-alert-l98G8u9O.js +1 -0
- package/out/assets/upload-ByP6Ydde.js +1 -0
- package/out/assets/{useChannelMessages-Bs1hEJyd.js → useChannelMessages-BgbYfF2c.js} +2 -2
- package/out/assets/useGameRoom-DPmweWwe.js +1 -0
- package/out/assets/{wallet-YxbxCi7C.js → wallet-c7zIhNSM.js} +1 -1
- package/out/assets/{wifi-v3JpPCNm.js → wifi-Bm4biAjc.js} +1 -1
- package/out/chat/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/demo/index.html +0 -0
- package/out/download/index.html +6 -2
- package/out/game/gandengyan/index.html +0 -0
- package/out/game/index.html +0 -0
- package/out/game/zhajinhua/index.html +0 -0
- package/out/index.html +6 -2
- package/out/note/index.html +0 -0
- package/out/ping/index.html +6 -2
- package/out/web3/index.html +0 -0
- package/package.json +2 -2
- package/server/index.js +9 -0
- package/server/src/core/channelAttachment.js +7 -3
- package/server/src/core/channelIdentity.js +50 -0
- package/server/src/core/cid.js +6 -1
- package/server/src/core/cidTopic.js +18 -4
- package/server/src/core/displayPath.js +10 -0
- package/server/src/core/gameRoom.js +2 -2
- package/server/src/core/mostLink.js +45 -25
- package/server/src/core/ownerMetadata.js +34 -0
- package/server/src/core/userSyncKeys.js +36 -0
- package/server/src/http/app.js +71 -149
- package/server/src/http/dataPath.js +26 -0
- package/server/src/http/errors.js +8 -4
- package/server/src/http/nodeStatus.js +13 -13
- package/server/src/http/rateLimit.js +39 -0
- package/server/src/http/routePolicy.js +43 -0
- package/server/src/index.js +1909 -759
- package/server/src/node/offlineSwarm.js +20 -0
- package/server/src/utils/api.js +1 -15
- package/server/src/utils/downloadMessages.js +17 -18
- package/server/src/utils/errors.js +3 -1
- package/server/src/utils/noteUtils.js +27 -3
- package/out/assets/AppShell-DmZQwVA9.js +0 -1
- package/out/assets/ChatUi-CVGqjFdx.js +0 -1
- package/out/assets/MilkdownEditor-BL8xE7u9.js +0 -1054
- package/out/assets/MoveModal-BKkVBvrS.js +0 -1
- package/out/assets/Nav-BDGeJnbC.js +0 -1
- package/out/assets/NoteSidebar-BIJ8_m5K.js +0 -1
- package/out/assets/OpenSidebarButton-Di62DGiu.js +0 -1
- package/out/assets/PemBlock-Dxx6k9MH.js +0 -1
- package/out/assets/SidebarAccount-CCHZLGdP.js +0 -1
- package/out/assets/admin-BepWGXWG.js +0 -2
- package/out/assets/app-3D79fY3w.js +0 -1
- package/out/assets/arrow-right-D0sGC8QA.js +0 -1
- package/out/assets/channelApi-CL7YsIQ-.js +0 -1
- package/out/assets/chat-B56sk6od.js +0 -1
- package/out/assets/check-DdfnsLKm.js +0 -1
- package/out/assets/chevron-down-Xlb3wTxd.js +0 -1
- package/out/assets/circle-check-CwAH4dgJ.js +0 -1
- package/out/assets/cloud-CcPRoob1.js +0 -1
- package/out/assets/code-Dr6STnCn.js +0 -1
- package/out/assets/database-DQ7ZtUT9.js +0 -1
- package/out/assets/dateTime-D1koKRQU.js +0 -1
- package/out/assets/demo-B_6rlIjn.js +0 -3
- package/out/assets/dist-BGtXa07s.js +0 -9
- package/out/assets/download-BLPU-Kzq.js +0 -1
- package/out/assets/downloadMessages-7Xbd-HhS.js +0 -1
- package/out/assets/ed25519-BEctXF0E.js +0 -1
- package/out/assets/filePreview-BvbHWUTG.js +0 -1
- package/out/assets/folder-CcbCxm-k.js +0 -1
- package/out/assets/game-B0zuqnOh.js +0 -1
- package/out/assets/gandengyan-DbQC7hCK.js +0 -1
- package/out/assets/index-BLhmAher.css +0 -1
- package/out/assets/index-Cf23WD2V.js +0 -29
- package/out/assets/join-DQHXjlfH.js +0 -1
- package/out/assets/note-DmWqGSS2.js +0 -2
- package/out/assets/ping-JILckfMu.js +0 -1
- package/out/assets/play-BIl5vwqS.js +0 -1
- package/out/assets/plus-DHvLpuuw.js +0 -1
- package/out/assets/routes-Dyckj88f.js +0 -1
- package/out/assets/search-C-EpsDNl.js +0 -1
- package/out/assets/sun-C3IUQTpa.js +0 -1
- package/out/assets/tools-BEctXF0E.js +0 -1
- package/out/assets/triangle-alert-DUODU79n.js +0 -1
- package/out/assets/upload-CpDM23UH.js +0 -1
- package/out/assets/useGameRoom-C6UgmIGG.js +0 -1
- package/out/assets/web3-CRX1YFmw.js +0 -3
- package/out/assets/zhajinhua-QDmSZbOp.js +0 -1
- package/out/web3/ed25519/index.html +0 -0
- package/out/web3/tools/index.html +0 -0
- /package/out/assets/{gandengyan-8eWJAjpY.css → index-8eWJAjpY.css} +0 -0
- /package/out/assets/{zhajinhua-BZc4blbW.css → index-BZc4blbW.css} +0 -0
package/server/src/http/app.js
CHANGED
|
@@ -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/*',
|
|
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.
|
|
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
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
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.
|
|
478
|
+
app.get('/api/user/sync/status', c => {
|
|
577
479
|
try {
|
|
578
|
-
|
|
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.
|
|
724
|
-
return c.json(
|
|
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.
|
|
774
|
-
return c.json(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
1033
|
-
const 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/
|
|
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: '
|
|
149
|
-
responses: { 200: { description: '
|
|
142
|
+
summary: 'Start hidden authenticated user metadata sync',
|
|
143
|
+
responses: { 200: { description: 'User sync status' } },
|
|
150
144
|
},
|
|
151
145
|
},
|
|
152
|
-
'/api/user/
|
|
153
|
-
|
|
154
|
-
summary: '
|
|
155
|
-
responses: { 200: { description: '
|
|
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
|
+
}
|