feat: 플레이리스트 추가 화면 임시저장(draft) 기능

다운로드 시작 전, 수정해둔 값(플레이리스트 URL + 항목별 제목/ID/시작/길이/
ID모드/자릿수채우기)을 폴더당 1개 저장하고 나중에 불러올 수 있게 함.

- db: playlist_drafts 테이블 (folder_id PK, JSON data, updated_at)
- storeDb: savePlaylistDraft/getPlaylistDraft/deletePlaylistDraft
- op: GET/POST/POST(delete) /playlist/draft (하위 폴더 한정)
- UI: 임시저장 / 불러오기 / 삭제 버튼 + 진입 시 기존 draft 자동 감지

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Claude (owner)
2026-06-06 16:22:42 +09:00
parent 0ec6b06b57
commit 042d9f7059
6 changed files with 292 additions and 1 deletions

View File

@@ -22,7 +22,10 @@ import {
moveUploadIntoVideoDir,
renameFolder,
updateVideo,
videoExistsInFolder
videoExistsInFolder,
savePlaylistDraft,
getPlaylistDraft,
deletePlaylistDraft
} from '../storeDb.js'
import { tmpDir } from '../paths.js'
import {
@@ -266,6 +269,72 @@ opRouter.get(
}
)
// ─── 플레이리스트 임시저장(draft) ──────────────────────────────────────
// 다운로드 시작 전 수정해둔 값(URL + 항목별 제목/ID/구간)을 폴더당 1개 저장/복원.
/** 하위 폴더를 경로로 해석. 못 찾거나 최상위면 throw. */
function requireSubFolder(req: Request): { id: number } {
const folder = getFolderByPath(collectFolderSegments(req.params))
if (!folder) throw new Error('폴더를 찾을 수 없습니다.')
if (folder.parentId === null) {
throw new Error('플레이리스트는 하위 폴더에서만 사용할 수 있습니다.')
}
return folder
}
// 임시저장 JSON 크기 상한 (과도한 payload 방지). 2 MiB 면 수백 항목도 충분.
const DRAFT_MAX_CHARS = 2 * 1024 * 1024
opRouter.get(
'/op/folder/:topName/:subName/playlist/draft',
requireAuth,
(req, res) => {
try {
const folder = requireSubFolder(req)
const draft = getPlaylistDraft(folder.id)
res.json({ ok: true, draft })
} catch (err) {
res.status(400).json({ ok: false, message: (err as Error).message })
}
}
)
opRouter.post(
'/op/folder/:topName/:subName/playlist/draft',
requireAuth,
(req, res) => {
try {
const folder = requireSubFolder(req)
const data = req.body?.data
if (typeof data !== 'object' || data === null) {
throw new Error('저장할 데이터가 올바르지 않습니다.')
}
const json = JSON.stringify(data)
if (json.length > DRAFT_MAX_CHARS) {
throw new Error('임시저장 데이터가 너무 큽니다.')
}
const saved = savePlaylistDraft(folder.id, json)
res.json({ ok: true, updatedAt: saved.updatedAt })
} catch (err) {
res.status(400).json({ ok: false, message: (err as Error).message })
}
}
)
opRouter.post(
'/op/folder/:topName/:subName/playlist/draft/delete',
requireAuth,
(req, res) => {
try {
const folder = requireSubFolder(req)
deletePlaylistDraft(folder.id)
res.json({ ok: true })
} catch (err) {
res.status(400).json({ ok: false, message: (err as Error).message })
}
}
)
// ─── 영상 업로드 / 유튜브 ───────────────────────────────────────────────
function uploadSingle(fieldName: string) {