terms: per-pack storage + import from another pack (v0.3.2)

- store.ts: 약관을 manifest/terms/<packKey>/ 폴더별로 저장. 첫 접근 시
  legacy 전역 .md 파일을 시드로 자동 복사한다.
- importTerms() 추가: 다른 음악퀴즈의 .md + _meta.json 을 현재 pack 으로
  복사한다. 동일 kind 는 source 값으로 덮어쓴다.
- /op/agreement 라우트를 세 단계로 분리:
  · /op/agreement → 음악퀴즈 카드 선택 페이지
  · /op/agreement/:packName → 해당 pack 의 약관 목록 + 추가 + 불러오기
  · /op/agreement/:packName/:kind → 에디터
- 공개 라우트도 /manifest/terms/:packKey/:fileName 으로 변경.
- 설치기 main.ts: state.selectedKey 를 약관 URL 에 포함하도록 수정 (메인 +
  rp 양쪽). pack 미선택 상태에서는 에러 반환.
- termsEditor.js: PACK_KEY 를 받아 저장 URL 에 포함.
- 다른 음악퀴즈 후보 select + 확인 모달 + locale 추가.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:29:04 +09:00
parent c14b0507c7
commit 25977d894b
11 changed files with 429 additions and 141 deletions

View File

@@ -64,15 +64,16 @@ app.get('/manifest.json', (_req, res) => {
})
// 설치기 + 사이트가 약관(markdown) 을 가져갈 수 있도록 .md 만 허용한다.
// 음악퀴즈(pack) 별로 manifest/terms/<packKey>/<file>.md 에서 노출한다.
// _meta.json 같은 시스템 파일이나 경로 탈출은 isPublicTermsFile 에서 차단.
app.get('/manifest/terms/:fileName', (req, res) => {
const fileName = req.params.fileName
if (!isPublicTermsFile(fileName)) {
app.get('/manifest/terms/:packKey/:fileName', (req, res) => {
const { packKey, fileName } = req.params
if (!isPublicTermsFile(packKey, fileName)) {
res.status(404).send('Not Found')
return
}
res.type('text/markdown; charset=utf-8')
res.sendFile(path.join(manifestTermsDirPath, fileName), (err) => {
res.sendFile(path.join(manifestTermsDirPath, packKey, fileName), (err) => {
if (!err || res.headersSent) return
res.status(404).send('Not Found')
})