feat(api): Docker Compose 整合與補充文件 - Docker Compose profiles 支援選用 API Server - API Server Dockerfile (多階段建置) - .env.api.example 環境變數範本 - integration_tests.rs 完整整合測試 - health_check.sh 健康檢查腳本 - 主專案 README 新增 API Server 說明區塊

This commit is contained in:
Your Name 2026-01-21 11:50:11 +08:00
parent e083e5d11d
commit e577658231
9 changed files with 1269 additions and 144 deletions

View file

@ -27,22 +27,22 @@ Authorization: Bearer <jwt-token>
}
```
| 欄位 | 必填 | 說明 |
|------|------|------|
| `sub` | ✓ | 使用者唯一識別碼 |
| `exp` | ✓ | Token 過期時間Unix timestamp |
| `iat` | ✓ | Token 簽發時間Unix timestamp |
| `email` | - | 使用者 Email |
| `roles` | - | 使用者角色列表 |
| 欄位 | 必填 | 說明 |
| ------- | ---- | -------------------------------- |
| `sub` | ✓ | 使用者唯一識別碼 |
| `exp` | ✓ | Token 過期時間Unix timestamp |
| `iat` | ✓ | Token 簽發時間Unix timestamp |
| `email` | - | 使用者 Email |
| `roles` | - | 使用者角色列表 |
### 認證錯誤回應
| 狀況 | 錯誤碼 | HTTP 狀態 |
|------|--------|-----------|
| 缺少 Authorization Header | `MISSING_AUTH_HEADER` | 401 |
| Token 格式錯誤 | `INVALID_TOKEN` | 401 |
| Token 簽名無效 | `INVALID_TOKEN` | 401 |
| Token 已過期 | `TOKEN_EXPIRED` | 401 |
| 狀況 | 錯誤碼 | HTTP 狀態 |
| ------------------------- | --------------------- | --------- |
| 缺少 Authorization Header | `MISSING_AUTH_HEADER` | 401 |
| Token 格式錯誤 | `INVALID_TOKEN` | 401 |
| Token 簽名無效 | `INVALID_TOKEN` | 401 |
| Token 已過期 | `TOKEN_EXPIRED` | 401 |
---
@ -80,6 +80,7 @@ Authorization: Bearer <jwt-token>
列出所有可用的轉換引擎。
**Headers**
```
Authorization: Bearer <token>
```
@ -93,8 +94,33 @@ Authorization: Bearer <token>
"id": "ffmpeg",
"name": "FFmpeg",
"description": "Audio and video conversion using FFmpeg",
"supported_input_formats": ["mp4", "webm", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "m4a", "gif"],
"supported_output_formats": ["webm", "avi", "mkv", "mov", "mp3", "wav", "flac", "ogg", "gif", "m4a", "aac", "mp4"]
"supported_input_formats": [
"mp4",
"webm",
"avi",
"mkv",
"mov",
"mp3",
"wav",
"flac",
"ogg",
"m4a",
"gif"
],
"supported_output_formats": [
"webm",
"avi",
"mkv",
"mov",
"mp3",
"wav",
"flac",
"ogg",
"gif",
"m4a",
"aac",
"mp4"
]
},
{
"id": "imagemagick",
@ -114,6 +140,7 @@ Authorization: Bearer <token>
取得特定引擎的詳細資訊。
**參數**
- `engine_id`: 引擎識別碼(如 `ffmpeg`, `imagemagick`
**回應 (200)**
@ -167,6 +194,7 @@ Authorization: Bearer <token>
建立新的轉檔任務。
**Headers**
```
Authorization: Bearer <token>
Content-Type: multipart/form-data
@ -174,12 +202,12 @@ Content-Type: multipart/form-data
**表單欄位**
| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `file` | File | ✓ | 要轉換的檔案 |
| `engine` | String | ✓ | 轉換引擎 ID |
| `target_format` | String | ✓ | 目標格式(不含點) |
| `options` | String | - | JSON 格式的轉換選項 |
| 欄位 | 類型 | 必填 | 說明 |
| --------------- | ------ | ---- | ------------------- |
| `file` | File | ✓ | 要轉換的檔案 |
| `engine` | String | ✓ | 轉換引擎 ID |
| `target_format` | String | ✓ | 目標格式(不含點) |
| `options` | String | - | JSON 格式的轉換選項 |
**回應 (201)**
@ -254,12 +282,12 @@ Content-Type: multipart/form-data
**任務狀態**
| 狀態 | 說明 |
|------|------|
| `pending` | 等待處理 |
| `processing` | 處理中 |
| `completed` | 完成 |
| `failed` | 失敗 |
| 狀態 | 說明 |
| ------------ | -------- |
| `pending` | 等待處理 |
| `processing` | 處理中 |
| `completed` | 完成 |
| `failed` | 失敗 |
**回應 (200) - 完成**
@ -300,10 +328,12 @@ Content-Type: multipart/form-data
下載轉換後的檔案。
**前提條件**
- 任務狀態必須是 `completed`
- 只有任務建立者可以下載
**回應 Headers**
```
Content-Type: <mime-type>
Content-Disposition: attachment; filename="output.webm"
@ -311,12 +341,12 @@ Content-Disposition: attachment; filename="output.webm"
**錯誤回應**
| 狀況 | 錯誤碼 | HTTP 狀態 |
|------|--------|-----------|
| 任務未完成 | `BAD_REQUEST` | 400 |
| 無權限 | `UNAUTHORIZED` | 401 |
| 任務不存在 | `JOB_NOT_FOUND` | 404 |
| 檔案遺失 | `FILE_NOT_FOUND` | 404 |
| 狀況 | 錯誤碼 | HTTP 狀態 |
| ---------- | ---------------- | --------- |
| 任務未完成 | `BAD_REQUEST` | 400 |
| 無權限 | `UNAUTHORIZED` | 401 |
| 任務不存在 | `JOB_NOT_FOUND` | 404 |
| 檔案遺失 | `FILE_NOT_FOUND` | 404 |
---
@ -351,56 +381,72 @@ GraphQL Playground 可透過 GET 請求訪問同一 URL。
# ========== Queries ==========
type Query {
"""健康檢查(不需認證)"""
"""
健康檢查(不需認證)
"""
health: Health!
"""列出所有可用引擎"""
"""
列出所有可用引擎
"""
engines: [Engine!]!
"""取得特定引擎"""
"""
取得特定引擎
"""
engine(id: ID!): Engine
"""列出當前使用者的所有任務"""
"""
列出當前使用者的所有任務
"""
jobs: [Job!]!
"""取得特定任務"""
"""
取得特定任務
"""
job(id: ID!): Job
"""驗證轉換是否支援"""
validateConversion(
engine: String!
from: String!
to: String!
): CreateJobResult!
"""取得轉換建議"""
"""
驗證轉換是否支援
"""
validateConversion(engine: String!, from: String!, to: String!): CreateJobResult!
"""
取得轉換建議
"""
suggestions(from: String!, to: String!): [Suggestion!]!
}
# ========== Mutations ==========
type Mutation {
"""建立轉檔任務"""
createJob(
filename: String!
fileBase64: String!
input: CreateJobInput!
): CreateJobResult!
"""刪除任務"""
"""
建立轉檔任務
"""
createJob(filename: String!, fileBase64: String!, input: CreateJobInput!): CreateJobResult!
"""
刪除任務
"""
deleteJob(id: ID!): Boolean!
}
# ========== Input Types ==========
input CreateJobInput {
"""轉換引擎 ID"""
"""
轉換引擎 ID
"""
engine: String!
"""目標格式"""
"""
目標格式
"""
targetFormat: String!
"""轉換選項JSON 字串)"""
"""
轉換選項JSON 字串)
"""
options: String
}
@ -520,10 +566,7 @@ mutation CreateConversionJob($filename: String!, $content: String!) {
createJob(
filename: $filename
fileBase64: $content
input: {
engine: "imagemagick"
targetFormat: "jpg"
}
input: { engine: "imagemagick", targetFormat: "jpg" }
) {
success
job {
@ -545,6 +588,7 @@ mutation CreateConversionJob($filename: String!, $content: String!) {
```
Variables:
```json
{
"filename": "image.png",
@ -597,18 +641,18 @@ mutation {
## 錯誤碼對照表
| 錯誤碼 | HTTP 狀態 | 說明 | 包含建議 |
|--------|-----------|------|----------|
| `UNAUTHORIZED` | 401 | 未授權存取 | ✗ |
| `INVALID_TOKEN` | 401 | Token 無效 | ✗ |
| `TOKEN_EXPIRED` | 401 | Token 已過期 | ✗ |
| `MISSING_AUTH_HEADER` | 401 | 缺少認證標頭 | ✗ |
| `BAD_REQUEST` | 400 | 請求格式錯誤 | ✗ |
| `INVALID_FILE` | 400 | 無法辨識檔案格式 | ✗ |
| `FILE_TOO_LARGE` | 400 | 檔案超過大小限制 | ✗ |
| `ENGINE_NOT_FOUND` | 404 | 引擎不存在 | ✗ |
| `JOB_NOT_FOUND` | 404 | 任務不存在 | ✗ |
| `FILE_NOT_FOUND` | 404 | 檔案不存在 | ✗ |
| `UNSUPPORTED_CONVERSION` | 422 | 不支援的轉換 | ✓ |
| `CONVERSION_FAILED` | 500 | 轉換失敗 | ✗ |
| `INTERNAL_ERROR` | 500 | 內部錯誤 | ✗ |
| 錯誤碼 | HTTP 狀態 | 說明 | 包含建議 |
| ------------------------ | --------- | ---------------- | -------- |
| `UNAUTHORIZED` | 401 | 未授權存取 | ✗ |
| `INVALID_TOKEN` | 401 | Token 無效 | ✗ |
| `TOKEN_EXPIRED` | 401 | Token 已過期 | ✗ |
| `MISSING_AUTH_HEADER` | 401 | 缺少認證標頭 | ✗ |
| `BAD_REQUEST` | 400 | 請求格式錯誤 | ✗ |
| `INVALID_FILE` | 400 | 無法辨識檔案格式 | ✗ |
| `FILE_TOO_LARGE` | 400 | 檔案超過大小限制 | ✗ |
| `ENGINE_NOT_FOUND` | 404 | 引擎不存在 | ✗ |
| `JOB_NOT_FOUND` | 404 | 任務不存在 | ✗ |
| `FILE_NOT_FOUND` | 404 | 檔案不存在 | ✗ |
| `UNSUPPORTED_CONVERSION` | 422 | 不支援的轉換 | ✓ |
| `CONVERSION_FAILED` | 500 | 轉換失敗 | ✗ |
| `INTERNAL_ERROR` | 500 | 內部錯誤 | ✗ |

View file

@ -59,12 +59,14 @@
負責所有請求的 JWT 驗證。
**職責**
- 解析 Authorization Header
- 驗證 JWT Token 簽名
- 檢查 Token 是否過期
- 提取使用者資訊
**不負責**
- 產生 JWT Token由外部認證服務處理
- 使用者管理
@ -73,6 +75,7 @@
提供 RESTful HTTP 端點。
**端點**
- `GET /health` - 健康檢查
- `GET /api/v1/engines` - 列出引擎
- `POST /api/v1/convert` - 建立轉檔任務
@ -86,6 +89,7 @@
提供 GraphQL 查詢與變更。
**Queries**
- `health` - 健康檢查
- `engines` - 列出引擎
- `engine(id)` - 取得特定引擎
@ -95,6 +99,7 @@
- `suggestions` - 取得建議
**Mutations**
- `createJob` - 建立任務
- `deleteJob` - 刪除任務
@ -103,11 +108,13 @@
管理所有可用的轉換引擎及其能力。
**職責**
- 註冊引擎及其支援的格式
- 驗證轉換是否可行
- 提供替代方案建議
**設計原則**
- 引擎必須明確指定,不自動選擇
- 不支援的轉換必須回傳建議
@ -116,6 +123,7 @@
處理轉檔任務的核心邏輯。
**職責**
- 建立和管理轉檔任務
- 儲存上傳的檔案
- 呼叫外部轉換程式
@ -126,6 +134,7 @@
統一的錯誤處理機制。
**特色**
- 結構化的錯誤回應
- 錯誤碼分類
- 轉換建議整合