# 素材上傳與發布流程

## 目標

- 管理者能上傳音檔、圖片、PDF。
- 素材不直接暴露在未驗證狀態。
- 發布時自動產生新的 `data/materials.json`。
- 失敗時不覆蓋線上版。

## 推薦檔案規格

### 音檔

- 格式：`mp3`、`m4a`
- 單檔大小：建議 `15 MB` 以下
- 命名：上傳後一律改成 UUID 或 hash 檔名

### 圖片

- 格式：`jpg`、`png`、`webp`
- 單檔大小：建議 `4 MB` 以下

### PDF

- 格式：`pdf`
- 單檔大小：建議 `25 MB` 以下

## 上傳流程

1. 管理者登入後台。
2. 在內容編輯器或素材庫選擇檔案。
3. 後端先驗證：
   - MIME type
   - 副檔名
   - 檔案大小
   - 是否含危險腳本
4. 通過後先存進暫存區。
5. 重新命名檔案並寫入 `assets` 資料表。
6. 若是音檔，可額外記錄時長與波形資訊。
7. 管理者將素材綁定到教材內容。

## 建議儲存路徑

- 暫存：`storage/uploads/tmp/{yyyy}/{mm}/`
- 音檔：`public/uploads/audio/{yyyy}/{mm}/`
- 圖片：`public/uploads/image/{yyyy}/{mm}/`
- PDF：`public/uploads/pdf/{yyyy}/{mm}/`

## 綁定教材流程

1. 建立或打開教材內容。
2. 從素材庫選擇已上傳的音檔或 PDF。
3. 儲存內容時只存素材 ID，不直接把路徑硬寫進 HTML。
4. 發布時再由後端把素材 URL 組裝進輸出的 JSON。

## 發布流程

1. 讀取所有 `status = published` 的主題與教材。
2. 依照主題指派表組合出：
   - `themes`
   - `memoryCards`
   - `repeatCards`
   - `recallCards`
   - `vocabLibrary`
   - `grammarLibrary`
   - `readingLibrary`
3. 將素材 URL 一起組進對應欄位，例如 `audioUrl`。
4. 驗證輸出 JSON：
   - 必要欄位是否齊全
   - 主題引用是否存在
   - JSON 是否可 parse
5. 先寫成臨時檔，例如 `materials.next.json`。
6. 計算 checksum 並寫入 `publish_snapshots`。
7. 驗證成功後，才覆蓋正式版 `data/materials.json`。

## 回滾流程

1. 從 `publish_snapshots` 找到指定版本。
2. 把該版本對應的 JSON 快照複製回 `data/materials.json`。
3. 寫入一筆新的 audit log，記錄回滾操作者與原因。

## 失敗保護

- 若 JSON 編譯失敗，不可覆蓋正式版。
- 若檔案寫入失敗，保留上一版線上資料。
- 若素材遺失或 URL 無效，發布應中止並回報是哪一筆內容出錯。

## 第一版可以先不做的事

- 背景轉檔隊列
- 自動語音辨識
- 音檔波形裁切器
- CDN 圖片最佳化

## 第一期最重要的品質門檻

- 新增一個複誦句並綁一個音檔後，可以從後台順利發布到前台。
- 發布前後，前台仍然只需要讀 `data/materials.json`。
- 管理者不需要手動改任何前台靜態檔案。
