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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user