5 changed files with 412 additions and 10 deletions
@ -0,0 +1,87 @@ |
|||
import multer from "multer"; |
|||
import fs from "node:fs"; |
|||
import os from "node:os"; |
|||
import path from "node:path"; |
|||
import { callNodeListener, getRequestIP } from "h3"; |
|||
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media"; |
|||
import { replaceMediaAssetFileFromTempUpload } from "#server/service/media"; |
|||
import { requireAdmin } from "#server/utils/admin-guard"; |
|||
import { R } from "#server/utils/response"; |
|||
import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; |
|||
|
|||
type MulterUploadedFile = { |
|||
originalname: string; |
|||
filename: string; |
|||
path: string; |
|||
mimetype: string; |
|||
size: number; |
|||
}; |
|||
|
|||
export default defineWrappedResponseHandler(async (event) => { |
|||
const admin = await requireAdmin(event); |
|||
const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown"; |
|||
assertUnderRateLimit(`admin-media-asset-reupload:${ip}`, 40, 60_000); |
|||
|
|||
const idRaw = getRouterParam(event, "id"); |
|||
const assetId = Number(idRaw); |
|||
if (!Number.isInteger(assetId) || assetId < 1) { |
|||
throw createError({ statusCode: 400, statusMessage: "无效的资源 id" }); |
|||
} |
|||
|
|||
const upload = multer({ |
|||
storage: multer.diskStorage({ |
|||
destination: (_req, _file, cb) => { |
|||
cb(null, os.tmpdir()); |
|||
}, |
|||
filename: (_req, file, cb) => { |
|||
const ext = path.extname(file.originalname).toLowerCase(); |
|||
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, "-"); |
|||
cb(null, `reupload-${Date.now()}-${Math.round(Math.random() * 1e9)}-${baseName}${ext}`); |
|||
}, |
|||
}), |
|||
limits: { fileSize: 10 * 1024 * 1024 }, |
|||
fileFilter: (_req, file, cb) => { |
|||
const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; |
|||
if (allowed.includes(file.mimetype)) { |
|||
cb(null, true); |
|||
} else { |
|||
cb(new Error("只支持 PNG/JPG/WebP 格式图片")); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
await callNodeListener( |
|||
// @ts-expect-error multer 中间件类型
|
|||
upload.single("file"), |
|||
event.node.req, |
|||
event.node.res, |
|||
); |
|||
|
|||
// @ts-expect-error multer 挂载 file
|
|||
const file = event.node.req.file as MulterUploadedFile | undefined; |
|||
if (!file?.path) { |
|||
throw createError({ statusCode: 400, statusMessage: "请选择要上传的图片" }); |
|||
} |
|||
|
|||
try { |
|||
const result = await replaceMediaAssetFileFromTempUpload({ |
|||
assetId, |
|||
actorUserId: admin.id, |
|||
actorIsAdmin: true, |
|||
tempInputPath: file.path, |
|||
}); |
|||
return R.success({ |
|||
url: `${POST_MEDIA_PUBLIC_PREFIX}${result.storageKey}`, |
|||
sizeBytes: result.sizeBytes, |
|||
}); |
|||
} catch (err) { |
|||
if (file.path && fs.existsSync(file.path)) { |
|||
try { |
|||
fs.unlinkSync(file.path); |
|||
} catch { |
|||
/* ignore */ |
|||
} |
|||
} |
|||
throw err; |
|||
} |
|||
}); |
|||
@ -0,0 +1,83 @@ |
|||
import multer from "multer"; |
|||
import fs from "node:fs"; |
|||
import os from "node:os"; |
|||
import path from "node:path"; |
|||
import { callNodeListener } from "h3"; |
|||
import { POST_MEDIA_PUBLIC_PREFIX } from "#server/constants/media"; |
|||
import { replaceMediaAssetFileFromTempUpload } from "#server/service/media"; |
|||
import { R } from "#server/utils/response"; |
|||
|
|||
type MulterUploadedFile = { |
|||
originalname: string; |
|||
filename: string; |
|||
path: string; |
|||
mimetype: string; |
|||
size: number; |
|||
}; |
|||
|
|||
export default defineWrappedResponseHandler(async (event) => { |
|||
const user = await event.context.auth.requireUser(); |
|||
const idRaw = getRouterParam(event, "id"); |
|||
const assetId = Number(idRaw); |
|||
if (!Number.isInteger(assetId) || assetId < 1) { |
|||
throw createError({ statusCode: 400, statusMessage: "无效的资源 id" }); |
|||
} |
|||
|
|||
const upload = multer({ |
|||
storage: multer.diskStorage({ |
|||
destination: (_req, _file, cb) => { |
|||
cb(null, os.tmpdir()); |
|||
}, |
|||
filename: (_req, file, cb) => { |
|||
const ext = path.extname(file.originalname).toLowerCase(); |
|||
const baseName = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, "-"); |
|||
cb(null, `reupload-${Date.now()}-${Math.round(Math.random() * 1e9)}-${baseName}${ext}`); |
|||
}, |
|||
}), |
|||
limits: { fileSize: 10 * 1024 * 1024 }, |
|||
fileFilter: (_req, file, cb) => { |
|||
const allowed = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; |
|||
if (allowed.includes(file.mimetype)) { |
|||
cb(null, true); |
|||
} else { |
|||
cb(new Error("只支持 PNG/JPG/WebP 格式图片")); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
await callNodeListener( |
|||
// @ts-expect-error multer 中间件类型
|
|||
upload.single("file"), |
|||
event.node.req, |
|||
event.node.res, |
|||
); |
|||
|
|||
// @ts-expect-error multer 挂载 file
|
|||
const file = event.node.req.file as MulterUploadedFile | undefined; |
|||
if (!file?.path) { |
|||
throw createError({ statusCode: 400, statusMessage: "请选择要上传的图片" }); |
|||
} |
|||
|
|||
try { |
|||
const result = await replaceMediaAssetFileFromTempUpload({ |
|||
assetId, |
|||
actorUserId: user.id, |
|||
actorIsAdmin: false, |
|||
tempInputPath: file.path, |
|||
}); |
|||
return R.success({ |
|||
url: `${POST_MEDIA_PUBLIC_PREFIX}${result.storageKey}`, |
|||
sizeBytes: result.sizeBytes, |
|||
}); |
|||
} catch (err) { |
|||
if (file.path && fs.existsSync(file.path)) { |
|||
try { |
|||
fs.unlinkSync(file.path); |
|||
} catch { |
|||
/* ignore */ |
|||
} |
|||
} |
|||
throw err; |
|||
} |
|||
}); |
|||
|
|||
Loading…
Reference in new issue