Commit Graph

9 Commits

Author SHA1 Message Date
Claude
3089af1ad8 fix(folders): enforce videos-only at depth 2 (P1 review fix)
- routes/op.ts: editor / upload / youtube-start reject when folder.parentId === null
- op/folder.ejs: at depth 1 show only "하위 폴더 추가"; at depth 2 show
  "영상 추가" + "플레이리스트 추가". Video grid and sub-folder modal
  rendered conditionally.
- folder.ejs (public): at depth 1 render only sub-folder grid; at depth 2
  render only video grid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:43:24 +09:00
Claude
c5faa8808c feat(folders): nested folder tree (max depth 2) + id-based mutations
- new storeDb.ts: DB+FS layer (folders w/ parent_id, videos w/ global id)
- depth cap: sub-folder cannot have children (enforced in createFolder)
- video id: editable anytime (changeVideoId), uniqueness via PK
- routes:
  - public /folder/:topName[/:subName], /video/:topName[/:subName]/:videoId
  - admin /op/folder/:topName[/:subName], id-based /op/folders/:id/*
    and /op/videos/:id/* (rename/changeId/delete/save + idAvailable check)
- views: breadcrumb + subFolder grid; 플레이리스트 추가 button shows at 2단계
- client JS: id-based mutation endpoints, share URL data attribute,
  in-site player pushes share URL to address bar
- store.ts slimmed to Account + readAccounts (FS layer moved to storeDb)
- pickIntId accepts numeric JSON bodies

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:36:02 +09:00
Claude
67d4fb89b8 fix(job-poll): disable caching and recover from fetch errors
Review P1: 진행률 폴링이 304 응답을 받으면 r.json() 이 reject 되는데
.catch() 가 없어 setTimeout 도 안 걸리고 폴링이 영구 중단됐습니다.
이게 "60fps 변환 확인 중" 에서 바가 멈춰 보이던 진짜 원인이었어요.

세 곳을 다 고침:

1) src/routes/op.ts `/op/job/:id`
   - `Cache-Control: no-store, no-cache, must-revalidate` + `Pragma: no-cache`
   - 브라우저가 conditional GET 으로 304 를 받지 않게 한다.

2) public/editor.js fetch
   - `{ cache: 'no-store' }` 옵션. 서버 헤더 + 클라 옵션 둘 다.

3) public/editor.js pollJob
   - `.catch()` 추가. 일시적 네트워크/파싱 오류여도 2 초 백오프로
     폴링을 재개한다. 변환이 오래 걸려도 바가 계속 갱신됨을 보장.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 03:00:58 +09:00
59f96a12a6 feat(routes): add short /file/video/:id alias for video stream
외부 공유용으로 더 짧은 영상 파일 URL 을 제공:
  /file/video/:videoId   ← 신규 (영상 주소 복사 결과)
  /api/video/:videoId/file ← 호환용 alias 로 유지 (기존 링크 안 깨지게)
  /api/video/:videoId     ← 메타 JSON (변경 없음)

핸들러는 한 함수를 두 라우트에 공유. 우클릭 "영상 주소 복사" 가
이제 새 짧은 경로를 클립보드에 넣음.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 21:58:37 +09:00
aae58f645a fix: harden UPLOAD_MAX_BYTES parsing; sync docs with 1 GiB default
리뷰어 지적사항 반영:
- "abc" 같은 오타도 Infinity 로 풀리던 문제 수정. 잘못된 값은 기본 1 GiB 로
  fallback 하고 경고 로그를 남김. 무제한은 "0" 또는 "Infinity" 만 명시적으로 인정.
- .env.example / README 의 "비우면 무제한" 표현을 코드 동작과 일치시켜
  "비우면 기본 1 GiB" 로 정정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:10:27 +09:00
a14963e237 feat: cap upload size at 1 GiB by default (UPLOAD_MAX_BYTES override)
기본 업로드 한도를 1 GiB (1073741824 바이트) 로 설정. .env 의 UPLOAD_MAX_BYTES 로
바꿀 수 있고, 0 이나 Infinity 로 두면 무제한.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 18:06:21 +09:00
cb9406d88e 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>
2026-05-15 17:13:47 +09:00
3f4bcf52d0 fix: escape JSON in <script>; popup player picks edited if available
P1: views were emitting <%- JSON.stringify(...) %> directly inside <script>
tags. A video title like "</script><script>alert(1)</script>" would break
out of the script and inject HTML. Added res.locals.jsonForScript() that
escapes <, >, &, U+2028, U+2029 before output and switched all three
templates (op/editor.ejs, op/folder.ejs, folder.ejs) to use it.

P2: The internal popup player in /folder/:name always hit
/api/video/:id/file which returned the original. Made the file endpoint
default to the edited variant when present and only fall back to original
when ?edited=0 is given. Editor page passes ?edited=0 explicitly so the
operator always re-trims from the original. Standalone /player/:id no
longer needs the ?edited=1 hint.

Verified: rendered editor HTML escapes </script> payloads to \u003c/script,
default file endpoint serves edited.mp4 while ?edited=0 serves original.mp4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:49:03 +09:00
0db04cf5cd feat: implement video site per README spec
- Express + EJS + express-session stack (auth/navbar ported from minecraft_launcher)
- Public: main folder list, folder video grid, internal popup player (/player/:videoId)
- Admin (/op): login, folder CRUD with right-click context menu + add-folder modal
- Admin folder: video grid with right-click edit/rename/delete, "영상 추가" -> editor
- Video editor: drag-drop upload, file picker, YouTube URL probe (ETA + 5분 경고),
  background yt-dlp download with progress polling, navbar title edit, trim controls,
  save runs ffmpeg trim (original preserved)
- Filesystem storage under data/folders/<name>/<videoId>/{meta.json, original.<ext>, edited.<ext>}

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:42:00 +09:00