feat: npm run setup (yt-dlp + deps + build); lift upload size limit
scripts/setup.mjs runs `npm install`, downloads the platform-specific yt-dlp binary from GitHub releases to ./bin/yt-dlp (which src/youtube.ts already prefers), checks for ffmpeg and prints install hints, then runs `tsc`. One command replaces three for fresh checkouts. While verifying setup, hit `MulterError: File too large` (LIMIT_FILE_SIZE) on a 10 GB mkv upload, and ETXTBSY on freshly downloaded yt-dlp. - ETXTBSY: the redirect path in downloadFile opened a writestream to the destination before following the redirect, so the (unused) outer stream still held the file open when the post-download spawnSync ran. Split redirect-following from file writing so only the final 200 response opens the destination file. - LIMIT_FILE_SIZE: removed the hard-coded 4 GB cap. Upload limit now defaults to Infinity and is configurable via UPLOAD_MAX_BYTES. Wrapped multer's middleware so its errors (LIMIT_FILE_SIZE etc.) come back as a clean 413 JSON instead of a stack trace from the global error handler. - Also disabled Node's default 5 minute requestTimeout so 10 GB uploads over slow links don't get cut mid-stream. Configurable via HTTP_REQUEST_TIMEOUT_MS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Router } from 'express'
|
||||
import path from 'node:path'
|
||||
import multer from 'multer'
|
||||
import multer, { MulterError } from 'multer'
|
||||
import type { Request, Response, NextFunction } from 'express'
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { requireAuth } from '../auth.js'
|
||||
import {
|
||||
@@ -30,9 +31,13 @@ import { FfmpegUnavailableError, applyTrimToVideo } from '../editor.js'
|
||||
|
||||
export const opRouter = Router()
|
||||
|
||||
// 업로드 용량 상한은 환경변수 UPLOAD_MAX_BYTES 로 조정. 기본은 사실상 무제한(Infinity).
|
||||
const uploadMaxBytes = process.env.UPLOAD_MAX_BYTES
|
||||
? Math.max(1, Number(process.env.UPLOAD_MAX_BYTES))
|
||||
: Infinity
|
||||
const upload = multer({
|
||||
dest: tmpDir,
|
||||
limits: { fileSize: 4 * 1024 * 1024 * 1024 } // 4GB
|
||||
limits: { fileSize: uploadMaxBytes }
|
||||
})
|
||||
|
||||
function pickStr(v: unknown): string {
|
||||
@@ -181,11 +186,31 @@ opRouter.post('/op/folder/:name/video/delete', requireAuth, async (req, res) =>
|
||||
}
|
||||
})
|
||||
|
||||
// multer 가 던지는 LIMIT_FILE_SIZE 같은 에러를 라우트 핸들러가 잡지 못해
|
||||
// 글로벌 에러 핸들러로 새서 stack trace 가 그대로 노출되던 문제를 막는다.
|
||||
function uploadSingle(fieldName: string) {
|
||||
const mw = upload.single(fieldName)
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
mw(req, res, (err: unknown) => {
|
||||
if (!err) return next()
|
||||
if (err instanceof MulterError) {
|
||||
const message =
|
||||
err.code === 'LIMIT_FILE_SIZE'
|
||||
? `파일이 너무 큽니다. (한도: ${uploadMaxBytes === Infinity ? '무제한' : uploadMaxBytes + ' bytes'}; UPLOAD_MAX_BYTES 환경변수로 조정)`
|
||||
: `업로드 실패: ${err.message}`
|
||||
res.status(413).json({ ok: false, code: err.code, message })
|
||||
return
|
||||
}
|
||||
res.status(400).json({ ok: false, message: (err as Error).message || '업로드 실패' })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 업로드: 단일 파일. multipart/form-data, fields: title, file
|
||||
opRouter.post(
|
||||
'/op/folder/:name/video/upload',
|
||||
requireAuth,
|
||||
upload.single('file'),
|
||||
uploadSingle('file'),
|
||||
async (req, res) => {
|
||||
try {
|
||||
const safe = sanitizeFolderName(req.params.name)
|
||||
|
||||
Reference in New Issue
Block a user