chore: remove obsolete APIs (register, probe, n8n, videos, people)
- Remove /api/v1/register (replaced by /api/v1/files/register) - Remove /api/v1/probe (replaced by /api/v1/files/:uuid) - Remove /api/v1/n8n/... (n8n workflow only) - Remove /api/v1/unregister (high risk) - Remove /api/v1/videos list (replaced by /api/v1/files) - Remove /api/v1/people (merged into /api/v1/identities) - Clean up dead code and unused structs
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2340,7 +2340,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "momentry_core"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "momentry_core"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
authors = ["Momentry Team"]
|
||||
description = "Digital asset management system with video analysis and RAG"
|
||||
|
||||
464
docs_v1.0/DEV_API_V1.0/API_REFERENCE_v1.0.0 draft.md
Normal file
464
docs_v1.0/DEV_API_V1.0/API_REFERENCE_v1.0.0 draft.md
Normal file
@@ -0,0 +1,464 @@
|
||||
# Momentry Core API Documentation v1.0.0
|
||||
|
||||
## 快速資訊
|
||||
- **Base URL**: `http://<host>:3003` (開發環境)
|
||||
- **Production**: `http://<host>:3002`
|
||||
- **認證方式**: Header `X-API-Key: <your_api_key>`
|
||||
- **測試 Key**: `muser_test_001`
|
||||
|
||||
---
|
||||
|
||||
## API 分類原則
|
||||
|
||||
| 分類 | 用途 | 代表端點 |
|
||||
|------|------|----------|
|
||||
| **健康檢查** | 系統狀態確認 | `/health` |
|
||||
| **檔案管理 (Files)** | 列出、查詢檔案 | `/api/v1/files` |
|
||||
| **人物管理 (People)** | Identity 搜尋、候選 | `/api/v1/people` |
|
||||
| **Identity 管理** | 人物詳細資訊 | `/api/v1/identities/:uuid` |
|
||||
| **搜尋 (Search)** | 文字、BM25、混合搜尋 | `/api/v1/search/*` |
|
||||
| **人臉 (Face)** | 人臉辨識、列表 | `/api/v1/face/*` |
|
||||
| **任務 (Jobs)** | 處理任務狀態 | `/api/v1/jobs` |
|
||||
| **統計 (Stats)** | 系統統計 | `/api/v1/stats/ingest` |
|
||||
|
||||
### 設計概念
|
||||
1. **Files 是核心資源**:所有影片/圖片都是 File,用 32 碼 `file_uuid` 識別
|
||||
2. **Identity 是跨檔案的人物**:一個 Identity 可出現在多個 Files
|
||||
3. **People = Identity 的別名路由**:`/api/v1/people` 和 `/api/v1/identities` 指向相同資料
|
||||
4. **所有列表 API 支援分頁**:`page`, `page_size` 參數
|
||||
5. **所有操作 API 回傳統一格式**:`{ success, data, total, page, page_size }`
|
||||
|
||||
---
|
||||
|
||||
## 1. 健康檢查
|
||||
|
||||
### `GET /health`
|
||||
檢查系統是否運作。
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0 (build: 2026-04-30 15:40:04)",
|
||||
"uptime_ms": 348240
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 檔案管理 (Files)
|
||||
|
||||
> **測試日期**: 2026-04-30 | **環境**: Playground (Port 3003) | **Schema**: dev
|
||||
|
||||
### `GET /api/v1/files` — 列出所有檔案
|
||||
|
||||
**參數**: `page` (預設 1), `page_size` (預設 20)
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files?page=1&page_size=2" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 21,
|
||||
"page": 1,
|
||||
"page_size": 2,
|
||||
"data": [
|
||||
{
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"status": "ready"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/:uuid` — 取得檔案詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/384b0ff44aaaa1f14cb2cd63b3fea966" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"metadata": {
|
||||
"format": {
|
||||
"duration": "6879.329524",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"streams": [
|
||||
{
|
||||
"codec_name": "h264",
|
||||
"codec_type": "video",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"r_frame_rate": "60000/1001"
|
||||
},
|
||||
{
|
||||
"codec_name": "aac",
|
||||
"codec_type": "audio",
|
||||
"sample_rate": "44100",
|
||||
"channels": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/scan` — 掃描檔案系統
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/scan" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_size": 2361629896,
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"status": "pending",
|
||||
"is_registered": true,
|
||||
"registration_time": "2026-04-28 18:25:14.010351+00"
|
||||
}
|
||||
],
|
||||
"total": 20,
|
||||
"registered_count": 20,
|
||||
"unregistered_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/files/register` — 註冊新檔案
|
||||
|
||||
> **⚠️ 已知問題**: 此 endpoint 在 dev 環境有 SQLx 型別綁定問題 (probe_json text vs jsonb)。不影響 marcom 團隊的 GUI 設計。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"file_path": "/path/to/video.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 人物管理 (People)
|
||||
|
||||
### `GET /api/v1/people` — 列出所有人物
|
||||
|
||||
**參數**: `page`, `page_size`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/people?page=1&page_size=3" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"data": [
|
||||
{
|
||||
"identity_id": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"metadata": {},
|
||||
"created_at": "2026-04-28T06:10:14.582062Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/people/search` — 搜尋人物
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "Trace",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:3003/api/v1/people/search" \
|
||||
-H "X-API-Key: muser_test_001" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query":"Trace","limit":3}'
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 3,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"data": [
|
||||
{
|
||||
"identity_id": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"metadata": {},
|
||||
"created_at": "2026-04-28T06:10:14.582062Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/people/candidates` — 列出未命名人物候選
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/people/candidates" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Identity 詳細資訊
|
||||
|
||||
### `GET /api/v1/identities/:uuid` — 取得人物詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/a9a90105-6d6b-46ff-92da-0c3c1a57dff4" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"uuid": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"identity_type": "people",
|
||||
"source": "auto_trace",
|
||||
"status": "active",
|
||||
"metadata": {},
|
||||
"reference_data": {
|
||||
"trace_id": 2,
|
||||
"quality_avg": 0.8748,
|
||||
"trace_stats": {
|
||||
"start_frame": 155,
|
||||
"end_frame": 297,
|
||||
"avg_confidence": 0.8624,
|
||||
"duration_seconds": 6.5,
|
||||
"total_appearances": 143
|
||||
},
|
||||
"angles_covered": ["profile_right", "three_quarter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/identities/:uuid/files` — 取得人物出現的檔案列表
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/files" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 搜尋 (Search)
|
||||
|
||||
### `POST /api/v1/search` — 向量搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "person talking",
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"results": [],
|
||||
"query": "person talking"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/bm25` — BM25 全文搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "test",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"uuid": "a1b10138a6bbb0cd",
|
||||
"chunk_id": "sentence_1006",
|
||||
"text": "...",
|
||||
"bm25_score": 5.23
|
||||
}
|
||||
],
|
||||
"query": "test"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/hybrid` — 混合搜尋 (向量 + BM25)
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "test",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 人臉 (Face)
|
||||
|
||||
### `GET /api/v1/face/list` — 列出人臉
|
||||
|
||||
**必填參數**: `file_uuid`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/face/list?file_uuid=384b0ff44aaaa1f14cb2cd63b3fea966&page=1&page_size=3" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Found 2 faces",
|
||||
"faces": [
|
||||
{
|
||||
"face_id": "identity_8d7c22e3a6794a8c93b4a3655f106944",
|
||||
"name": "Cary Grant",
|
||||
"created_at": "2026-04-18T10:44:48.571407+00:00",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"face_id": "identity_9c5d1e8965eb49ae83d9b88db12fbb18",
|
||||
"name": "Audrey Hepburn",
|
||||
"created_at": "2026-04-18T10:44:31.536039+00:00",
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"page": 1,
|
||||
"page_size": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 任務 (Jobs)
|
||||
|
||||
### `GET /api/v1/jobs` — 列出處理任務
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/jobs?page=1&page_size=2" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"id": 32,
|
||||
"uuid": "942d0bdf5d6fb6ac18b47deb031e60c3",
|
||||
"status": "running",
|
||||
"current_processor": null,
|
||||
"progress_current": 0,
|
||||
"progress_total": 0,
|
||||
"created_at": "2026-04-28 15:55:47.911654",
|
||||
"started_at": null
|
||||
}
|
||||
],
|
||||
"count": 19,
|
||||
"page": 1,
|
||||
"page_size": 2
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/rules/:rule/status` — 取得規則狀態
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/rules/default/status" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"rule": "default",
|
||||
"supported_processor_ids": [],
|
||||
"active_jobs": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 統計 (Stats)
|
||||
|
||||
### `GET /api/v1/stats/ingest` — 取得匯入統計
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/stats/ingest" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"total_videos": 21,
|
||||
"total_chunks": 0,
|
||||
"sentence_chunks": 0,
|
||||
"cut_chunks": 0,
|
||||
"time_chunks": 0,
|
||||
"searchable_chunks": 0,
|
||||
"chunks_with_visual": 0,
|
||||
"chunks_with_summary": 0,
|
||||
"pending_videos": 12
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 錯誤回應
|
||||
|
||||
| HTTP Code | 說明 |
|
||||
|-----------|------|
|
||||
| `400` | 請求參數錯誤 (例如缺少必填欄位) |
|
||||
| `401` | API Key 無效或缺失 |
|
||||
| `404` | 資源不存在 |
|
||||
| `422` | 請求格式錯誤 (JSON 解析失敗) |
|
||||
| `500` | 伺服器內部錯誤 |
|
||||
904
docs_v1.0/DEV_API_V1.0/API_REFERENCE_v1.0.0.md
Normal file
904
docs_v1.0/DEV_API_V1.0/API_REFERENCE_v1.0.0.md
Normal file
@@ -0,0 +1,904 @@
|
||||
# Momentry Core API Documentation v1.0.0
|
||||
|
||||
## 快速資訊
|
||||
- **Base URL**: `http://<host>:3003` (開發環境)
|
||||
- **Production**: `http://<host>:3002`
|
||||
- **認證方式**: Header `X-API-Key: <your_api_key>`
|
||||
- **測試 Key**: `muser_test_001`
|
||||
|
||||
---
|
||||
|
||||
## API 分類原則
|
||||
|
||||
| 分類 | 用途 | 代表端點 |
|
||||
|------|------|----------|
|
||||
| **健康檢查** | 系統狀態確認 | `/health` |
|
||||
| **檔案管理 (Files)** | 列出、查詢檔案 | `/api/v1/files` |
|
||||
| **人物管理 (People)** | Identity 搜尋、候選 | `/api/v1/people` |
|
||||
| **Identity 管理** | 人物詳細資訊 | `/api/v1/identities/:uuid` |
|
||||
| **搜尋 (Search)** | 文字、BM25、混合搜尋 | `/api/v1/search/*` |
|
||||
| **人臉 (Face)** | 人臉辨識、列表 | `/api/v1/face/*` |
|
||||
| **任務 (Jobs)** | 處理任務狀態 | `/api/v1/jobs` |
|
||||
| **統計 (Stats)** | 系統統計 | `/api/v1/stats/ingest` |
|
||||
|
||||
### 設計概念
|
||||
1. **Files 是核心資源**:所有影片/圖片都是 File,用 32 碼 `file_uuid` 識別
|
||||
2. **Identity 是跨檔案的人物**:一個 Identity 可出現在多個 Files
|
||||
3. **People = Identity 的別名路由**:`/api/v1/people` 和 `/api/v1/identities` 指向相同資料
|
||||
4. **所有列表 API 支援分頁**:`page`, `page_size` 參數
|
||||
5. **所有操作 API 回傳統一格式**:`{ success, data, total, page, page_size }`
|
||||
|
||||
---
|
||||
|
||||
## 1. 健康檢查
|
||||
|
||||
### `GET /health`
|
||||
檢查系統是否運作。
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0 (build: 2026-04-30 15:40:04)",
|
||||
"uptime_ms": 348240
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 檔案管理 (Files)
|
||||
|
||||
> **測試日期**: 2026-04-30 | **環境**: Playground (Port 3003) | **Schema**: dev
|
||||
|
||||
### `GET /api/v1/files` — 列出所有檔案
|
||||
|
||||
**參數**: `page` (預設 1), `page_size` (預設 20)
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files?page=1&page_size=2" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 21,
|
||||
"page": 1,
|
||||
"page_size": 2,
|
||||
"data": [
|
||||
{
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"status": "ready"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/:uuid` — 取得檔案詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/384b0ff44aaaa1f14cb2cd63b3fea966" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"metadata": {
|
||||
"format": {
|
||||
"duration": "6879.329524",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"streams": [
|
||||
{
|
||||
"codec_name": "h264",
|
||||
"codec_type": "video",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"r_frame_rate": "60000/1001"
|
||||
},
|
||||
{
|
||||
"codec_name": "aac",
|
||||
"codec_type": "audio",
|
||||
"sample_rate": "44100",
|
||||
"channels": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/scan` — 掃描檔案系統
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/scan" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**實際回應**:
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov",
|
||||
"file_size": 2361629896,
|
||||
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
|
||||
"status": "pending",
|
||||
"is_registered": true,
|
||||
"registration_time": "2026-04-28 18:25:14.010351+00"
|
||||
}
|
||||
],
|
||||
"total": 20,
|
||||
"registered_count": 20,
|
||||
"unregistered_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/files/register` — 註冊新檔案
|
||||
|
||||
> **⚠️ 已知問題**: 此 endpoint 在 dev 環境有 SQLx 型別綁定問題 (probe_json text vs jsonb)。不影響 marcom 團隊的 GUI 設計。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"file_path": "/path/to/video.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 人物管理 (People)
|
||||
|
||||
### `GET /api/v1/people` — 列出所有人物
|
||||
|
||||
**參數**: `page`, `page_size`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/people?page=1&page_size=3" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"data": [
|
||||
{
|
||||
"identity_id": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"metadata": {},
|
||||
"created_at": "2026-04-28T06:10:14.582062Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/people/search` — 搜尋人物
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "Trace",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:3003/api/v1/people/search" \
|
||||
-H "X-API-Key: muser_test_001" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query":"Trace","limit":3}'
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"total": 3,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"data": [
|
||||
{
|
||||
"identity_id": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"metadata": {},
|
||||
"created_at": "2026-04-28T06:10:14.582062Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/people/candidates` — 列出未命名人物候選
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/people/candidates" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Identity 詳細資訊
|
||||
|
||||
### `GET /api/v1/identities/:uuid` — 取得人物詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/a9a90105-6d6b-46ff-92da-0c3c1a57dff4" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"uuid": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"identity_type": "people",
|
||||
"source": "auto_trace",
|
||||
"status": "active",
|
||||
"metadata": {},
|
||||
"reference_data": {
|
||||
"trace_id": 2,
|
||||
"quality_avg": 0.8748,
|
||||
"trace_stats": {
|
||||
"start_frame": 155,
|
||||
"end_frame": 297,
|
||||
"avg_confidence": 0.8624,
|
||||
"duration_seconds": 6.5,
|
||||
"total_appearances": 143
|
||||
},
|
||||
"angles_covered": ["profile_right", "three_quarter"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/identities/:uuid/files` — 取得人物出現的檔案列表
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/files" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 搜尋 (Search)
|
||||
|
||||
### `POST /api/v1/search` — 向量搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "person talking",
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"results": [],
|
||||
"query": "person talking"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/bm25` — BM25 全文搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "test",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"uuid": "a1b10138a6bbb0cd",
|
||||
"chunk_id": "sentence_1006",
|
||||
"text": "...",
|
||||
"bm25_score": 5.23
|
||||
}
|
||||
],
|
||||
"query": "test"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/hybrid` — 混合搜尋 (向量 + BM25)
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"query": "test",
|
||||
"limit": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 人臉 (Face)
|
||||
|
||||
### `GET /api/v1/face/list` — 列出人臉
|
||||
|
||||
**必填參數**: `file_uuid`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/face/list?file_uuid=384b0ff44aaaa1f14cb2cd63b3fea966&page=1&page_size=3" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Found 2 faces",
|
||||
"faces": [
|
||||
{
|
||||
"face_id": "identity_8d7c22e3a6794a8c93b4a3655f106944",
|
||||
"name": "Cary Grant",
|
||||
"created_at": "2026-04-18T10:44:48.571407+00:00",
|
||||
"is_active": true
|
||||
},
|
||||
{
|
||||
"face_id": "identity_9c5d1e8965eb49ae83d9b88db12fbb18",
|
||||
"name": "Audrey Hepburn",
|
||||
"created_at": "2026-04-18T10:44:31.536039+00:00",
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"count": 2,
|
||||
"page": 1,
|
||||
"page_size": 3
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 任務 (Jobs)
|
||||
|
||||
### `GET /api/v1/jobs` — 列出處理任務
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/jobs?page=1&page_size=2" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"id": 32,
|
||||
"uuid": "942d0bdf5d6fb6ac18b47deb031e60c3",
|
||||
"status": "running",
|
||||
"current_processor": null,
|
||||
"progress_current": 0,
|
||||
"progress_total": 0,
|
||||
"created_at": "2026-04-28 15:55:47.911654",
|
||||
"started_at": null
|
||||
}
|
||||
],
|
||||
"count": 19,
|
||||
"page": 1,
|
||||
"page_size": 2
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/rules/:rule/status` — 取得規則狀態
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/rules/default/status" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"rule": "default",
|
||||
"supported_processor_ids": [],
|
||||
"active_jobs": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 統計 (Stats)
|
||||
|
||||
### `GET /api/v1/stats/ingest` — 取得匯入統計
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/stats/ingest" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"total_videos": 21,
|
||||
"total_chunks": 0,
|
||||
"sentence_chunks": 0,
|
||||
"cut_chunks": 0,
|
||||
"time_chunks": 0,
|
||||
"searchable_chunks": 0,
|
||||
"chunks_with_visual": 0,
|
||||
"chunks_with_summary": 0,
|
||||
"pending_videos": 12
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 影片管理 (Videos)
|
||||
|
||||
> **注意**: `/api/v1/videos` 主要用於後端列表與詳細資料查詢。前端常用 `/api/v1/files` 作為主要資源入口。
|
||||
|
||||
### `GET /api/v1/videos` — 列出影片
|
||||
|
||||
**參數**: `page`, `page_size`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/videos?page=1&page_size=1" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/view7.mp4",
|
||||
"file_name": "view7.mp4",
|
||||
"file_type": null,
|
||||
"duration": 11.066667,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"status": "pending",
|
||||
"processing_status": {},
|
||||
"birth_registration": {},
|
||||
"created_at": "2026-04-30 18:17:46.161312+00",
|
||||
"registration_time": "2026-04-30 18:17:46.161312+00",
|
||||
"file_size": null,
|
||||
"probe_json": "...",
|
||||
"total_frames": 332
|
||||
}
|
||||
],
|
||||
"count": 22,
|
||||
"page": 1,
|
||||
"page_size": 1
|
||||
}
|
||||
```
|
||||
|
||||
### `DELETE /api/v1/videos/:uuid` — 刪除影片
|
||||
|
||||
刪除影片及其所有關聯資料 (faces, chunks, etc.)。
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s -X DELETE "http://localhost:3003/api/v1/videos/<file_uuid>" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
### `GET /api/v1/progress/:uuid` — 取得處理進度
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/progress/e79890f13e2e0bebf6c67b436f2c4279" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"file_name": "view7.mp4",
|
||||
"duration": 11.066667,
|
||||
"overall_progress": 0,
|
||||
"cpu_percent": 0.0,
|
||||
"gpu_percent": null,
|
||||
"memory_percent": 0.2,
|
||||
"memory_mb": 29504,
|
||||
"processors": [
|
||||
{
|
||||
"name": "asr",
|
||||
"status": "pending",
|
||||
"current": 0,
|
||||
"total": 0,
|
||||
"progress": 0,
|
||||
"message": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 其他工具 (Utilities)
|
||||
|
||||
### `GET /api/v1/lookup` — 查詢檔案資訊
|
||||
|
||||
可透過 `uuid` 或 `path` 查詢。
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/lookup?uuid=e79890f13e2e0bebf6c67b436f2c4279" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/view7.mp4",
|
||||
"file_name": "view7.mp4",
|
||||
"duration": 11.066667
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 人臉 (Face)
|
||||
|
||||
### `GET /api/v1/face/list` — 列出人臉
|
||||
|
||||
**必填參數**: `file_uuid`
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/face/list?file_uuid=e79890f13e2e0bebf6c67b436f2c4279&page=1&page_size=2" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"faces": [
|
||||
{
|
||||
"face_id": "identity_8d7c22e3a6794a8c93b4a3655f106944",
|
||||
"name": "Cary Grant",
|
||||
"is_active": true
|
||||
}
|
||||
],
|
||||
"count": 2
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/face/:face_id` — 取得人臉詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/face/identity_8d7c22e3a6794a8c93b4a3655f106944" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"face_id": "identity_8d7c22e3a6794a8c93b4a3655f106944",
|
||||
"name": "Cary Grant",
|
||||
"has_embedding": false,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/face/recognize` — 進行人臉辨識
|
||||
|
||||
> **注意**: 此端點會掃描影片並返回逐幀結果。若影片過大可能耗時較長。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"image_path": "/tmp/test.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"result": {
|
||||
"frame_count": 332,
|
||||
"fps": 30.0,
|
||||
"faces": [ ... ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 快照與截圖管理 (Snapshots)
|
||||
|
||||
> **設計概念**: 快照分為「熱 (hot)」與「冷 (cold)」。冷快照需遷移至記憶體 (migrate) 後才能進行高效存取。
|
||||
|
||||
### `GET /api/v1/files/:uuid/snapshots` — 列出快照狀態
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/e79890f13e2e0bebf6c67b436f2c4279/snapshots" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"tier": "cold",
|
||||
"hits": 0,
|
||||
"types": [
|
||||
"products", "ocr", "logos", "faces"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/:uuid/snapshots/status` — 檢查詳細狀態
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/e79890f13e2e0bebf6c67b436f2c4279/snapshots/status" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"status": "cold"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/files/:uuid/snapshots/migrate` — 載入快照至記憶體
|
||||
|
||||
將冷快照載入記憶體以加速讀取。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"parent_uuid": "e79890f13e2e0bebf6c67b436f2c4279"
|
||||
}
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Migrated 4 snapshot types",
|
||||
"migrated_types": ["products", "ocr", "logos", "faces"]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/files/:uuid/snapshots/teardown` — 釋放記憶體
|
||||
|
||||
釋放已載入的快照資源。
|
||||
|
||||
---
|
||||
|
||||
## 13. 身份綁定與訊號 (Identity Binding)
|
||||
|
||||
### `POST /api/v1/identities/bind` — 綁定人臉/聲音至身份
|
||||
|
||||
將特定的人臉或聲音訊號綁定到全域身份。若身份不存在,需提供 `name` 自動建立。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"name": "John Doe",
|
||||
"binding_type": "face",
|
||||
"binding_value": "identity_xxx"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/identities/unbind` — 解綁身份
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"binding_type": "face",
|
||||
"binding_value": "identity_xxx"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/signals/unbound` — 列出未綁定訊號
|
||||
|
||||
列出檔案中尚未被綁定的臉部或聲音訊號。
|
||||
|
||||
**參數**: `uuid`, `binding_type` (face/speaker)
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/signals/unbound?uuid=e79890f13e2e0bebf6c67b436f2c4279&binding_type=face" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Found 0 unbound face signals",
|
||||
"data": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. 身份來源與詳細 (Identity Sources)
|
||||
|
||||
### `GET /api/v1/identities/:uuid` — 取得身份詳情
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/a9a90105-6d6b-46ff-92da-0c3c1a57dff4" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"uuid": "a9a90105-6d6b-46ff-92da-0c3c1a57dff4",
|
||||
"name": "Trace 2 Fixed Format",
|
||||
"identity_type": "people",
|
||||
"source": "auto_trace",
|
||||
"status": "active",
|
||||
"reference_data": {
|
||||
"trace_id": 2,
|
||||
"quality_avg": 0.8748
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/identities/:identity_id/faces` — 取得身份下的所有臉部
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/identities/22/faces?page=1&page_size=5" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"identity_id": 22,
|
||||
"faces": [],
|
||||
"total": 0
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/v1/files/:uuid/identities` — 取得檔案中出現的身份
|
||||
|
||||
**curl**:
|
||||
```bash
|
||||
curl -s "http://localhost:3003/api/v1/files/e79890f13e2e0bebf6c67b436f2c4279/identities" \
|
||||
-H "X-API-Key: muser_test_001"
|
||||
```
|
||||
|
||||
**回應**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"total": 0,
|
||||
"data": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. 進階視覺搜尋 (Visual Search Variants)
|
||||
|
||||
### `POST /api/v1/search/visual/class` — 依物件類別搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"object_class": "person"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/visual/density` — 依物件密度搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"min_density": 0.1
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/search/visual/combination` — 依物件組合搜尋
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"combination": [["person", 1]]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. 代理人 (Agents)
|
||||
|
||||
### `POST /api/v1/agents/identity/analyze` — 分析身份重複
|
||||
|
||||
分析檔案中的身份是否重複,提供合併建議。
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279"
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/agents/5w1h/analyze` — 分析 5W1H 摘要
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"file_uuid": "e79890f13e2e0bebf6c67b436f2c4279",
|
||||
"chunk_id": "chunk_1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. 資產操作 (Assets)
|
||||
|
||||
### `POST /api/v1/assets/:uuid/process` — 觸發處理流程
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"processors": ["asr", "cut", "face"]
|
||||
}
|
||||
```
|
||||
|
||||
### `POST /api/v1/unregister` — 解除註冊檔案
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"uuid": "e79890f13e2e0bebf6c67b436f2c4279"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 錯誤回應
|
||||
|
||||
| HTTP Code | 說明 |
|
||||
|-----------|------|
|
||||
| `400` | 請求參數錯誤 (例如缺少必填欄位) |
|
||||
| `401` | API Key 無效或缺失 |
|
||||
| `404` | 資源不存在 |
|
||||
| `422` | 請求格式錯誤 (JSON 解析失敗) |
|
||||
| `500` | 伺服器內部錯誤 |
|
||||
@@ -12,17 +12,6 @@ use crate::core::db::ResourceRecord;
|
||||
|
||||
pub fn identity_routes() -> Router<crate::api::server::AppState> {
|
||||
Router::new()
|
||||
.route("/api/v1/people", get(list_people))
|
||||
.route("/api/v1/people/search", post(search_people))
|
||||
.route("/api/v1/people/candidates", get(list_candidates))
|
||||
.route(
|
||||
"/api/v1/people/:identity_id/confirm-candidate",
|
||||
post(confirm_candidate),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/people/:identity_id/reject-candidate",
|
||||
post(reject_candidate),
|
||||
)
|
||||
.route("/api/v1/files", get(list_files))
|
||||
.route("/api/v1/files/:uuid", get(get_file_detail))
|
||||
.route("/api/v1/files/:uuid/identities", get(get_file_identities))
|
||||
@@ -34,213 +23,6 @@ pub fn identity_routes() -> Router<crate::api::server::AppState> {
|
||||
.route("/api/v1/resources", get(list_resources))
|
||||
}
|
||||
|
||||
// ... (Keep existing functions) ...
|
||||
|
||||
// --- People / Identity Endpoints ---
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PeopleQuery {
|
||||
page: Option<usize>,
|
||||
page_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PeopleResponse {
|
||||
pub success: bool,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<PeopleItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PeopleItem {
|
||||
pub identity_id: Uuid,
|
||||
pub name: String,
|
||||
pub metadata: serde_json::Value,
|
||||
pub created_at: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
|
||||
async fn list_people(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Query(params): Query<PeopleQuery>,
|
||||
) -> Result<Json<PeopleResponse>, (StatusCode, String)> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.list_people(page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
// TODO: Get total count
|
||||
let total = 100; // Placeholder
|
||||
|
||||
let data = records
|
||||
.into_iter()
|
||||
.map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(PeopleResponse {
|
||||
success: true,
|
||||
total,
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchPeopleRequest {
|
||||
pub query: String,
|
||||
pub page: Option<usize>,
|
||||
pub page_size: Option<usize>,
|
||||
}
|
||||
|
||||
async fn search_people(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Json(req): Json<SearchPeopleRequest>,
|
||||
) -> Result<Json<PeopleResponse>, (StatusCode, String)> {
|
||||
let page = req.page.unwrap_or(1);
|
||||
let page_size = req.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.search_people(&req.query, page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<PeopleItem> = records
|
||||
.into_iter()
|
||||
.map(|r| PeopleItem {
|
||||
identity_id: r.uuid,
|
||||
name: r.name,
|
||||
metadata: r.metadata,
|
||||
created_at: r.created_at,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(PeopleResponse {
|
||||
success: true,
|
||||
total: data.len() as i64, // Approximation
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CandidatesQuery {
|
||||
page: Option<usize>,
|
||||
page_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CandidatesResponse {
|
||||
pub success: bool,
|
||||
pub total: i64,
|
||||
pub page: usize,
|
||||
pub page_size: usize,
|
||||
pub data: Vec<CandidateItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CandidateItem {
|
||||
pub pre_chunk_id: i64,
|
||||
pub file_uuid: Uuid,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
async fn list_candidates(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Query(params): Query<CandidatesQuery>,
|
||||
) -> Result<Json<CandidatesResponse>, (StatusCode, String)> {
|
||||
let page = params.page.unwrap_or(1);
|
||||
let page_size = params.page_size.unwrap_or(20);
|
||||
let offset = ((page - 1) as i64) * (page_size as i64);
|
||||
|
||||
let records = state
|
||||
.db
|
||||
.get_people_candidates(page_size as i32, offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data = records
|
||||
.into_iter()
|
||||
.map(|r| CandidateItem {
|
||||
pre_chunk_id: r.id,
|
||||
file_uuid: r.file_uuid,
|
||||
data: r.data,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(CandidatesResponse {
|
||||
success: true,
|
||||
total: 0, // TODO
|
||||
page,
|
||||
page_size,
|
||||
data,
|
||||
}))
|
||||
}
|
||||
|
||||
// --- Candidate Workflow Endpoints ---
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfirmCandidateRequest {
|
||||
pub pre_chunk_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ConfirmCandidateResponse {
|
||||
pub success: bool,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
async fn confirm_candidate(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(identity_id_str): Path<String>,
|
||||
Json(req): Json<ConfirmCandidateRequest>,
|
||||
) -> Result<Json<ConfirmCandidateResponse>, (StatusCode, String)> {
|
||||
let identity_id = Uuid::parse_str(&identity_id_str)
|
||||
.map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid UUID: {}", e)))?;
|
||||
|
||||
state
|
||||
.db
|
||||
.confirm_candidate(req.pre_chunk_id, identity_id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ConfirmCandidateResponse {
|
||||
success: true,
|
||||
message: "Candidate confirmed and linked to identity".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn reject_candidate(
|
||||
State(state): State<crate::api::server::AppState>,
|
||||
Path(_identity_id_str): Path<String>, // Unused, but consistent with route
|
||||
Json(req): Json<ConfirmCandidateRequest>,
|
||||
) -> Result<Json<ConfirmCandidateResponse>, (StatusCode, String)> {
|
||||
state
|
||||
.db
|
||||
.reject_candidate(req.pre_chunk_id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
Ok(Json(ConfirmCandidateResponse {
|
||||
success: true,
|
||||
message: "Candidate rejected".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
// --- Files Endpoints ---
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -209,7 +209,7 @@ impl From<VideoRow> for VideoRecord {
|
||||
width: row.width.unwrap_or(0) as u32,
|
||||
height: row.height.unwrap_or(0) as u32,
|
||||
fps: row.fps.unwrap_or(0.0),
|
||||
probe_json: row.probe_json.map(|v| v.to_string()),
|
||||
probe_json: row.probe_json,
|
||||
storage: StorageStatus {
|
||||
fs_video: row.fs_video.unwrap_or(false),
|
||||
fs_json: row.fs_json.unwrap_or(false),
|
||||
@@ -243,7 +243,7 @@ pub struct VideoRecord {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fps: f64,
|
||||
pub probe_json: Option<String>,
|
||||
pub probe_json: Option<serde_json::Value>,
|
||||
pub storage: StorageStatus,
|
||||
pub status: VideoStatus,
|
||||
pub processing_status: Option<serde_json::Value>,
|
||||
@@ -695,7 +695,7 @@ impl PostgresDb {
|
||||
}
|
||||
|
||||
// Videos
|
||||
sqlx::query("CREATE TABLE IF NOT EXISTS videos (id SERIAL PRIMARY KEY, file_uuid VARCHAR(32) UNIQUE NOT NULL, file_path TEXT NOT NULL, file_name TEXT NOT NULL, duration DOUBLE PRECISION, width INTEGER, height INTEGER, fps DOUBLE PRECISION, probe_json TEXT, fs_video BOOLEAN DEFAULT FALSE, fs_json BOOLEAN DEFAULT FALSE, psql_chunk BOOLEAN DEFAULT FALSE, pobject_chunk BOOLEAN DEFAULT FALSE, mobject_chunk BOOLEAN DEFAULT FALSE, pvector_chunk BOOLEAN DEFAULT FALSE, qvector_chunk BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)").execute(pool).await?;
|
||||
sqlx::query("CREATE TABLE IF NOT EXISTS videos (id SERIAL PRIMARY KEY, file_uuid VARCHAR(32) UNIQUE NOT NULL, file_path TEXT NOT NULL, file_name TEXT NOT NULL, duration DOUBLE PRECISION, width INTEGER, height INTEGER, fps DOUBLE PRECISION, probe_json jsonb, fs_video BOOLEAN DEFAULT FALSE, fs_json BOOLEAN DEFAULT FALSE, psql_chunk BOOLEAN DEFAULT FALSE, pobject_chunk BOOLEAN DEFAULT FALSE, mobject_chunk BOOLEAN DEFAULT FALSE, pvector_chunk BOOLEAN DEFAULT FALSE, qvector_chunk BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)").execute(pool).await?;
|
||||
sqlx::query("CREATE INDEX IF NOT EXISTS idx_videos_file_uuid ON videos(file_uuid)")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
@@ -171,7 +171,7 @@ impl IngestionService {
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
probe_json: Some(probe_json_str),
|
||||
probe_json: serde_json::from_str(&probe_json_str).ok(),
|
||||
storage: Default::default(),
|
||||
status: VideoStatus::Pending,
|
||||
processing_status: Some(serde_json::json!({"phase": "REGISTERED"})),
|
||||
|
||||
@@ -967,7 +967,7 @@ async fn main() -> Result<()> {
|
||||
width,
|
||||
height,
|
||||
fps,
|
||||
probe_json: Some(json_str),
|
||||
probe_json: serde_json::from_str(&json_str).ok(),
|
||||
storage: Default::default(),
|
||||
status: VideoStatus::Pending,
|
||||
processing_status: Some(serde_json::json!({"phase": "REGISTERED"})),
|
||||
|
||||
Reference in New Issue
Block a user