feat: Add Rust API Server with REST and GraphQL support

- Implement JWT authentication layer
- Add Engine Registry with 20+ conversion engines
- Implement Conversion Service with background job processing
- REST API endpoints for file conversion operations
- GraphQL API with queries and mutations
- Comprehensive error handling with conversion suggestions
- Integration tests for both REST and GraphQL APIs
- Complete API documentation

Features:
- REST API: /api/v1/* endpoints
- GraphQL API: /graphql endpoint with playground
- JWT Bearer token authentication
- Engine validation with alternative suggestions
- File download via API (not exposing file paths)
- Support for FFmpeg, ImageMagick, LibreOffice, Pandoc, and more
This commit is contained in:
Your Name 2026-01-21 11:40:03 +08:00
parent d0388066a5
commit e083e5d11d
18 changed files with 5235 additions and 0 deletions

557
api-server/README.md Normal file
View file

@ -0,0 +1,557 @@
# ConvertX API Server
一個使用 Rust 實作的 REST 與 GraphQL 檔案轉換 API 伺服器。
## 🎯 功能特色
- **雙 API 支援**: 同時提供 REST API 和 GraphQL API兩者完全獨立運作
- **JWT 認證**: 所有 API 請求都需要 JWT Bearer Token 驗證
- **多引擎支援**: 整合 20+ 種轉換引擎FFmpeg、ImageMagick、LibreOffice 等)
- **智慧建議**: 當轉換不支援時,自動回傳可用的替代方案
- **完整測試**: 包含單元測試和 API 整合測試
## 🏗️ 系統架構
```
API Server
├─ Auth Layer (JWT 驗證)
├─ REST API (/api/v1/*)
├─ GraphQL API (/graphql)
└─ Conversion Service (Domain Logic)
└─ Engine Registry (轉換引擎管理)
```
### 設計原則
- **API Server 只負責**:
- 驗證 JWT
- 驗證請求是否合法
- 建立轉檔任務
- 呼叫轉檔引擎
- 回傳結果 / 錯誤 / 建議
- **轉換引擎必須明確指定**: 使用者必須指定要使用的引擎,系統不會自動選擇
## 🚀 快速開始
### 環境需求
- Rust 1.75+
- 對應的轉換工具FFmpeg、ImageMagick 等,視需求安裝)
### 安裝與執行
```bash
# 進入 API Server 目錄
cd api-server
# 編譯
cargo build --release
# 執行
cargo run --release
```
### 環境變數
| 變數 | 說明 | 預設值 |
|------|------|--------|
| `API_HOST` | 伺服器監聽地址 | `0.0.0.0` |
| `API_PORT` | 伺服器監聽埠 | `3001` |
| `JWT_SECRET` | JWT 驗證密鑰 | (預設值,正式環境請更改) |
| `UPLOAD_DIR` | 上傳檔案目錄 | `./data/uploads` |
| `OUTPUT_DIR` | 輸出檔案目錄 | `./data/output` |
| `MAX_FILE_SIZE` | 最大檔案大小bytes | `104857600` (100MB) |
### 範例 .env 檔案
```env
API_HOST=0.0.0.0
API_PORT=3001
JWT_SECRET=your-super-secret-jwt-key-change-in-production
UPLOAD_DIR=./data/uploads
OUTPUT_DIR=./data/output
MAX_FILE_SIZE=104857600
```
## 🔐 認證機制
所有 API 請求(除了健康檢查)都需要 JWT Bearer Token
```
Authorization: Bearer <your-jwt-token>
```
### JWT Claims 結構
```json
{
"sub": "user-id",
"exp": 1234567890,
"iat": 1234567890,
"email": "user@example.com",
"roles": ["user"]
}
```
**注意**: API Server 只負責驗證 JWT不負責產生 JWT。Token 應由獨立的認證服務產生。
## 📖 REST API
### 基礎 URL
```
http://localhost:3001/api/v1
```
### Endpoints
#### 健康檢查
```http
GET /health
GET /api/v1/health
```
回應:
```json
{
"status": "healthy",
"version": "0.1.0",
"timestamp": "2024-01-01T00:00:00Z"
}
```
#### 列出所有引擎
```http
GET /api/v1/engines
Authorization: Bearer <token>
```
回應:
```json
{
"engines": [
{
"id": "ffmpeg",
"name": "FFmpeg",
"description": "Audio and video conversion using FFmpeg",
"supported_input_formats": ["mp4", "webm", "avi", ...],
"supported_output_formats": ["mp4", "webm", "mp3", ...]
}
]
}
```
#### 取得特定引擎資訊
```http
GET /api/v1/engines/:engine_id
Authorization: Bearer <token>
```
#### 建立轉檔任務
```http
POST /api/v1/convert
Authorization: Bearer <token>
Content-Type: multipart/form-data
```
表單欄位:
- `file`: 要轉換的檔案(必填)
- `engine`: 轉換引擎 ID必填
- `target_format`: 目標格式(必填)
- `options`: JSON 格式的選項(選填)
回應:
```json
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"message": "Conversion job created successfully"
}
```
#### 列出使用者的任務
```http
GET /api/v1/jobs
Authorization: Bearer <token>
```
#### 取得任務狀態
```http
GET /api/v1/jobs/:job_id
Authorization: Bearer <token>
```
回應:
```json
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"original_filename": "video.mp4",
"source_format": "mp4",
"target_format": "webm",
"engine": "ffmpeg",
"download_url": "/api/v1/jobs/550e8400-.../download",
"created_at": "2024-01-01T00:00:00Z",
"completed_at": "2024-01-01T00:01:00Z"
}
```
#### 下載轉換結果
```http
GET /api/v1/jobs/:job_id/download
Authorization: Bearer <token>
```
#### 刪除任務
```http
DELETE /api/v1/jobs/:job_id
Authorization: Bearer <token>
```
### cURL 範例
```bash
# 健康檢查
curl http://localhost:3001/health
# 列出引擎
curl -H "Authorization: Bearer $TOKEN" http://localhost:3001/api/v1/engines
# 建立轉檔任務
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-F "file=@video.mp4" \
-F "engine=ffmpeg" \
-F "target_format=webm" \
http://localhost:3001/api/v1/convert
# 查詢任務狀態
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3001/api/v1/jobs/$JOB_ID
# 下載結果
curl -H "Authorization: Bearer $TOKEN" \
-o result.webm \
http://localhost:3001/api/v1/jobs/$JOB_ID/download
```
## 📊 GraphQL API
### Endpoint
```
http://localhost:3001/graphql
```
GraphQL Playground 可透過瀏覽器訪問 `http://localhost:3001/graphql`
### Schema
#### Queries
```graphql
type Query {
# 健康檢查(不需認證)
health: Health!
# 列出所有引擎
engines: [Engine!]!
# 取得特定引擎
engine(id: ID!): Engine
# 列出使用者的任務
jobs: [Job!]!
# 取得特定任務
job(id: ID!): Job
# 驗證轉換是否支援
validateConversion(engine: String!, from: String!, to: String!): CreateJobResult!
# 取得轉換建議
suggestions(from: String!, to: String!): [Suggestion!]!
}
```
#### Mutations
```graphql
type Mutation {
# 建立轉檔任務
createJob(
filename: String!
fileBase64: String!
input: CreateJobInput!
): CreateJobResult!
# 刪除任務
deleteJob(id: ID!): Boolean!
}
input CreateJobInput {
engine: String!
targetFormat: String!
options: String
}
```
#### Types
```graphql
type Engine {
id: ID!
name: String!
description: String!
supportedInputFormats: [String!]!
supportedOutputFormats: [String!]!
}
type Job {
id: ID!
originalFilename: String!
sourceFormat: String!
targetFormat: String!
engine: String!
status: JobStatus!
outputFilename: String
errorMessage: String
downloadUrl: String
createdAt: DateTime!
completedAt: DateTime
}
enum JobStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}
type Suggestion {
engine: String!
from: String!
to: String!
}
type CreateJobResult {
success: Boolean!
job: Job
error: ConversionError
}
type ConversionError {
code: String!
message: String!
suggestions: [Suggestion!]!
}
```
### GraphQL 範例
```graphql
# 列出所有引擎
query {
engines {
id
name
supportedInputFormats
supportedOutputFormats
}
}
# 驗證轉換是否支援
query {
validateConversion(engine: "ffmpeg", from: "mp4", to: "webm") {
success
error {
code
message
suggestions {
engine
from
to
}
}
}
}
# 建立轉檔任務
mutation {
createJob(
filename: "video.mp4"
fileBase64: "base64-encoded-content"
input: {
engine: "ffmpeg"
targetFormat: "webm"
}
) {
success
job {
id
status
}
error {
code
message
suggestions {
engine
from
to
}
}
}
}
# 查詢任務
query {
jobs {
id
status
originalFilename
downloadUrl
}
}
```
## ❌ 錯誤處理
### 錯誤回應格式
```json
{
"error": {
"code": "UNSUPPORTED_CONVERSION",
"message": "Conversion from pdf to mp4 is not supported by engine ffmpeg",
"suggestions": [
{
"engine": "libreoffice",
"from": "pdf",
"to": "docx"
},
{
"engine": "calibre",
"from": "pdf",
"to": "epub"
}
]
}
}
```
### 錯誤碼
| 錯誤碼 | HTTP 狀態 | 說明 |
|--------|-----------|------|
| `UNAUTHORIZED` | 401 | 未授權 |
| `INVALID_TOKEN` | 401 | Token 格式或簽名無效 |
| `TOKEN_EXPIRED` | 401 | Token 已過期 |
| `MISSING_AUTH_HEADER` | 401 | 缺少 Authorization 標頭 |
| `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 | 內部錯誤 |
## 📦 轉換結果策略
本 API Server 採用 **選項 B結果僅透過 API 提供下載**
### 設計理由
1. **安全性**: 不暴露實體檔案路徑,避免路徑遍歷攻擊
2. **權限控制**: 下載時驗證 JWT確保只有任務擁有者能下載
3. **彈性部署**: 適合雲端環境,可輕易整合 CDN 或物件儲存
4. **清理管理**: 方便實作自動清理過期檔案的機制
### 檔案儲存
內部儲存結構:
```
data/
├── uploads/
│ └── <job_id>/
│ └── <original_filename>
└── output/
└── <job_id>/
└── <converted_filename>
```
使用者透過 `/api/v1/jobs/{job_id}/download` 下載API 會驗證權限後串流檔案。
## 🔧 支援的轉換引擎
| 引擎 ID | 名稱 | 說明 |
|---------|------|------|
| `ffmpeg` | FFmpeg | 音視頻轉換 |
| `imagemagick` | ImageMagick | 圖片格式轉換 |
| `graphicsmagick` | GraphicsMagick | 圖片格式轉換(替代方案) |
| `libreoffice` | LibreOffice | 辦公文件轉換 |
| `pandoc` | Pandoc | 文件/標記語言轉換 |
| `calibre` | Calibre | 電子書轉換 |
| `inkscape` | Inkscape | 向量圖轉換 |
| `resvg` | resvg | SVG 渲染 |
| `vips` | libvips | 高效能圖片處理 |
| `libheif` | libheif | HEIF/HEIC 轉換 |
| `libjxl` | libjxl | JPEG XL 轉換 |
| `potrace` | Potrace | 點陣圖轉向量 |
| `vtracer` | VTracer | 進階向量化 |
| `dasel` | Dasel | 資料格式轉換 |
| `assimp` | Assimp | 3D 模型轉換 |
| `xelatex` | XeLaTeX | LaTeX 編譯 |
| `dvisvgm` | dvisvgm | DVI 轉 SVG |
| `msgconvert` | msgconvert | Outlook MSG 轉 EML |
| `vcf` | VCF Converter | vCard 轉換 |
| `markitdown` | MarkItDown | 文件轉 Markdown |
## 🧪 測試
```bash
# 執行所有測試
cargo test
# 執行特定測試
cargo test auth_tests
cargo test graphql_tests
# 顯示測試輸出
cargo test -- --nocapture
```
## 📁 專案結構
```
api-server/
├── Cargo.toml
├── src/
│ ├── main.rs # 程式入口
│ ├── lib.rs # 函式庫模組
│ ├── config.rs # 設定管理
│ ├── auth.rs # JWT 認證
│ ├── error.rs # 錯誤處理
│ ├── models.rs # 資料模型
│ ├── engine.rs # 引擎註冊
│ ├── conversion.rs # 轉換服務
│ ├── rest.rs # REST API
│ └── graphql.rs # GraphQL API
└── tests/
├── api_tests.rs # REST API 測試
└── graphql_tests.rs # GraphQL 測試
```
## 📄 授權
MIT License