feat: Initial v0.9 release with API Key authentication

## v0.9.20260325_144654

### Features
- API Key Authentication System
- Job Worker System
- V2 Backup Versioning

### Bug Fixes
- get_processor_results_by_job column mapping

Co-authored-by: OpenCode
This commit is contained in:
accusys
2026-03-25 14:52:51 +08:00
parent 47e86b696f
commit 383201cacd
193 changed files with 40268 additions and 422 deletions

21
.markdownlint.json Normal file
View File

@@ -0,0 +1,21 @@
{
"default": true,
"MD003": false,
"MD009": false,
"MD010": false,
"MD013": false,
"MD022": false,
"MD024": false,
"MD025": false,
"MD031": false,
"MD032": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD040": false,
"MD046": false,
"MD055": false,
"MD056": false,
"MD058": false,
"MD060": false
}

21
.markdownlintrc Normal file
View File

@@ -0,0 +1,21 @@
{
"default": true,
"MD003": false,
"MD009": false,
"MD010": false,
"MD013": false,
"MD022": false,
"MD024": false,
"MD025": false,
"MD031": false,
"MD032": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD040": false,
"MD046": false,
"MD055": false,
"MD056": false,
"MD058": false,
"MD060": false
}

143
CHANGELOG.md Normal file
View File

@@ -0,0 +1,143 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Gitea API token integration
- n8n API key integration
- API key caching with Moka
- Rate limiting for API key validation
- Constant-time hash comparison
- OpenAPI documentation with utoipa
## [0.1.0] - 2026-03-21
### Added
#### API Key Management System
- API key generation with secure random (UUID v4)
- SHA256 key hashing
- 5 key types: System, User, Service, Integration, Emergency
- Key expiration with configurable TTL
- Grace period for key rotation
#### Anomaly Detection
- High request rate detection (>1000/min)
- High error rate detection (>50%)
- Multiple IP detection (>5/hour)
- Unusual time activity detection
- Redis Pub/Sub for anomaly alerts
#### Rotation Mechanism
- Automatic rotation scheduling
- Manual rotation requests
- Forced rotation for security incidents
- Grace period management per key type:
- System: 72 hours
- User: 24 hours
- Service: 48 hours
- Integration: 24 hours
- Emergency: 0 hours (immediate)
#### PostgreSQL Integration
- `api_keys` table for key storage
- `api_key_audit_log` table for audit trail
- `api_key_anomalies` table for anomaly records
- Full CRUD operations for API keys
#### Redis Integration
- Anomaly alert Pub/Sub (`momentry:anomaly:alerts`)
- Key anomaly state tracking
- Real-time alert notifications
#### CLI Commands
- `momentry api-key create` - Create new API key
- `momentry api-key list` - List all API keys
- `momentry api-key validate` - Validate an API key
- `momentry api-key revoke` - Revoke an API key
- `momentry api-key rotate` - Request key rotation
- `momentry api-key stats` - Show statistics
#### Gitea Integration
- Create Gitea Personal Access Tokens
- List user tokens
- Delete tokens
- Local token tracking
- CLI commands:
- `momentry gitea create`
- `momentry gitea list`
- `momentry gitea delete`
- `momentry gitea verify`
#### n8n Integration
- Create n8n API keys
- List API keys
- Delete API keys
- Local key tracking
- CLI commands:
- `momentry n8n create`
- `momentry n8n list`
- `momentry n8n delete`
- `momentry n8n verify`
#### Security Features
- Constant-time hash comparison (subtle crate)
- Rate limiting for validation attempts
- IP-based lockout after failed attempts
- Configurable thresholds via environment variables
#### Performance Optimizations
- Moka-based API key validation cache
- Configurable TTL and capacity
- Reduced database queries for hot keys
#### Documentation
- API Key Management design document
- Redis user configuration guide
- Gitea token integration guide
- n8n API key integration guide
- Optimization plan with task codes
### Environment Variables
#### API Key Configuration
```
CACHE_TTL_SECONDS=300 # Cache TTL (default: 300)
CACHE_MAX_CAPACITY=10000 # Max cache entries (default: 10000)
RATE_LIMIT_MAX_ATTEMPTS=5 # Max failed attempts (default: 5)
RATE_LIMIT_WINDOW_SECONDS=900 # Lockout duration (default: 900)
```
#### Service URLs
```
GITEA_URL=http://localhost:3000
N8N_URL=https://n8n.momentry.ddns.net
```
### Database Schema
#### Tables Created
- `api_keys` - API key storage
- `api_key_audit_log` - Audit trail
- `api_key_anomalies` - Anomaly records
- `gitea_tokens` - Gitea token tracking
- `n8n_api_keys` - n8n API key tracking
### Dependencies Added
- `uuid` - UUID generation
- `subtle` - Constant-time comparison
- `moka` - Async cache
- `utoipa` - OpenAPI documentation
- `utoipa-swagger-ui` - Swagger UI
---
## Version History
| Version | Date | Description |
|---------|------|-------------|
| 0.1.0 | 2026-03-21 | Initial release with API Key Management |

1
a1b10138a6bbb0cd.cut.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.cut.json

1
a1b10138a6bbb0cd.face.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.face.json

1
a1b10138a6bbb0cd.ocr.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.ocr.json

1
a1b10138a6bbb0cd.pose.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.pose.json

1
a1b10138a6bbb0cd.story.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.story.json

1
a1b10138a6bbb0cd.yolo.json Symbolic link
View File

@@ -0,0 +1 @@
/Users/accusys/momentry_core_0.1/output/a1b10138a6bbb0cd.yolo.json

193
docs/API_ACCESS.md Normal file
View File

@@ -0,0 +1,193 @@
# Momentry Core API 存取指南
## 基本網址
| 環境 | URL | 說明 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 直接訪問 API繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 |
### 何時使用哪個 URL
**使用 `localhost:3002`**
- 開發/測試環境
- 直接在伺服器上操作
- 當反向代理有問題時
**使用 `api.momentry.ddns.net`**
- n8n workflow 中呼叫 API
- 外部系統整合
- 生產環境
## 認證
目前為開放狀態(示範用途無需認證)。正式環境將實作 API Key。
---
## 影片搜尋 API
### 語意搜尋
**端點:** `POST /api/v1/search`
**請求:**
```json
{
"query": "charade",
"limit": 5,
"uuid": "a1b10138a6bbb0cd"
}
```
| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `query` | 字串 | 是 | 搜尋文字 |
| `limit` | 整數 | 否 | 最大回傳結果數(預設 10 |
| `uuid` | 字串 | 否 | 依影片 UUID 過濾 |
**回應:**
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0006",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526
}
],
"query": "charade"
}
```
---
### n8n 整合搜尋
**端點:** `POST /api/v1/n8n/search`
**請求:**
```json
{
"query": "charade",
"limit": 5
}
```
**回應:**
```json
{
"query": "charade",
"count": 5,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## 影片管理 API
### 列出所有影片
**端點:** `GET /api/v1/videos`
### 查詢影片資訊
**端點:** `GET /api/v1/lookup?uuid={uuid}``GET /api/v1/lookup?path={path}`
### 取得處理進度
**端點:** `GET /api/v1/progress/{uuid}`
---
## 區塊資料結構
每個搜尋結果包含影片播放的時間資訊:
| 欄位 | 說明 |
|------|------|
| `uuid` | 影片識別碼 |
| `chunk_id` | 區塊唯一識別碼 |
| `chunk_type` | 類型:`sentence``cut``time_based` |
| `start_time` | 開始時間(秒) |
| `end_time` | 結束時間(秒) |
| `text` | 語音轉文字內容 |
| `score` | 相關性分數0-1 |
---
## 整合範例
### JavaScript/fetch
```javascript
const response = await fetch('http://localhost:3002/api/v1/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'charade', limit: 5 })
});
const data = await response.json();
console.log(data.results);
```
### PHP/cURL
```php
$ch = curl_init('http://localhost:3002/api/v1/search');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => 'charade',
'limit' => 5
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$data = json_decode($response, true);
```
---
## 影片嵌入網址
影片可透過 SFTPGo 分享連結存取:
```
https://wp.momentry.ddns.net/{檔案名稱}
```
**手動建立分享連結:**
1. 開啟 SFTPGo Web UI`http://localhost:8080`
2. 使用帳號 `demo` / 密碼 `demopassword123` 登入
3. 導航至 `Files` → 選擇影片檔案
4. 點擊 `Share``Create Link`
5. 複製產生的分享連結
使用搜尋結果中的 `start_time``end_time` 來嵌入影片片段。
---
## 服務列表
| 服務 | 網址 | 用途 |
|------|------|------|
| Momentry API | `http://localhost:3002` | 核心 API |
| SFTPGo | `http://localhost:8080` | 檔案儲存 |
| Qdrant | `http://localhost:6333` | 向量搜尋 |
| PostgreSQL | `localhost:5432` | 資料庫 |
---
## 示範影片
- **檔案:** `Old_Time_Movie_Show_-_Charade_1963.HD.mov`
- **UUID** `a1b10138a6bbb0cd`
- **長度:** 約 6879 秒(約 1.9 小時)
- **區塊數:** 3886 個(句子 + 場景 + 時間)

492
docs/API_CURL_EXAMPLES.md Normal file
View File

@@ -0,0 +1,492 @@
# Momentry API 使用說明 (curl 範例)
| 項目 | 內容 |
|------|------|
| 版本 | V1.2 |
| 日期 | 2026-03-23 |
| Base URL | `http://localhost:3002` |
---
> **狀態說明**:
> - ✅ **已實作**: 健康檢查、搜尋、影片管理端點
> - ⚠️ **規劃中**: API Key 管理功能
> - 🔧 **CLI**: 部分功能需使用命令列工具
---
## 目錄
1. [已實作端點](#1-已實作端點)
2. [API Key 管理](#2-api-key-管理-規劃中)
3. [影片管理](#3-影片管理)
4. [查詢與搜索](#4-查詢與搜索)
5. [系統狀態](#5-系統狀態)
---
## URL 選擇指南
### 兩種 URL 的使用情境
| 環境 | URL | 說明 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 直接訪問 API繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 |
### 何時使用 localhost:3002
- ✅ 開發/測試環境
- ✅ 直接在伺服器上操作
- ✅ 當 Caddy/反向代理有問題時
- ✅ 需要快速除錯時
### 何時使用 api.momentry.ddns.net
- ✅ n8n workflow 中呼叫 API
- ✅ 外部系統整合
- ✅ 生產環境
- ✅ 從其他機器訪問
### 快速切換範例
```bash
# 本地測試
curl http://localhost:3002/health
# 外部測試(功能相同)
curl https://api.momentry.ddns.net/health
```
### 常見問題
**Q: 為什麼有兩個 URL**
A: `localhost:3002` 是直接訪問,`api.momentry.ddns.net` 通過 Caddy 反向代理。
**Q: 兩者功能相同嗎?**
A: 是的,所有端點和功能完全相同。
**Q: 502 錯誤時怎麼辦?**
A: 如果 `api.momentry.ddns.net` 返回 502檢查 Momentry API 服務是否運行:
```bash
launchctl list | grep momentry.api
# 如果未運行
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## 1. 已實作端點
### 健康檢查
```bash
curl http://localhost:3002/health
```
**回應**:
```json
{"status":"ok","version":"0.1.0","uptime_ms":123456}
```
### 詳細健康檢查
```bash
curl http://localhost:3002/health/detailed
```
---
## 2. API Key 管理 *(規劃中)*
> ⚠️ **此功能尚未實作**。以下為規劃中的 API 說明,僅供參考。
### 2.1 建立 API Key
```bash
curl -X POST http://localhost:3002/api/v1/api-keys \
-H "Content-Type: application/json" \
-H "X-API-Key: your-admin-key" \
-d '{
"name": "my-service-key",
"key_type": "service",
"permissions": ["read", "write"],
"ttl_days": 90
}'
```
### 2.2 列出所有 API Keys
```bash
curl -X GET http://localhost:3002/api/v1/api-keys \
-H "X-API-Key: your-admin-key"
```
### 2.3 驗證 API Key
```bash
curl -X GET http://localhost:3002/api/v1/api-keys/validate \
-H "X-API-Key: key-to-validate"
```
### 2.4 撤銷 API Key
```bash
curl -X DELETE http://localhost:3002/api/v1/api-keys/msvc_a1b2c3d4_... \
-H "X-API-Key: your-admin-key"
```
### 2.5 請求 Key 輪換
```bash
curl -X POST http://localhost:3002/api/v1/api-keys/msvc_a1b2c3d4_.../rotate \
-H "X-API-Key: your-admin-key" \
-H "Content-Type: application/json" \
-d '{"reason": "scheduled_rotation"}'
```
### 2.6 取得統計資訊
```bash
curl -X GET http://localhost:3002/api/v1/api-keys/stats \
-H "X-API-Key: your-admin-key"
```
---
## 3. 影片管理
### 3.1 註冊影片 ✅
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/video.mp4"}'
```
**回應範例**:
```json
{
"id": 1,
"uuid": "a1b2c3d4e5f6g7h8",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080
}
```
### 3.2 列出所有影片 ✅
```bash
curl http://localhost:3002/api/v1/videos
```
### 3.3 查詢影片 ✅
```bash
# 依 UUID 查詢
curl "http://localhost:3002/api/v1/lookup?uuid=a1b2c3d4e5f6g7h8"
# 依路徑查詢
curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
```
### 3.4 處理影片 🔧 *(CLI - 非 API)*
影片處理需要使用 CLI 命令:
```bash
# 處理影片(生成 ASR, CUT, YOLO, OCR, Face, Pose 資料)
cargo run --bin momentry -- process <uuid>
# 或處理多個影片
cargo run --bin momentry -- process <uuid1> <uuid2> <uuid3>
```
### 3.5 取得處理進度 ✅
```bash
curl http://localhost:3002/api/v1/progress/<uuid>
```
**回應範例**:
```json
{
"uuid": "a1b2c3d4e5f6g7h8",
"overall_progress": 75,
"processors": [
{
"name": "asr",
"status": "complete",
"current": 100,
"total": 100,
"progress": 100,
"message": "7 segments"
},
{
"name": "cut",
"status": "complete",
"current": 134,
"total": 134,
"progress": 100,
"message": "134 scenes"
},
{
"name": "yolo",
"status": "progress",
"current": 5000,
"total": 14315,
"progress": 35,
"message": "frame 5000"
}
]
}
```
---
## 4. 查詢與搜索
### 4.1 語意搜尋 ✅
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{
"query": "測試關鍵字",
"limit": 5
}'
```
**回應範例**:
```json
{
"results": [
{
"uuid": "a1b2c3d4e5f6g7h8",
"chunk_id": "sentence_0006",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists...",
"score": 0.526
}
],
"query": "測試關鍵字"
}
```
### 4.2 n8n 格式搜尋 ✅
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{
"query": "測試關鍵字",
"limit": 5
}'
```
**回應範例**:
```json
{
"query": "測試關鍵字",
"count": 2,
"hits": [
{
"id": "c_001",
"vid": "a1b2c3d4e5f6g7h8",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/video.mp4"
}
]
}
```
### 4.3 混合搜尋 ✅
```bash
curl -X POST http://localhost:3002/api/v1/search/hybrid \
-H "Content-Type: application/json" \
-d '{
"query": "測試關鍵字",
"limit": 5
}'
```
---
## 5. 系統狀態
### 5.1 健康檢查 ✅
```bash
curl http://localhost:3002/health
```
**回應**:
```json
{"status":"ok","version":"0.1.0","uptime_ms":123456}
```
### 5.2 詳細健康檢查 ✅
```bash
curl http://localhost:3002/health/detailed
```
**回應範例**:
```json
{
"status":"ok",
"version":"0.1.0",
"uptime_ms":123456,
"services":{
"postgres":{"status":"ok","latency_ms":42,"error":null},
"redis":{"status":"ok","latency_ms":0,"error":null},
"qdrant":{"status":"ok","latency_ms":15,"error":null}
}
}
```
---
## 6. n8n Webhook 測試
### 測試 n8n Workflow
**重要**: 測試前請先在 n8n UI 中點擊 "Execute workflow" 按鈕
```bash
# 測試 Video RAG Workflow (Test Mode)
curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
# 帶有 UUID 過濾的搜尋
curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"woody","limit":5,"uuid":"a1b10138a6bbb0cd"}'
```
### 生產環境 Webhook
**注意**: 工作流程必須處於 Active 狀態
```bash
curl -X POST http://localhost:5678/webhook/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
```
### n8n Webhook 常見問題
**Q: webhook-test 返回 404**
A: 需要在 n8n UI 中點擊 "Execute workflow" 按鈕後才能使用 test webhook
**Q: webhook (生產環境) 返回 404**
A: 需要將工作流程切換為 Active 狀態 (右上角開關)
---
## 附錄
### A. 服務 URL 列表
| 服務 | URL |
|------|-----|
| Momentry API (本地) | `http://localhost:3002` |
| Momentry API (外部) | `https://api.momentry.ddns.net` |
| n8n Web UI | `https://n8n.momentry.ddns.net` |
| n8n Webhook Test | `http://localhost:5678/webhook-test/{workflow-name}` |
| n8n Webhook Prod | `http://localhost:5678/webhook/{workflow-name}` |
### B. 所有可用端點
| 端點 | 方法 | 狀態 | 說明 |
|------|------|------|------|
| `/health` | GET | ✅ | 健康檢查 |
| `/health/detailed` | GET | ✅ | 詳細健康檢查 |
| `/api/v1/register` | POST | ✅ | 註冊影片 |
| `/api/v1/search` | POST | ✅ | 語意搜尋 |
| `/api/v1/n8n/search` | POST | ✅ | n8n 格式搜尋 |
| `/api/v1/search/hybrid` | POST | ✅ | 混合搜尋 |
| `/api/v1/lookup` | GET | ✅ | 查詢影片 |
| `/api/v1/videos` | GET | ✅ | 列出所有影片 |
| `/api/v1/progress/:uuid` | GET | ✅ | 處理進度 |
| `/api/v1/api-keys` | * | ⚠️ | API Key 管理 (規劃中) |
### C. 常見錯誤
| HTTP 狀態 | 說明 | 解決方式 |
|-----------|------|----------|
| 200 | 成功 | - |
| 400 | 請求格式錯誤 | 檢查 JSON 格式 |
| 404 | 端點不存在或資源未找到 | 確認端點 URL 正確 |
| 500 | 伺服器內部錯誤 | 檢查 API 服務日誌 |
| **502** | **Bad Gateway** | **API 服務未啟動,見下方說明** |
#### 502 Bad Gateway 錯誤
**問題**: 外部 URL `https://api.momentry.ddns.net` 返回 502
**原因**: Momentry Core API 服務未啟動
**解決方式**:
```bash
# 1. 檢查服務狀態
launchctl list | grep momentry.api
# 2. 如果未啟動,手動啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 3. 或使用本地測試(繞過反向代理)
curl http://localhost:3002/health
# 4. 檢查日誌
tail -50 /Users/accusys/momentry/log/momentry_api.error.log
```
### D. 範例腳本
```bash
#!/bin/bash
# api_test.sh - API 測試腳本
API_URL="http://localhost:3002"
# 健康檢查
echo "=== Health Check ==="
curl -s "$API_URL/health" | jq .
# 搜尋
echo -e "\n=== Search ==="
curl -s -X POST "$API_URL/api/v1/search" \
-H "Content-Type: application/json" \
-d '{"query": "test", "limit": 3}' | jq .
# 列出影片
echo -e "\n=== Videos ==="
curl -s "$API_URL/api/v1/videos" | jq '.videos | length'
```
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 使用範例

View File

@@ -16,9 +16,34 @@
---
## 認證
除健康檢查端點外,所有 API 端點都需要 API Key。
### Header 方式
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
```
### 響應
- `401 Unauthorized` - 缺少或無效的 API Key
- `200 OK` - 認證成功
### 取得 API Key
使用 CLI 建立:
```bash
./target/release/momentry api-key create "My API Key" --key-type user
```
---
## 端點列表
### 健康檢查
### 健康檢查(公開)
| 方法 | 端點 | 說明 |
|------|------|------|
@@ -213,5 +238,7 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 使用範例
- [API_EXAMPLES.md](./API_EXAMPLES.md) - **完整範例總覽curl / n8n / WordPress**
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 詳細指南
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 詳細指南
- [API_CURL_EXAMPLES.md](./API_CURL_EXAMPLES.md) - curl 範例

726
docs/API_EXAMPLES.md Normal file
View File

@@ -0,0 +1,726 @@
# Momentry Core API 使用範例總覽
| 項目 | 內容 |
|------|------|
| 版本 | V2.0 |
| 日期 | 2026-03-25 |
| Base URL (本地) | `http://localhost:3002` |
| Base URL (外部) | `https://api.momentry.ddns.net` |
---
## 快速參考
### 環境 URL 選擇
| 環境 | URL | 用途 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 開發/測試,直接訪問 API |
| **外部訪問** | `https://api.momentry.ddns.net` | n8n、WordPress、curl 生產環境 |
### 所有可用端點
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 健康檢查 |
| GET | `/health/detailed` | 詳細健康檢查 |
| POST | `/api/v1/search` | 語意搜尋(標準格式) |
| POST | `/api/v1/n8n/search` | 語意搜尋n8n 格式) |
| POST | `/api/v1/search/hybrid` | 混合搜尋 |
| POST | `/api/v1/register` | 註冊影片 |
| POST | `/api/v1/probe` | 探測影片資訊 |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片 |
| GET | `/api/v1/progress/:uuid` | 處理進度 |
| GET | `/api/v1/jobs` | 任務列表 |
| GET | `/api/v1/jobs/:uuid` | 任務詳情 |
---
## 認證
### API Key
所有 `/api/v1/*` 端點需要 API Key 認證。
```bash
# 添加 API Key Header
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
# 範例
curl -H "X-API-Key: muser_f08e13ba967e4d8ea8fc542ad9f99ac8_1774416728_90472a35" \
http://localhost:3002/api/v1/videos
```
### 響應狀態
| 狀態碼 | 說明 |
|--------|------|
| 200 | 成功 |
| 401 | 未授權(缺少或無效 API Key |
| 500 | 伺服器錯誤 |
### 建立 API Key
```bash
./target/release/momentry api-key create "My Key" --key-type user
```
---
## 1. curl 範例
### 基本語法
```bash
# 格式
curl [OPTIONS] URL
# 常用選項
-X METHOD # HTTP 方法 (GET, POST, etc.)
-H HEADER # 添加 HTTP 標頭
-d DATA # POST 請求體
-s # 靜默模式
-w FORMAT # 輸出額外信息
```
### 1.1 健康檢查
```bash
# 基本健康檢查
curl http://localhost:3002/health
# 詳細健康檢查
curl http://localhost:3002/health/detailed
```
**回應**:
```json
{"status":"ok","version":"0.1.0","uptime_ms":123456}
```
### 1.2 語意搜尋
```bash
# 標準格式搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 5}'
# n8n 格式搜尋(推薦)
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 5}'
# 混合搜尋
curl -X POST http://localhost:3002/api/v1/search/hybrid \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 5}'
```
**標準格式回應**:
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists...",
"score": 0.92
}
],
"query": "charade"
}
```
**n8n 格式回應**:
```json
{
"query": "charade",
"count": 1,
"hits": [
{
"id": "sentence_0001",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0001",
"text": "fun plot twists...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/video.mp4"
}
]
}
```
### 1.3 影片管理
```bash
# 列出所有影片
curl http://localhost:3002/api/v1/videos
# 查詢特定影片(依 UUID
curl "http://localhost:3002/api/v1/lookup?uuid=a1b10138a6bbb0cd"
# 查詢特定影片(依路徑)
curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
# 取得處理進度
curl http://localhost:3002/api/v1/progress/a1b10138a6bbb0cd
# 探測影片(不註冊)
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/video.mp4"}'
# 註冊影片
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "/path/to/video.mp4", "file_name": "video.mp4"}'
```
### 1.4 批次測試腳本
```bash
#!/bin/bash
# api_test.sh - API 測試腳本
API_URL="http://localhost:3002"
echo "=== 健康檢查 ==="
curl -s "$API_URL/health" | jq .
echo -e "\n=== 語意搜尋 ==="
curl -s -X POST "$API_URL/api/v1/search" \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}' | jq .
echo -e "\n=== 影片列表 ==="
curl -s "$API_URL/api/v1/videos" | jq '.videos | length'
```
### 1.5 外部 URL 範例
```bash
# 使用外部 URL需網路可達
curl https://api.momentry.ddns.net/health
# 外部搜尋
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 5}'
```
---
## 2. n8n 範例
### 2.1 HTTP Request Node 設定
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Authentication: None
├── Send Body: ✓ (checked)
├── Content Type: JSON
└── Body:
{
"query": "={{ $json.query }}",
"limit": "={{ $json.limit || 10 }}"
}
```
### 2.2 基本搜尋 Workflow
```json
{
"name": "Momentry Video Search",
"nodes": [
{
"parameters": {},
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": {
"query": "charade",
"limit": 3
}
},
"name": "Search Video API",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300]
}
],
"connections": {
"Manual Trigger": {
"main": [[{"node": "Search Video API"}]]
}
}
}
```
### 2.3 Webhook 動態搜尋
```json
{
"name": "Momentry Dynamic Search",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "search",
"responseMode": "lastNode"
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": {
"query": "={{ JSON.stringify($json.body.query) }}",
"limit": "={{ $json.body.limit || 5 }}"
}
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300]
}
],
"connections": {
"Webhook": {
"main": [[{"node": "Search API"}]]
}
}
}
```
### 2.4 測試 Webhook
```bash
# 測試模式(需先在 n8n UI 點擊 Execute
curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
# 生產環境(需 workflow 為 Active 狀態)
curl -X POST http://localhost:5678/webhook/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
```
### 2.5 健康檢查 Workflow
```json
{
"name": "Momentry Health Check",
"nodes": [
{
"parameters": {},
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/health",
"method": "GET"
},
"name": "Health Check",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300]
}
],
"connections": {
"Manual Trigger": {
"main": [[{"node": "Health Check"}]]
}
}
}
```
### 2.6 錯誤處理
| 錯誤 | 原因 | 解決 |
|------|------|------|
| 502 Bad Gateway | API 服務未啟動 | `sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist` |
| "Your request is invalid" | Body 格式設定錯誤 | 確認 Content Type: JSONBody 為有效 JSON |
| 404 on webhook-test | 未執行 workflow | 在 n8n UI 點擊 "Execute workflow" |
---
## 3. WordPress 範例
### 3.1 PHP 基本用法
```php
<?php
// 搜尋影片
$api_url = 'https://api.momentry.ddns.net/api/v1/n8n/search';
$data = [
'query' => 'charade',
'limit' => 10
];
$response = wp_remote_post($api_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($data),
'timeout' => 30
]);
if (is_wp_error($response)) {
echo '錯誤: ' . $response->get_error_message();
} else {
$body = json_decode(wp_remote_retrieve_body($response), true);
print_r($body['hits']);
}
?>
```
### 3.2 列出影片
```php
<?php
$api_url = 'https://api.momentry.ddns.net/api/v1/videos';
$response = wp_remote_get($api_url, ['timeout' => 30]);
if (!is_wp_error($response)) {
$body = json_decode(wp_remote_retrieve_body($response), true);
foreach ($body['videos'] as $video) {
echo $video['file_name'] . "\n";
}
}
?>
```
### 3.3 查詢特定影片
```php
<?php
$uuid = 'a1b10138a6bbb0cd';
$api_url = 'https://api.momentry.ddns.net/api/v1/lookup?uuid=' . $uuid;
$response = wp_remote_get($api_url, ['timeout' => 30]);
if (!is_wp_error($response)) {
$video = json_decode(wp_remote_retrieve_body($response), true);
echo '檔案: ' . $video['file_name'] . "\n";
echo '時長: ' . $video['duration'] . ' 秒';
}
?>
```
### 3.4 JavaScript fetch
```javascript
// 搜尋影片
async function searchVideos(query, limit = 10) {
const response = await fetch('https://api.momentry.ddns.net/api/v1/n8n/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, limit })
});
if (!response.ok) {
throw new Error('API 請求失敗');
}
return await response.json();
}
// 使用範例
searchVideos('charade', 5)
.then(data => {
data.hits.forEach(hit => {
console.log(`${hit.text} (score: ${hit.score})`);
});
});
```
### 3.5 WordPress Shortcode
`functions.php` 中註冊短碼:
```php
<?php
// 註冊短碼
add_shortcode('momentry_search', function($atts) {
$atts = shortcode_atts([
'query' => '',
'limit' => '10'
], $atts);
if (empty($atts['query'])) {
return '<p>請提供搜尋關鍵字</p>';
}
$response = wp_remote_post('https://api.momentry.ddns.net/api/v1/n8n/search', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'query' => $atts['query'],
'limit' => (int)$atts['limit']
]),
'timeout' => 30
]);
if (is_wp_error($response)) {
return '<p>搜尋服務暫時無法使用</p>';
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (empty($data['hits'])) {
return '<p>找不到相關結果</p>';
}
$output = '<ul class="momentry-results">';
foreach ($data['hits'] as $hit) {
$output .= sprintf(
'<li>%s <a href="%s?start=%s">播放</a></li>',
esc_html($hit['text']),
$hit['media_url'],
$hit['start']
);
}
$output .= '</ul>';
return $output;
});
?>
```
**使用方式**:
```
[momentry_search query="charade" limit="5"]
```
### 3.6 WordPress REST API Endpoint
在 WordPress REST API 中註冊自定義端點:
```php
<?php
// 註冊 REST API 端點
add_action('rest_api_init', function() {
register_rest_route('momentry/v1', '/search', [
'methods' => 'POST',
'callback' => function($request) {
$response = wp_remote_post(
'https://api.momentry.ddns.net/api/v1/n8n/search',
[
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'query' => $request->get_param('query'),
'limit' => $request->get_param('limit', 10)
])
]
);
if (is_wp_error($response)) {
return new WP_Error('api_error', 'API 請求失敗');
}
return json_decode(wp_remote_retrieve_body($response));
}
]);
});
?>
```
**呼叫方式**:
```
POST /wp-json/momentry/v1/search
Body: {"query": "charade", "limit": 5}
```
---
## 4. 回應格式說明
### 4.1 n8n 格式 (`/api/v1/n8n/search`)
```json
{
"query": "charade",
"count": 10,
"hits": [
{
"id": "sentence_0001",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0001",
"text": "fun plot twists...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/video.mp4"
}
]
}
```
### 4.2 標準格式 (`/api/v1/search`)
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists...",
"score": 0.92
}
],
"query": "charade"
}
```
### 4.3 健康檢查
```json
{
"status": "ok",
"version": "0.1.0",
"uptime_ms": 123456
}
```
### 4.4 詳細健康檢查
```json
{
"status": "ok",
"version": "0.1.0",
"uptime_ms": 123456,
"services": {
"postgres": {"status": "ok", "latency_ms": 42, "error": null},
"redis": {"status": "ok", "latency_ms": 0, "error": null},
"qdrant": {"status": "ok", "latency_ms": 15, "error": null},
"mongodb": {"status": "ok", "latency_ms": 0, "error": null}
}
}
```
### 4.5 處理進度
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"overall_progress": 75,
"processors": [
{"name": "asr", "status": "complete", "progress": 100},
{"name": "cut", "status": "complete", "progress": 100},
{"name": "yolo", "status": "progress", "progress": 35}
]
}
```
### 4.6 Probe 回應
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {
"filename": "/path/to/video.mp4",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "120.5",
"size": "12345678",
"bit_rate": "819200"
},
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "30/1",
"duration": "120.5"
}
]
}
```
---
## 5. HTTP 狀態碼
| 狀態 | 說明 | 解決 |
|------|------|------|
| 200 | 成功 | - |
| 400 | 請求格式錯誤 | 檢查 JSON 格式 |
| 404 | 端點或資源不存在 | 確認 URL 正確 |
| 500 | 伺服器內部錯誤 | 檢查 API 服務日誌 |
| 502 | API 服務未啟動 | 見下方說明 |
### 502 Bad Gateway 解決
```bash
# 檢查服務狀態
launchctl list | grep momentry.api
# 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 或使用本地測試
curl http://localhost:3002/health
```
---
## 6. 常見問題
### Q: 為什麼有兩個 URL
| URL | 用途 |
|-----|------|
| `localhost:3002` | 直接訪問,繞過反向代理 |
| `api.momentry.ddns.net` | 通過 Caddy 反向代理 |
### Q: 兩者功能相同嗎?
是的,所有端點和功能完全相同。
### Q: n8n webhook-test 返回 404
需在 n8n UI 中點擊 "Execute workflow" 按鈕後才能使用測試 Webhook。
### Q: 生產環境 webhook 返回 404
需將 workflow 切換為 Active 狀態(右上角開關)。
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽
- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 詳細指南
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 詳細指南

102
docs/API_INDEX.md Normal file
View File

@@ -0,0 +1,102 @@
# Momentry Core API 文件總覽
| 項目 | 內容 |
|------|------|
| 版本 | V2.1 |
| 日期 | 2026-03-25 |
---
## 文件架構
```
docs/
├── API_INDEX.md ← 本文件:總覽與入口
├── API_ENDPOINTS.md ← API 端點完整說明
├── API_EXAMPLES.md ← 完整範例總覽curl / n8n / WordPress
├── DEMO_MANUAL.md ← ⭐ 示範手冊(含 Demo API Key
├── API_N8N_GUIDE.md ← n8n 詳細指南
├── API_WORDPRESS_GUIDE.md ← WordPress 詳細指南
├── API_CURL_EXAMPLES.md ← curl 快速範例
└── API_REFERENCE.md ← 詳細技術參考
```
---
## 快速選擇指南
| 需求 | 閱讀文件 |
|------|----------|
| **我要快速開始測試** | ⭐ [DEMO_MANUAL.md](./DEMO_MANUAL.md) |
| **我要查看所有範例** | [API_EXAMPLES.md](./API_EXAMPLES.md) |
| 我想了解有哪些 API 端點 | [API_ENDPOINTS.md](./API_ENDPOINTS.md) |
| 我要在 n8n workflow 中呼叫 API | [DEMO_MANUAL.md](./DEMO_MANUAL.md#2-n8n-範例) |
| 我要在 WordPress 中呼叫 API | [DEMO_MANUAL.md](./DEMO_MANUAL.md#3-wordpress-範例) |
| 我要用 curl 快速測試 | [DEMO_MANUAL.md](./DEMO_MANUAL.md#1-curl-範例) |
---
## 認證
### Demo API Key
```
API Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
Key ID: muser_68600856036340bcafc01930eb4bd839
過期日: 2027-03-25
```
### 使用方式
```bash
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3002/api/v1/videos
```
---
## API URL 選擇
| 環境 | URL | 使用時機 |
|------|-----|----------|
| **本地開發** | `http://localhost:3002` | 開發/測試、繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | n8n、WordPress、遠端系統 |
### 何時用哪個
**使用 `localhost:3002`**
- 本地終端機測試
- 當反向代理有問題時
- 快速除錯
**使用 `api.momentry.ddns.net`**
- n8n workflow
- WordPress 網站
- 外部系統整合
---
## 常見問題
### Q: API 返回 401 錯誤?
API Key 無效或過期。請使用 Demo API Key 或建立新的 API Key。
### Q: API 返回 502 錯誤?
```bash
# 檢查服務狀態
launchctl list | grep momentry.api
# 如未啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
### Q: 兩個 URL 功能相同嗎?
是的,所有端點完全相同,只是訪問路徑不同。
---
## 相關文件
- [DEMO_MANUAL.md](./DEMO_MANUAL.md) - ⭐ 示範手冊(推薦新手)
- [INSTALL_MOMENTRY_API.md](./INSTALL_MOMENTRY_API.md) - API 服務安裝指南
- [PENDING_ISSUES.md](./PENDING_ISSUES.md) - 待解決問題追蹤

View File

@@ -0,0 +1,195 @@
# API Key Management System Architecture
## System Overview
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ API Key Management System │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CLI │ │ HTTP API │ │ Service │ │ External │ │
│ │ Layer │────▶│ Layer │────▶│ Layer │────▶│ Services │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Core Modules │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Service │ │Validator│ │ Anomaly │ │Rotation │ │ Cleanup │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Webhook │ │Encrypt │ │Blacklist│ │ Report │ │ Error │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PostgreSQL │ │ Redis │ │ External │ │
│ │ (Storage) │ │ (Cache) │ │ (Gitea/n8n)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
## Module Dependencies
```
┌──────────────┐
│ models.rs │
│ (Types) │
└──────┬───────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ service.rs │ │ error.rs │ │ validator.rs │
│ (Core CRUD) │ │ (Errors) │ │ (Cache+Rate) │
└───────┬───────┘ └───────────────┘ └───────────────┘
│ ┌───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ anomaly.rs │ │ rotation.rs │ │ blacklist.rs │
│ (Detection) │ │ (Rotation) │ │ (IP Block) │
└───────────────┘ └───────────────┘ └───────────────┘
```
## Request Flow
```
Client Request
┌─────────────┐
│ CLI/API │
└──────┬──────┘
┌─────────────┐ ┌─────────────┐
│ Rate Limit │────▶│ IP Blacklist│
│ Check │ │ Check │
└──────┬──────┘ └──────┬──────┘
│ │
└─────────┬─────────┘
┌───────────────┐
│ Hash API Key │
└───────┬───────┘
┌───────────────┐ ┌───────────────┐
│ Cache Lookup │────▶│ PostgreSQL │
└───────┬───────┘ │ Lookup │
│ └───────┬───────┘
│ │
└──────────┬──────────┘
┌───────────────┐
│ Validate │
│ (Status, │
│ Expiry) │
└───────┬───────┘
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Valid │ │ Invalid │ │ Error │
│ Response│ │ Response │ │ Response │
└──────────┘ └──────────┘ └──────────┘
```
## Database Schema
```
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ api_keys │ │ api_key_audit_ │ │
│ ├─────────────────┤ │ log │ │
│ │ id │ ├─────────────────┤ │
│ │ key_id │─────▶│ id │ │
│ │ key_hash │ │ key_id (FK) │ │
│ │ name │ │ action │ │
│ │ key_type │ │ ip_address │ │
│ │ status │ │ details │ │
│ │ expires_at │ └─────────────────┘ │
│ │ ... │ │
│ └─────────────────┘ ┌─────────────────┐ │
│ │ api_key_anomalies│ │
│ ┌─────────────────┐ ├─────────────────┤ │
│ │ gitea_tokens │ │ id │ │
│ ├─────────────────┤ │ key_id (FK) │ │
│ │ id │ │ anomaly_type │ │
│ │ gitea_token_id │ │ severity │ │
│ │ token_name │ │ details │ │
│ │ scopes │ └─────────────────┘ │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ n8n_api_keys │ │
│ ├─────────────────┤ │
│ │ id │ │
│ │ n8n_key_id │ │
│ │ label │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## External Integrations
```
┌─────────────────────────────────────────────────────────────────────────────────┐
│ External Integrations │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Gitea │ │ n8n │ │ Webhook │ │
│ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │
│ │ • Create Token │ │ • Create API Key│ │ • Key Created │ │
│ │ • List Tokens │ │ • List API Keys │ │ • Key Revoked │ │
│ │ • Delete Token │ │ • Delete API Key│ │ • Anomaly │ │
│ │ • Verify Token │ │ • Verify │ │ • Rate Limited │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
```
## Security Layers
```
┌─────────────────────────────────────────────────────────────────┐
│ Security Layers │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: Network │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • IP Blacklist │ │
│ │ • Rate Limiting │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Layer 2: Authentication │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • API Key Hash (SHA256) │ │
│ │ • Constant-time Comparison │ │
│ │ • Key Validation (Status, Expiry) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Layer 3: Monitoring │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ • Anomaly Detection │ │
│ │ • Audit Logging (Encrypted) │ │
│ │ • Webhook Notifications │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```

View File

@@ -0,0 +1,236 @@
# API Key Management Integration Tests
## Test Environment Setup
### Prerequisites
```bash
# Start services
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
# Set environment variables
export DATABASE_URL="postgres://accusys@localhost:5432/momentry"
export REDIS_URL="redis://:accusys@localhost:6379"
export GITEA_URL="http://localhost:3000"
export N8N_URL="https://n8n.momentry.ddns.net"
```
### Run Tests
```bash
# Run all unit tests
cargo test --lib
# Run API key specific tests
cargo test --lib api_key
# Run with output
cargo test --lib -- --nocapture
```
---
## Test Cases
### 1. API Key Creation
```bash
# Test: Create a service key
momentry api-key create test-key --key-type service --ttl 90
# Expected Output:
# ✅ API Key created successfully!
# Key ID: msvc_...
# API Key: msvc_...
# Expires: 2026-06-19
```
### 2. API Key Validation
```bash
# Test: Validate the created key
momentry api-key validate --key "msvc_..."
# Expected Output:
# ✅ API Key is valid
# Key ID: msvc_...
# Name: test-key
# Type: service
```
### 3. API Key Listing
```bash
# Test: List all keys
momentry api-key list
# Expected Output:
# 📋 API Key List
# ┌────────────────────────────────────────────────────────────────────────────┐
# │ Status │ Name │ Type │ Usage │ Last Used │
# ├────────────────────────────────────────────────────────────────────────────┤
# │ ✓ active │ test-key │ "service" │ 0 │ never │
# └────────────────────────────────────────────────────────────────────────────┘
```
### 4. API Key Statistics
```bash
# Test: Show statistics
momentry api-key stats
# Expected Output:
# 📊 API Key Statistics
# ┌─────────────────────────────────────────┐
# │ Total Keys: 1 │
# │ Active Keys: 1 │
# │ Expired Keys: 0 │
# └─────────────────────────────────────────┘
```
### 5. Gitea Token Creation
```bash
# Test: Create Gitea token
momentry gitea create \
--username admin \
--password "Test3200Test3200Test3200" \
--token-name "test-token" \
--scopes "read:repository,write:repository"
# Expected Output:
# ✅ Gitea Token created successfully!
# Token ID: ...
# SHA1: ...
```
### 6. n8n API Key Creation
```bash
# Test: Create n8n API key
momentry n8n create \
--api-key "existing-n8n-key" \
--label "test-key" \
--expires-in-days 90
# Expected Output:
# ✅ n8n API Key created successfully!
# Key ID: ...
# API Key: ...
```
---
## Automated Test Script
```bash
#!/bin/bash
# integration_test.sh
set -e
echo "=== API Key Integration Tests ==="
# 1. Create API key
echo "1. Testing API key creation..."
momentry api-key create integration-test --key-type service --ttl 30
echo "✅ API key created"
# 2. List keys
echo "2. Testing API key listing..."
momentry api-key list
echo "✅ API key list OK"
# 3. Show stats
echo "3. Testing statistics..."
momentry api-key stats
echo "✅ Statistics OK"
# 4. Test Gitea integration
echo "4. Testing Gitea integration..."
GITEA_URL="http://localhost:3000" \
momentry gitea list --username admin --password "Test3200Test3200Test3200"
echo "✅ Gitea integration OK"
echo ""
echo "=== All Tests Passed ==="
```
---
## Unit Test Coverage
| Module | Tests | Status |
|--------|-------|--------|
| `models.rs` | 0 | ✅ |
| `service.rs` | 5 | ✅ |
| `validator.rs` | 2 | ✅ |
| `gitea.rs` | 3 | ✅ |
| `n8n.rs` | 2 | ✅ |
| `rotation.rs` | 4 | ✅ |
| `anomaly.rs` | 0 | ✅ |
| `blacklist.rs` | 5 | ✅ |
| `encryption.rs` | 2 | ✅ |
| `webhook.rs` | 2 | ✅ |
| `error.rs` | 3 | ✅ |
| `report.rs` | 1 | ✅ |
| `cleanup.rs` | 1 | ✅ |
| **Total** | **30** | **✅** |
---
## CI/CD Integration
### GitHub Actions / Gitea Actions
```yaml
name: API Key Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: accusys
POSTGRES_DB: momentry_test
ports:
- 5432:5432
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test --lib api_key
```
---
## Troubleshooting
### Common Issues
1. **Database connection failed**
```bash
# Check PostgreSQL status
pg_isready -h localhost -p 5432
```
2. **Redis connection failed**
```bash
# Check Redis status
redis-cli -a accusys ping
```
3. **Gitea authentication failed**
```bash
# Verify credentials
curl -u admin:password http://localhost:3000/api/v1/user
```

699
docs/API_KEY_MANAGEMENT.md Normal file
View File

@@ -0,0 +1,699 @@
# Momentry API Key 管理系統設計
| 項目 | 內容 |
|------|------|
| 版本 | V1.2 |
| 日期 | 2026-03-21 |
| 狀態 | 開發中 |
---
## 1. 概述
### 1.1 目標
建立安全的 API Key 管理機制,支援:
- 多類型 API Key系統、用戶、服務
- 自動過期與輪換
- 異常使用偵測
- 強制更新機制
- 完整審計日誌
- Gitea Token 整合
- n8n API Key 整合
### 1.2 設計原則
| 原則 | 說明 |
|------|------|
| 最小權限 | 每個 Key 僅授予必要權限 |
| 定期輪換 | 自動過期強制更新 |
| 追蹤可審 | 所有操作都有日誌 |
| 分離儲存 | Key 與使用者資料分離 |
---
## 2. API Key 類型
### 2.1 Key 類型矩陣
| 類型 | 前綴 | 用途 | 預設有效期 | 輪換方式 |
|------|------|------|------------|----------|
| `system` | `msys_` | 系統內部服務 | 365 天 | 手動 |
| `user` | `muser_` | 個人用戶 | 90 天 | 自動 |
| `service` | `msvc_` | 服務間通訊 | 180 天 | 自動 |
| `integration` | `mint_` | 第三方整合 | 30 天 | 強制更新 |
| `emergency` | `memg_` | 緊急存取 | 24 小時 | 一次性 |
### 2.2 Key 格式
```
{prefix}{uuid_v4}_{timestamp}_{checksum}
```
**範例:**
```
msys_a1b2c3d4-e5f6-7890-abcd-ef1234567890_1710998400_sha256
```
---
## 3. 資料庫 Schema
### 3.1 api_keys 表
```sql
CREATE TABLE api_keys (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) UNIQUE NOT NULL, -- 公開 Key ID
key_hash VARCHAR(128) NOT NULL, -- SHA256 哈希
key_prefix VARCHAR(8) NOT NULL, -- Key 前綴
name VARCHAR(128) NOT NULL, -- Key 名稱
key_type VARCHAR(32) NOT NULL, -- system/user/service/integration/emergency
user_id BIGINT, -- 關聯用戶 (nullable for system)
service_name VARCHAR(64), -- 服務名稱 (for service keys)
permissions JSONB NOT NULL DEFAULT '[]', -- 權限列表
expires_at TIMESTAMP, -- 過期時間
last_used_at TIMESTAMP, -- 最後使用時間
last_used_ip VARCHAR(45), -- 最後使用 IP
usage_count BIGINT DEFAULT 0, -- 使用次數
status VARCHAR(16) DEFAULT 'active', -- active/suspended/expired/revoked
rotation_required BOOLEAN DEFAULT FALSE, -- 強制輪換標記
rotation_reason VARCHAR(256), -- 輪換原因
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_api_keys_key_id ON api_keys(key_id);
CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);
CREATE INDEX idx_api_keys_type ON api_keys(key_type);
CREATE INDEX idx_api_keys_status ON api_keys(status);
CREATE INDEX idx_api_keys_expires ON api_keys(expires_at);
```
### 3.2 api_key_audit_log 表
```sql
CREATE TABLE api_key_audit_log (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL, -- created/used/rotated/revoked/expired/suspended
actor VARCHAR(64), -- 操作者 (user_id or 'system')
ip_address VARCHAR(45),
user_agent VARCHAR(512),
request_path VARCHAR(256),
response_code INTEGER,
details JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_audit_key_id ON api_key_audit_log(key_id);
CREATE INDEX idx_audit_action ON api_key_audit_log(action);
CREATE INDEX idx_audit_created ON api_key_audit_log(created_at);
```
### 3.3 api_key_rotation_log 表
```sql
CREATE TABLE api_key_rotation_log (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) NOT NULL,
old_key_id VARCHAR(64),
new_key_id VARCHAR(64),
rotation_type VARCHAR(32) NOT NULL, -- scheduled/manual/forced/emergency
reason VARCHAR(256),
triggered_by VARCHAR(64), -- system/user/scheduler
grace_period_end TIMESTAMP, -- 寬限期結束時間
created_at TIMESTAMP DEFAULT NOW()
);
```
---
## 4. API Key 狀態機
```
┌──────────────┐
│ created │
└──────┬───────┘
┌────────────────────┐
│ active │◄─────────────┐
└─────────┬──────────┘ │
│ │
┌─────────────┼─────────────┐ │
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ suspended │ │ expired │ │ revoked │─────┘
└──────────┘ └──────────┘ └──────────┘
```
### 狀態轉換規則
| 從 | 到 | 觸發條件 |
|----|----|----------|
| created | active | 啟用 Key |
| active | suspended | 異常使用偵測 |
| active | expired | 達到過期時間 |
| active | revoked | 手動撤銷 |
| suspended | active | 解除鎖定 |
| suspended | revoked | 確認異常 |
| expired | active | 重新啟用 |
---
## 5. 異常偵測機制
### 5.1 異常指標
| 指標 | 閾值 | 處置 |
|------|------|------|
| 每分鐘請求數 | > 1000 | 警告 |
| 每小時請求數 | > 10000 | 鎖定 |
| 錯誤率 | > 50% | 警告 |
| 不同 IP 數 | > 5/小時 | 警告 |
| 非工作時間使用 | 深夜請求 | 警告 |
| 異常模式 | 暴力破解 | 鎖定 |
### 5.2 異常處理流程
```
異常偵測
┌─────────┐
│ 分析 │──→ 排除正常流量
└────┬────┘
┌─────────┐
│ 評估 │──→ 輕微 → 警告
└────┬────┘
┌─────────┐
│ 處置 │──→ 嚴重 → 鎖定 + 輪換
└─────────┘
```
---
## 6. 強制更新機制
### 6.1 觸發條件
| 條件 | 嚴重性 | 動作 |
|------|--------|------|
| 疑似洩露 | 高 | 立即停用 + 強制輪換 |
| 異常使用 | 中 | 警告 + 建議輪換 |
| 計劃性維護 | 低 | 通知 + 排程輪換 |
| 政策要求 | 高 | 強制輪換 |
| 過期 | 低 | 停用 + 通知 |
### 6.2 強制輪換流程
```
1. 系統偵測到需要強制更新
2. 建立新 Key保留舊 Key 在寬限期內)
3. 發送通知Email/Slack/Redis PubSub
4. 寬限期開始(預設 24 小時)
├── 在寬限期內更新 → 完成輪換
└── 寬限期結束 → 舊 Key 停用
```
### 6.3 寬限期配置
| Key 類型 | 寬限期 |
|----------|--------|
| system | 72 小時 |
| user | 24 小時 |
| service | 48 小時 |
| integration | 24 小時 |
| emergency | 0 小時 |
---
## 7. CLI 管理命令
### 7.1 命令列表
```bash
# Key 管理
momentry api-key create --name "My Key" --type user --permissions read,write
momentry api-key list --type user
momentry api-key info <key_id>
momentry api-key revoke <key_id> --reason "安全原因"
# 輪換管理
momentry api-key rotate <key_id> # 正常輪換
momentry api-key force-rotate <key_id> # 強制輪換
momentry api-key rotation-status <key_id> # 查看輪換狀態
# 異常管理
momentry api-key suspend <key_id> --reason "異常使用"
momentry api-key unsuspend <key_id>
momentry api-key blacklist <key_id> # 列入黑名單
# 審計
momentry api-key audit <key_id> --since 7d
momentry api-key stats --type service --period 30d
```
### 7.2 輸出範例
```bash
$ momentry api-key list --type service
┌────────────────────────────────────┬─────────┬──────────────┬────────────────┐
│ Key ID │ Name │ Status │ Expires │
├────────────────────────────────────┼─────────┼──────────────┼────────────────┤
│ msvc_a1b2c3d4_1710998400_sha256 │ N8N │ active │ 2026-09-21 │
│ msvc_e5f6g7h8_1713600000_sha256 │ OpenCode│ rotation_req │ 2026-09-21 │
└────────────────────────────────────┴─────────┴──────────────┴────────────────┘
⚠️ 1 個 Key 需要輪換
```
---
## 8. 實現計畫
### Phase 1: 核心功能
- [ ] 資料庫 Schema
- [ ] Key 生成與哈希
- [ ] 基本 CRUD API
- [ ] 過期檢查
### Phase 2: 安全機制
- [ ] 異常偵測
- [ ] 自動鎖定
- [ ] 強制輪換
- [ ] 寬限期管理
### Phase 3: 管理工具
- [ ] CLI 命令
- [ ] 審計日誌
- [ ] 統計報表
- [ ] 通知系統
### Phase 4: 自動化
- [ ] 定時輪換排程
- [ ] Prometheus 指標
- [ ] Alertmanager 整合
- [ ] 自動化回應
---
## 9. 安全考量
### 9.1 Key 儲存
- 明文 Key 只顯示一次(創建時)
- 儲存時使用 SHA256 哈希
- 使用 Fernet 對稱加密敏感配置
### 9.2 傳輸安全
- 所有 API 必須使用 HTTPS
- Key 在 Header 中傳輸X-API-Key
- 避免 Key 在 URL 中
### 9.3 存取控制
- 只有管理員可創建/撤銷 Key
- 用戶只能管理自己的 Key
- 系統 Key 需要特殊權限
---
## 10. 環境變數配置
```bash
# API Key 管理
MOMENTRY_API_KEY_GRACE_PERIOD=86400 # 寬限期(秒)
MOMENTRY_API_KEY_MAX_PER_USER=5 # 每用戶最大 Key 數
MOMENTRY_API_KEY_ROTATION_DAYS=90 # 自動輪換天數
# 異常偵測
MOMENTRY_API_KEY_RATE_LIMIT=1000 # 每分鐘限制
MOMENTRY_API_KEY_ERROR_THRESHOLD=0.5 # 錯誤率閾值
MOMENTRY_API_KEY_IP_LIMIT=5 # 每小時 IP 限制
# 通知
MOMENTRY_API_KEY_ALERT_WEBHOOK= # 異常通知 webhook
```
---
## 11. Gitea API Token 整合
### 11.1 概述
支援透過 API Key 管理系統建立和管理 Gitea Personal Access Tokens採用「建立時納管」模式。
### 11.2 納管模式
```
使用者提供帳號密碼 → 呼叫 Gitea API 建立 Token → 明文只顯示一次 → 同步儲存至管理系統
```
**特點:**
- Token 明文僅在建立時取得
- 管理系統記錄 Token 元數據(不含明文)
- 支援本地查詢和刪除
### 11.3 資料庫結構
```sql
CREATE TABLE gitea_tokens (
id SERIAL PRIMARY KEY,
gitea_token_id BIGINT NOT NULL, -- Gitea 內部 Token ID
gitea_user VARCHAR(128) NOT NULL, -- Gitea 用戶名
token_name VARCHAR(128) NOT NULL, -- Token 名稱
token_last_eight VARCHAR(8) NOT NULL, -- SHA1 最後 8 碼(顯示用)
scopes JSONB DEFAULT '[]', -- 權限範圍
api_key_id VARCHAR(48), -- 關聯的 API Key ID可選
last_verified TIMESTAMP, -- 最後驗證時間
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(gitea_user, token_name)
);
```
### 11.4 Token 權限範圍
| 範圍 | 說明 |
|------|------|
| `read:repository` | 讀取倉庫 |
| `write:repository` | 寫入倉庫 |
| `read:issue` | 讀取議題 |
| `write:issue` | 寫入議題 |
| `read:user` | 讀取用戶資訊 |
| `write:write` | 修改用戶資訊 |
| `read:organization` | 讀取組織 |
| `write:organization` | 修改組織 |
| `read:package` | 讀取套件 |
| `write:package` | 發布套件 |
| `read:notification` | 讀取通知 |
| `write:notification` | 修改通知 |
| `read:admin` | 管理員讀取 |
| `write:admin` | 管理員寫入 |
### 11.5 CLI 命令
#### 建立 Token
```bash
# 基本用法
momentry gitea create \
--username <gitea_user> \
--password <gitea_password> \
--token-name <token_name> \
--scopes "read:repository,write:repository"
# 範例:建立整合用 Token
momentry gitea create \
--username admin \
--password "MyPassword123" \
--token-name "ci-pipeline" \
--scopes "read:repository,write:repository,read:issue,write:issue"
```
**輸出範例:**
```
✅ Gitea Token created successfully!
┌─────────────────────────────────────────────────────────────────────────────┐
│ ⚠️ IMPORTANT: Save this token now - it will not be shown again! │
└─────────────────────────────────────────────────────────────────────────────┘
Token ID: 9
Token Name: ci-pipeline
SHA1: 9a4f282e9ba817b430082e6bff2c18e2ae38e480
Last 8: ae38e480
Authorization Header:
Authorization: token 9a4f282e9ba817b430082e6bff2c18e2ae38e480
```
#### 列出 Token
```bash
# 列出用戶的所有 Token
momentry gitea list \
--username <gitea_user> \
--password <gitea_password>
```
**輸出範例:**
```
📋 Gitea Tokens for user: admin
┌────────────────────────────────────────────────────────────────────────────┐
│ ID │ Name │ Last 8 │ Registered │
├────────────────────────────────────────────────────────────────────────────┤
│ 9 │ ci-pipeline │ ae38e480 │ ✓ │
│ 8 │ dev-token │ 1234abcd │ - │
└────────────────────────────────────────────────────────────────────────────┘
Total: 2 token(s)
```
#### 刪除 Token
```bash
# 刪除指定 Token
momentry gitea delete \
--username <gitea_user> \
--password <gitea_password> \
--token-name <token_name>
```
#### 查詢本地記錄
```bash
# 查詢已納管的 Token 記錄
momentry gitea verify --token-name <token_name>
```
**輸出範例:**
```
📋 Gitea Token: ci-pipeline
User: admin
Token ID: 9
Last 8: ae38e480
Scopes: ["read:repository","write:repository"]
Created: 2026-03-21 06:44:55.577586 UTC
Last Verified: never
```
### 11.6 使用範圍
#### 適用場景
| 場景 | 說明 |
|------|------|
| CI/CD 整合 | 建立專用 Token 用於自動化流程 |
| 服務間通訊 | 建立 Token 供其他服務存取 Gitea API |
| 開發環境 | 為開發者建立短期 Token |
| 監控整合 | 建立只讀 Token 用於監控和報告 |
#### 限制
| 限制 | 說明 |
|------|------|
| 明文 Token | 僅在建立時取得,無法再次查詢 |
| 管理 API | 需要帳號密碼BasicAuth |
| Token 驗證 | 只能透過 API 呼叫驗證有效性 |
| 同步刪除 | 本地刪除不會自動同步到 Gitea |
### 11.7 環境變數
```bash
# Gitea 連線設定
GITEA_URL=http://localhost:3000 # Gitea API URL
```
### 11.8 安全考量
| 項目 | 措施 |
|------|------|
| 密碼傳輸 | 僅在 CLI 命令中使用,不儲存 |
| Token 儲存 | 本地僅存元數據,不含明文 |
| 權限最小化 | 建議僅授予必要權限 |
| 定期輪換 | 建議定期更新 Token |
---
## 12. n8n API Key 整合
### 12.1 概述
支援透過 API Key 管理系統建立和管理 n8n API Keys採用「建立時納管」模式。
### 12.2 納管模式
```
使用者提供現有 n8n API Key → 呼叫 n8n API 建立新 Key → 明文只顯示一次 → 同步儲存至管理系統
```
**特點:**
- 需要一個現有的 n8n API Key 作為管理憑證
- API Key 明文僅在建立時取得
- 管理系統記錄 Key 元數據(不含明文)
- 支援本地查詢和刪除
### 12.3 資料庫結構
```sql
CREATE TABLE n8n_api_keys (
id SERIAL PRIMARY KEY,
n8n_key_id VARCHAR(64) UNIQUE NOT NULL, -- n8n 內部 Key ID
label VARCHAR(100) NOT NULL, -- Key 標籤
api_key_last_eight VARCHAR(8) NOT NULL, -- API Key 最後 8 碼(顯示用)
momentry_api_key_id VARCHAR(48), -- 關聯的 API Key ID可選
expires_at TIMESTAMP WITH TIME ZONE, -- 過期時間
last_verified TIMESTAMP WITH TIME ZONE, -- 最後驗證時間
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```
### 12.4 認證方式
n8n 使用 JWT-based API Key透過 `X-N8N-API-KEY` Header 認證:
```bash
curl -H "X-N8N-API-KEY: <your-api-key>" https://n8n.example.com/api/v1/workflows
```
### 12.5 CLI 命令
#### 建立 API Key
```bash
# 基本用法
momentry n8n create \
--api-key <existing_n8n_api_key> \
--label <key_label> \
--expires-in-days <days>
# 範例:建立 CI/CD 用 Key
momentry n8n create \
--api_key "n8n_api_xxxxxxxxxxxx" \
--label "ci-pipeline" \
--expires-in-days 90
```
**輸出範例:**
```
✅ n8n API Key created successfully!
┌─────────────────────────────────────────────────────────────────────────────┐
│ ⚠️ IMPORTANT: Save this API key now - it will not be shown again! │
└─────────────────────────────────────────────────────────────────────────────┘
Key ID: abc123-def456
Label: ci-pipeline
API Key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Usage:
curl -H 'X-N8N-API-KEY: eyJhbGciOiJIUz...' https://n8n.momentry.ddns.net/api/v1/workflows
```
#### 列出 API Keys
```bash
# 列出所有 API Keys
momentry n8n list --api-key <existing_n8n_api_key>
```
**輸出範例:**
```
📋 n8n API Keys
┌────────────────────────────────────────────────────────────────────────────┐
│ Label │ ID │
├────────────────────────────────────────────────────────────────────────────┤
│ ci-pipeline │ abc123-def456-789 │
│ monitoring │ xyz789-abc123-456 │
└────────────────────────────────────────────────────────────────────────────┘
Total: 2 key(s)
```
#### 刪除 API Key
```bash
# 刪除指定 API Key
momentry n8n delete \
--api-key <existing_n8n_api_key> \
--label <key_label>
```
#### 查詢本地記錄
```bash
# 查詢已納管的 API Key 記錄
momentry n8n verify --label <key_label>
```
**輸出範例:**
```
📋 n8n API Key: ci-pipeline
Key ID: abc123-def456
Last 8: ...JVCJ9
Created: 2026-03-21 06:44:55.577586 UTC
Expires: 2026-06-19 06:44:55.577586 UTC
Last Verified: never
```
### 12.6 使用範圍
#### 適用場景
| 場景 | 說明 |
|------|------|
| CI/CD 整合 | 建立專用 Key 用於自動化流程 |
| 監控整合 | 建立只讀 Key 用於監控工作流狀態 |
| 服務間通訊 | 建立 Key 供其他服務呼叫 n8n API |
| 開發環境 | 為開發者建立短期 Key |
#### 限制
| 限制 | 說明 |
|------|------|
| 明文 API Key | 僅在建立時取得,無法再次查詢 |
| 管理憑證 | 需要一個現有的 n8n API Key |
| 本地刪除 | 不會自動同步到 n8n |
| 權限範圍 | 非 Enterprise 版無細粒度權限 |
### 12.7 環境變數
```bash
# n8n 連線設定
N8N_URL=https://n8n.momentry.ddns.net # n8n API URL
```
### 12.8 安全考量
| 項目 | 措施 |
|------|------|
| 管理 Key | 需妥善保管,作為管理其他 Key 的憑證 |
| API Key 儲存 | 本地僅存元數據,不含明文 |
| 過期機制 | 建議設定過期時間 |
| 定期輪換 | 建議定期更新 Key |
---
## 13. 參考文檔
- PostgreSQL Schema
- Redis Key 設計( MOMENTRY_CORE_REDIS_KEYS.md
- 監控系統MOMENTRY_CORE_MONITORING.md
- Gitea 安裝指南INSTALL_GITEA.md
- n8n API 文件https://docs.n8n.io/api/authentication/

View File

@@ -0,0 +1,399 @@
# API Key Management 優化計畫
| 項目 | 內容 |
|------|------|
| 版本 | V1.0 |
| 日期 | 2026-03-21 |
| 狀態 | 規劃中 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-21 | 創建優化計畫 | OpenCode | - |
---
## 任務編碼規則
```
AKO-{類別}-{序號}
AKO = API Key Optimization
類別:
- CODE = 程式碼品質
- PERF = 效能優化
- SEC = 安全性
- FEAT = 功能增強
- DOC = 文件
```
---
## Phase 1: 程式碼品質 (CODE)
| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 |
|------|------|------|--------|----------|------|
| AKO-CODE-01 | 修復 from_str 警告 | 重命名為 `parse_scope` 或實作 `FromStr` trait | 🔴 高 | 0.5h | ⏳ 待辦 |
| AKO-CODE-02 | 函數參數重構 | 使用 Config struct 減少參數數量 | 🔴 高 | 1h | ⏳ 待辦 |
| AKO-CODE-03 | 抽象 CRUD Trait | 建立 `ExternalTokenStore` trait 統一 Gitea/n8n | 🟡 中 | 3h | ⏳ 待辦 |
| AKO-CODE-04 | 錯誤處理統一 | 使用 `thiserror` 定義自訂錯誤類型 | 🟡 中 | 2h | ⏳ 待辦 |
### AKO-CODE-01 細節
```rust
// Before
impl GiteaScope {
pub fn from_str(s: &str) -> Option<Self> { ... }
}
// After: Option A - Rename
impl GiteaScope {
pub fn parse(s: &str) -> Option<Self> { ... }
}
// After: Option B - Implement FromStr
impl std::str::FromStr for GiteaScope {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { ... }
}
```
### AKO-CODE-02 細節
```rust
// Before
pub async fn create_api_key(
&self,
key_id: &str,
key_hash: &str,
key_prefix: &str,
name: &str,
key_type: &str,
user_id: Option<i64>,
service_name: Option<&str>,
permissions: &serde_json::Value,
expires_at: Option<DateTime<Utc>>,
) -> Result<i64>
// After
pub struct CreateApiKeyConfig<'a> {
pub key_id: &'a str,
pub key_hash: &'a str,
pub key_prefix: &'a str,
pub name: &'a str,
pub key_type: &'a str,
pub user_id: Option<i64>,
pub service_name: Option<&'a str>,
pub permissions: &'a serde_json::Value,
pub expires_at: Option<DateTime<Utc>>,
}
pub async fn create_api_key(&self, config: CreateApiKeyConfig<'_>) -> Result<i64>
```
### AKO-CODE-03 細節
```rust
#[async_trait]
pub trait ExternalTokenStore<T> {
async fn create(&self, record: T) -> Result<i64>;
async fn get_by_label(&self, label: &str) -> Result<Option<T>>;
async fn list(&self) -> Result<Vec<T>>;
async fn delete(&self, label: &str) -> Result<()>;
async fn update_verification(&self, label: &str) -> Result<()>;
}
```
---
## Phase 2: 效能優化 (PERF)
| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 |
|------|------|------|--------|----------|------|
| AKO-PERF-01 | 連線池配置外部化 | 使用環境變數控制 max_connections | 🟡 中 | 0.5h | ⏳ 待辦 |
| AKO-PERF-02 | API Key 驗證快取 | 使用 Moka 快取減少資料庫查詢 | 🔴 高 | 2h | ⏳ 待辦 |
| AKO-PERF-03 | 批次查詢優化 | 合併多次查詢為單一 SQL | 🟡 中 | 1h | ⏳ 待辦 |
| AKO-PERF-04 | 非同步日誌寫入 | 使用 channel 非同步寫入審計日誌 | 🟢 低 | 2h | ⏳ 待辦 |
### AKO-PERF-01 細節
```rust
// Before
let pool_options = PgPoolOptions::new()
.max_connections(10)
.acquire_timeout(std::time::Duration::from_secs(60));
// After
let max_conn = std::env::var("DB_MAX_CONNECTIONS")
.unwrap_or_else(|_| "10".to_string())
.parse()
.unwrap_or(10);
let pool_options = PgPoolOptions::new()
.max_connections(max_conn)
.acquire_timeout(std::time::Duration::from_secs(60));
```
### AKO-PERF-02 細節
```rust
use moka::future::Cache;
use std::time::Duration;
pub struct ApiKeyCache {
cache: Cache<String, CachedApiKey>,
}
pub struct CachedApiKey {
pub record: ApiKeyRecord,
pub cached_at: chrono::DateTime<chrono::Utc>,
}
impl ApiKeyCache {
pub fn new(ttl_seconds: u64, max_capacity: u64) -> Self {
Self {
cache: Cache::builder()
.time_to_live(Duration::from_secs(ttl_seconds))
.max_capacity(max_capacity)
.build(),
}
}
pub async fn get(&self, key_hash: &str) -> Option<ApiKeyRecord> {
self.cache.get(key_hash).await.map(|c| c.record)
}
pub async fn insert(&self, key_hash: String, record: ApiKeyRecord) {
self.cache.insert(key_hash, CachedApiKey {
record,
cached_at: chrono::Utc::now(),
}).await;
}
pub async fn invalidate(&self, key_hash: &str) {
self.cache.invalidate(key_hash).await;
}
}
```
---
## Phase 3: 安全性 (SEC)
| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 |
|------|------|------|--------|----------|------|
| AKO-SEC-01 | Constant-time 比較 | 使用 `subtle` crate 防止 timing attack | 🔴 高 | 0.5h | ⏳ 待辦 |
| AKO-SEC-02 | Rate Limiter | 限制驗證失敗重試次數 | 🔴 高 | 2h | ⏳ 待辦 |
| AKO-SEC-03 | IP 黑名單 | 支援封鎖特定 IP | 🟡 中 | 1.5h | ⏳ 待辦 |
| AKO-SEC-04 | 審計日誌加密 | 敏感欄位加密儲存 | 🟡 中 | 2h | ⏳ 待辦 |
| AKO-SEC-05 | Key 強度檢查 | 驗證建立的 Key 符合強度要求 | 🟢 低 | 1h | ⏳ 待辦 |
### AKO-SEC-01 細節
```rust
use subtle::ConstantTimeEq;
// Before
if stored_hash == computed_hash {
// valid
}
// After
if bool::from(stored_hash.as_bytes().ct_eq(computed_hash.as_bytes())) {
// valid
}
```
### AKO-SEC-02 細節
```rust
use moka::future::Cache;
pub struct RateLimiter {
attempts: Cache<String, AttemptInfo>,
max_attempts: u32,
window_seconds: u64,
}
pub struct AttemptInfo {
pub count: u32,
pub first_attempt: chrono::DateTime<chrono::Utc>,
pub locked_until: Option<chrono::DateTime<chrono::Utc>>,
}
impl RateLimiter {
pub async fn check(&self, identifier: &str) -> Result<()> {
if let Some(info) = self.attempts.get(identifier).await {
if let Some(locked_until) = info.locked_until {
if chrono::Utc::now() < locked_until {
anyhow::bail!("Account locked until {}", locked_until);
}
}
}
Ok(())
}
pub async fn record_failure(&self, identifier: &str) -> Result<()> {
let mut info = self.attempts.get(identifier).await
.unwrap_or(AttemptInfo {
count: 0,
first_attempt: chrono::Utc::now(),
locked_until: None,
});
info.count += 1;
if info.count >= self.max_attempts {
info.locked_until = Some(
chrono::Utc::now() + chrono::Duration::seconds(self.window_seconds as i64)
);
}
self.attempts.insert(identifier.to_string(), info).await;
Ok(())
}
pub async fn record_success(&self, identifier: &str) {
self.attempts.invalidate(identifier).await;
}
}
```
---
## Phase 4: 功能增強 (FEAT)
| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 |
|------|------|------|--------|----------|------|
| AKO-FEAT-01 | 批量建立 Key | 支援 JSON 檔案批量匯入 | 🟡 中 | 3h | ⏳ 待辦 |
| AKO-FEAT-02 | 批量撤銷 Key | 支援條件式批量撤銷 | 🟡 中 | 2h | ⏳ 待辦 |
| AKO-FEAT-03 | Key 匯出 | 匯出 Key 列表(不含明文) | 🟢 低 | 1.5h | ⏳ 待辦 |
| AKO-FEAT-04 | Key 匯入 | 匯入 Key 元數據 | 🟢 低 | 1.5h | ⏳ 待辦 |
| AKO-FEAT-05 | Webhook 通知 | 異常發生時發送 Webhook | 🟡 中 | 3h | ⏳ 待辦 |
| AKO-FEAT-06 | Email 通知 | Key 到期前提醒 | 🟢 低 | 4h | ⏳ 待辦 |
| AKO-FEAT-07 | 統計報表 | 生成使用統計報表 | 🟢 低 | 2h | ⏳ 待辦 |
| AKO-FEAT-08 | 清理過期記錄 | 自動清理過期的 Key 記錄 | 🟢 低 | 1h | ⏳ 待辦 |
### AKO-FEAT-01 細節
```json
// keys.json
{
"keys": [
{
"name": "ci-service-1",
"key_type": "service",
"permissions": ["read", "write"],
"ttl_days": 90
},
{
"name": "ci-service-2",
"key_type": "service",
"permissions": ["read"],
"ttl_days": 180
}
]
}
```
```bash
momentry api-key batch-create --file keys.json
```
### AKO-FEAT-05 細節
```rust
pub struct WebhookConfig {
pub url: String,
pub secret: String,
pub events: Vec<WebhookEvent>,
}
pub enum WebhookEvent {
KeyCreated,
KeyRevoked,
KeyExpired,
AnomalyDetected,
RotationRequired,
}
pub struct WebhookNotifier {
client: Client,
config: WebhookConfig,
}
impl WebhookNotifier {
pub async fn notify(&self, event: WebhookEvent, payload: serde_json::Value) -> Result<()> {
if !self.config.events.contains(&event) {
return Ok(());
}
let signature = self.sign(&payload);
self.client.post(&self.config.url)
.header("X-Webhook-Signature", signature)
.json(&serde_json::json!({
"event": event,
"timestamp": chrono::Utc::now(),
"payload": payload,
}))
.send()
.await?;
Ok(())
}
}
```
---
## Phase 5: 文件 (DOC)
| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 |
|------|------|------|--------|----------|------|
| AKO-DOC-01 | API 文件自動生成 | 使用 `utoipa` 生成 OpenAPI | 🟢 低 | 3h | ⏳ 待辦 |
| AKO-DOC-02 | CHANGELOG.md | 建立變更日誌 | 🟢 低 | 1h | ⏳ 待辦 |
| AKO-DOC-03 | 架構圖 | 添加系統架構圖 | 🟢 低 | 2h | ⏳ 待辦 |
| AKO-DOC-04 | 整合測試文件 | 記錄整合測試流程 | 🟢 低 | 1h | ⏳ 待辦 |
---
## 總工時估算
| Phase | 工時 | 任務數 |
|-------|------|--------|
| CODE | 6.5h | 4 |
| PERF | 5.5h | 4 |
| SEC | 7h | 5 |
| FEAT | 18h | 8 |
| DOC | 7h | 4 |
| **總計** | **44h** | **25** |
---
## 環境變數
```bash
# 效能
DB_MAX_CONNECTIONS=10
CACHE_TTL_SECONDS=300
CACHE_MAX_CAPACITY=10000
# 安全
RATE_LIMIT_MAX_ATTEMPTS=5
RATE_LIMIT_WINDOW_SECONDS=900
# 通知
WEBHOOK_URL=https://example.com/webhook
WEBHOOK_SECRET=your-secret
```
---
## 參考文件
- `docs/API_KEY_MANAGEMENT.md` - API Key 管理系統設計
- `docs/PENDING_ISSUES.md` - 待解決問題追蹤
- `src/core/api_key/` - API Key 模組

193
docs/API_N8N_GUIDE.md Normal file
View File

@@ -0,0 +1,193 @@
# n8n 呼叫 Momentry API 指南
| 項目 | 內容 |
|------|------|
| 版本 | V1.0 |
| 日期 | 2026-03-23 |
| 用途 | 在 n8n workflow 中呼叫 Momentry API |
---
## API URL
在 n8n HTTP Request Node 中,**請使用外部 URL**
```
https://api.momentry.ddns.net
```
> ⚠️ **不要使用** `localhost:3002`,因為 n8n 需要從外部訪問 API。
---
## 常用端點
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 健康檢查 |
| POST | `/api/v1/n8n/search` | 語意搜尋(推薦) |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片 |
| GET | `/api/v1/progress/:uuid` | 處理進度 |
---
## HTTP Request Node 設定
### 語意搜尋(推薦)
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Authentication: None
├── Send Body: ✓ (checked)
├── Content Type: JSON
└── Body:
{
"query": "={{ $json.query }}",
"limit": "={{ $json.limit || 10 }}"
}
```
### 測試用(固定關鍵字)
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Send Body: ✓
├── Content Type: JSON
└── Body:
{
"query": "charade",
"limit": 3
}
```
---
## 完整 Workflow 範例
### 基本搜尋 Workflow
```json
{
"name": "Momentry Video Search",
"nodes": [
{
"parameters": {},
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": {
"query": "charade",
"limit": 3
}
},
"name": "Search Video API",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300]
}
],
"connections": {
"Manual Trigger": {
"main": [[{"node": "Search Video API"}]]
}
}
}
```
---
## 動態查詢 Workflow
```json
{
"name": "Momentry Dynamic Search",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "search",
"responseMode": "lastNode"
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": {
"query": "={{ JSON.stringify($json.body.query) }}",
"limit": "={{ $json.body.limit || 5 }}"
}
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300]
}
],
"connections": {
"Webhook": {
"main": [[{"node": "Search API"}]]
}
}
}
```
---
## 常見錯誤
### 錯誤: 502 Bad Gateway
**原因**: API 服務未啟動
**解決**:
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
### 錯誤: "Your request is invalid"
**原因**: Body 格式設定錯誤
**正確設定**:
- `Content Type`: JSON
- `Body`: 必須是有效的 JSON 物件
---
## curl 測試
在終端機中測試 API
```bash
# 健康檢查
curl https://api.momentry.ddns.net/health
# 搜尋測試
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
```
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽
- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明
- [N8N_HTTP_REQUEST_GUIDE.md](./N8N_HTTP_REQUEST_GUIDE.md) - HTTP Request 詳細設定

447
docs/API_REFERENCE.md Normal file
View File

@@ -0,0 +1,447 @@
# Momentry Core API 安裝指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-23 | 更新端點與實際一致 | OpenCode | - |
---
## Base URL
| 環境 | URL | 說明 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 直接訪問 API繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 |
> **Note:** Port 3000 is used by Gitea. Momentry API server runs on **port 3002**.
### URL 使用時機
| 情境 | 建議 URL |
|------|----------|
| 本地開發/測試 | `http://localhost:3002` |
| n8n workflow | `https://api.momentry.ddns.net` |
| 外部系統整合 | `https://api.momentry.ddns.net` |
| 反向代理有問題時 | `http://localhost:3002` (繞過代理) |
## Authentication
Currently no authentication is required.
---
## Endpoints
### 1. Register Video
Register a video file to the system.
**Endpoint:** `POST /api/v1/register`
**Request Body:**
```json
{
"path": "/path/to/video.mp4"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `path` | string | Yes | Absolute path to video file |
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"video_id": 1,
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080
}
```
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-d '{"path": "/Users/accusys/test_video/BigBuckBunny_320x180.mp4"}'
```
---
### 2. Process Video (CLI)
Process video to generate ASR, CUT, YOLO, OCR, Face, Pose data.
**Note:** This is a CLI command, not an HTTP endpoint.
```bash
# Process video by UUID
cargo run --bin momentry -- process 5dea6618a606e7c7
# Or process by file path
cargo run --bin momentry -- process /path/to/video.mp4
```
---
### 3. Get Progress
Get real-time processing progress via Redis.
**Endpoint:** `GET /api/v1/progress/:uuid`
| Parameter | Type | Description |
|-----------|------|-------------|
| `uuid` | path | Video UUID (16 characters) |
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"processors": [
{
"name": "asr",
"status": "complete",
"current": 0,
"total": 0,
"message": "7 segments"
},
{
"name": "cut",
"status": "complete",
"current": 134,
"total": 134,
"message": "134 scenes"
},
{
"name": "yolo",
"status": "progress",
"current": 5000,
"total": 14315,
"message": "frame 5000"
},
{
"name": "ocr",
"status": "pending",
"current": 0,
"total": 0,
"message": ""
}
]
}
```
**Processor Status Values:**
- `pending` - Not started
- `info` - Starting/info message
- `progress` - In progress
- `complete` - Finished
- `error` - Failed
**Example:**
```bash
# Get progress for specific video
curl http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```
---
### 4. Natural Language Search
Search video chunks using natural language queries (RAG).
**Endpoint:** `POST /api/v1/search`
**Request Body:**
```json
{
"query": "What is the person saying about machine learning?",
"limit": 10,
"uuid": "5dea6618a606e7c7"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | string | Yes | Natural language search query |
| `limit` | integer | No | Max results (default: 10) |
| `uuid` | string | No | Filter by specific video UUID |
**Response (200):**
```json
{
"results": [
{
"uuid": "5dea6618a606e7c7",
"chunk_id": "0",
"chunk_type": "sentence",
"start_time": 5.5,
"end_time": 8.2,
"text": "Machine learning is a subset of artificial intelligence...",
"score": 0.85
}
],
"query": "What is the person saying about machine learning?"
}
```
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "machine learning", "limit": 5}'
```
---
### 4a. N8N Search (n8n Workflow Integration)
N8n-compatible search endpoint with standardized response format for direct workflow integration.
**Endpoint:** `POST /api/v1/n8n/search`
**Request Body:**
```json
{
"query": "sunset",
"limit": 10,
"uuid": "5dea6618a606e7c7"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | string | Yes | Natural language search query |
| `limit` | integer | No | Max results (default: 10) |
| `uuid` | string | No | Filter by specific video UUID |
**Response (200):**
```json
{
"query": "sunset",
"count": 2,
"hits": [
{
"id": "c_001",
"vid": "5dea6618a606e7c7",
"start": 5.5,
"end": 8.2,
"title": "Sunset Scene",
"text": "The sun slowly sets over the ocean...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/video.mp4"
}
]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `query` | string | Original search query |
| `count` | integer | Number of results |
| `hits[].id` | string | Chunk ID |
| `hits[].vid` | string | Video UUID |
| `hits[].start` | number | Start time in seconds |
| `hits[].end` | number | End time in seconds |
| `hits[].title` | string | Chunk title (from metadata or auto-generated) |
| `hits[].text` | string | Text content |
| `hits[].score` | number | Relevance score (0-1) |
| `hits[].media_url` | string | Full media URL (optional) |
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "sunset", "limit": 5}'
```
**Environment Variables:**
| Variable | Default | Description |
|----------|---------|-------------|
| `MOMENTRY_MEDIA_BASE_URL` | `https://wp.momentry.ddns.net` | Base URL for constructing media URLs |
---
### 5. Lookup Video
Lookup video UUID by path or get video details by UUID.
**Endpoint:** `GET /api/v1/lookup`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | query | No* | Video file path |
| `uuid` | query | No* | Video UUID |
*One of `path` or `uuid` is required.
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 120.5
}
```
**Example:**
```bash
# Lookup by path
curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
# Lookup by UUID
curl "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
---
### 6. List Videos
List all registered videos.
**Endpoint:** `GET /api/v1/videos`
**Response (200):**
```json
{
"videos": [
{
"uuid": "5dea6618a606e7c7",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080
}
]
}
```
**Example:**
```bash
curl http://localhost:3002/api/v1/videos
```
---
## Data Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ 完整工作流程 │
└─────────────────────────────────────────────────────────────────────┘
1. Register Video
POST /api/v1/register
└── UUID: 5dea6618a606e7c7
2. Process Video (CLI)
cargo run -- process 5dea6618a606e7c7
├── ASR (WhisperX) → 7 segments
├── CUT (PySceneDetect) → 134 scenes
├── YOLO (YOLOv8) → 10483 frames with objects
├── OCR (EasyOCR) → 40 frames with text
├── Face (OpenCV) → 44 frames with faces
└── Pose (YOLOv8-Pose) → 14315 frames
3. Monitor Progress (Real-time)
GET /api/v1/progress/:uuid
└── Redis Pub/Sub + Hash
4. Chunk (CLI)
cargo run -- chunk 5dea6618a606e7c7
└── Create chunks in database
5. Vectorize (CLI)
cargo run -- vectorize 5dea6618a606e7c7
└── Generate embeddings in Qdrant
6. Search (API)
POST /api/v1/search
└── Natural language query
```
---
## Processor Reference
| Processor | Model | Description |
|-----------|-------|-------------|
| **ASR** | WhisperX (faster-whisper) | Speech recognition + diarization |
| **CUT** | PySceneDetect | Scene detection/segmentation |
| **ASRX** | WhisperX | Speaker diarization |
| **YOLO** | YOLOv8n | Object detection |
| **OCR** | EasyOCR | Text recognition |
| **Face** | OpenCV Haar Cascade | Face detection |
| **Pose** | YOLOv8n-Pose | Pose estimation |
---
## Error Responses
**400 Bad Request**
```json
{
"error": "Invalid request body"
}
```
**404 Not Found**
```json
{
"error": "Resource not found"
}
```
**500 Internal Server Error**
```json
{
"error": "Internal server error"
}
```
---
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DATABASE_URL` | `postgres://accusys@localhost:5432/momentry` | PostgreSQL connection |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection |
| `REDIS_PASSWORD` | `accusys` | Redis password |
| `QDRANT_URL` | `http://localhost:6333` | Qdrant vector DB URL |
| `QDRANT_API_KEY` | - | Qdrant API key |
| `QDRANT_COLLECTION` | `chunks` | Qdrant collection name |
| `MOMENTRY_MEDIA_BASE_URL` | `https://wp.momentry.ddns.net` | Base URL for n8n search media URLs |
---
## Starting the Server
```bash
# Default (port 3002, since 3000 is Gitea)
cargo run --bin momentry -- server
# Custom host and port
cargo run --bin momentry -- server --host 127.0.0.1 --port 3002
```
---
## Quick Reference
| Task | Command |
|------|---------|
| Register video | `POST /api/v1/register` |
| Process video | `cargo run -- process <uuid>` |
| Check progress | `GET /api/v1/progress/<uuid>` |
| Search | `POST /api/v1/search` |
| List videos | `GET /api/v1/videos` |
| Lookup | `GET /api/v1/lookup?uuid=<uuid>` |

270
docs/API_WORDPRESS_GUIDE.md Normal file
View File

@@ -0,0 +1,270 @@
# WordPress 呼叫 Momentry API 指南
| 項目 | 內容 |
|------|------|
| 版本 | V1.0 |
| 日期 | 2026-03-23 |
| 用途 | 在 WordPress 中呼叫 Momentry API |
---
## API URL
在 WordPress 中呼叫 API**請使用外部 URL**
```
https://api.momentry.ddns.net
```
> ⚠️ WordPress 運行於瀏覽器端,無法直接訪問 `localhost`。
---
## 常用端點
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 健康檢查 |
| POST | `/api/v1/search` | 語意搜尋(標準格式) |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片 |
---
## PHP 範例
### 基本搜尋
```php
<?php
$api_url = 'https://api.momentry.ddns.net/api/v1/n8n/search';
$data = [
'query' => 'charade',
'limit' => 10
];
$response = wp_remote_post($api_url, [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($data),
'timeout' => 30
]);
if (is_wp_error($response)) {
echo '錯誤: ' . $response->get_error_message();
} else {
$body = json_decode(wp_remote_retrieve_body($response), true);
print_r($body['hits']);
}
?>
```
### 列出所有影片
```php
<?php
$api_url = 'https://api.momentry.ddns.net/api/v1/videos';
$response = wp_remote_get($api_url, ['timeout' => 30]);
if (!is_wp_error($response)) {
$body = json_decode(wp_remote_retrieve_body($response), true);
foreach ($body['videos'] as $video) {
echo $video['file_name'] . "\n";
}
}
?>
```
### 查詢特定影片
```php
<?php
$uuid = '5dea6618a606e7c7';
$api_url = 'https://api.momentry.ddns.net/api/v1/lookup?uuid=' . $uuid;
$response = wp_remote_get($api_url, ['timeout' => 30]);
if (!is_wp_error($response)) {
$video = json_decode(wp_remote_retrieve_body($response), true);
echo '檔案: ' . $video['file_name'] . "\n";
echo '時長: ' . $video['duration'] . ' 秒';
}
?>
```
---
## JavaScript 範例
### 使用 fetch
```javascript
// 搜尋影片
async function searchVideos(query, limit = 10) {
const response = await fetch('https://api.momentry.ddns.net/api/v1/n8n/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, limit })
});
if (!response.ok) {
throw new Error('API 請求失敗');
}
return await response.json();
}
// 使用範例
searchVideos('charade', 5)
.then(data => {
data.hits.forEach(hit => {
console.log(`${hit.text} (score: ${hit.score})`);
});
});
```
---
## WordPress Shortcode 範例
`functions.php` 中註冊短碼:
```php
<?php
// 註冊短碼
add_shortcode('momentry_search', function($atts) {
$atts = shortcode_atts([
'query' => '',
'limit' => '10'
], $atts);
if (empty($atts['query'])) {
return '<p>請提供搜尋關鍵字</p>';
}
$response = wp_remote_post('https://api.momentry.ddns.net/api/v1/n8n/search', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'query' => $atts['query'],
'limit' => (int)$atts['limit']
]),
'timeout' => 30
]);
if (is_wp_error($response)) {
return '<p>搜尋服務暫時無法使用</p>';
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (empty($data['hits'])) {
return '<p>找不到相關結果</p>';
}
$output = '<ul class="momentry-results">';
foreach ($data['hits'] as $hit) {
$output .= sprintf(
'<li>%s <a href="%s?start=%s">播放</a></li>',
esc_html($hit['text']),
$hit['media_url'],
$hit['start']
);
}
$output .= '</ul>';
return $output;
});
?>
```
**使用方式**:
```
[momentry_search query="charade" limit="5"]
```
---
## REST API Endpoint (WP >= 5.0)
在 WordPress REST API 中註冊自定義端點:
```php
<?php
// 註冊 REST API 端點
add_action('rest_api_init', function() {
register_rest_route('momentry/v1', '/search', [
'methods' => 'POST',
'callback' => function($request) {
$response = wp_remote_post(
'https://api.momentry.ddns.net/api/v1/n8n/search',
[
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'query' => $request->get_param('query'),
'limit' => $request->get_param('limit', 10)
])
]
);
if (is_wp_error($response)) {
return new WP_Error('api_error', 'API 請求失敗');
}
return json_decode(wp_remote_retrieve_body($response));
}
]);
});
?>
```
**呼叫方式**:
```
POST /wp-json/momentry/v1/search
Body: {"query": "charade", "limit": 5}
```
---
## 常見錯誤
### 錯誤: cURL error 7
**原因**: 無法連接到 API
**檢查**:
1. API 服務是否啟動
2. 網路是否可達
### 錯誤: 502 Bad Gateway
**原因**: API 服務未啟動
**解決**:
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## curl 測試
在終端機中測試:
```bash
# 健康檢查
curl https://api.momentry.ddns.net/health
# 搜尋測試
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":5}'
```
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽
- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例

View File

@@ -0,0 +1,331 @@
# 架構優化待評估事項
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-03-21 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-21 | 創建文件 | OpenCode |
| V1.1 | 2026-03-22 | 新增 TigerGraph/GraphRAG 說故事評估 | OpenCode |
---
## 架構優化項目
### 1. PostgreSQL → Redis 故障轉移
**說明**: 當 PostgreSQL 不可用時,降級到 Redis 作為臨時存儲
**複雜度**: 中
**影響範圍**:
- `src/core/db/postgres_db.rs`
- `src/core/db/redis_client.rs`
**風險**:
- 數據一致性問題
- 需要定義轉移策略
**優先級**: 待評估
---
### 2. 連接池監控
**說明**: 添加 PostgreSQL 和 Redis 連接池指標到 Prometheus
**複雜度**: 低
**影響範圍**:
- `src/core/db/postgres_db.rs`
- `src/core/db/redis_client.rs`
- `src/api/` (新增 metrics endpoint)
**風險**: 低
**優先級**: 待評估
---
### 3. Processor 重試機制
**說明**: 當 processor 失敗時自動重試
**複雜度**: 中
**影響範圍**:
- `src/core/processor/executor.rs` (新增 `run_with_retry` 方法)
- `src/core/processor/mod.rs` (導出 `RetryConfig`)
**風險**:
- 無限重試風險 → 已通過 `max_attempts` 控制
- 需要指數退避 → 已實現
**優先級**: ✅ 已完成 (2026-03-21)
**實作內容**:
- `RetryConfig` 結構體 (可配置重試次數、初始延遲、最大延遲、退避倍數)
- `run_with_retry()` 方法 (自動重試 + 指數退避)
- 單元測試覆蓋
**使用範例**:
```rust
use crate::core::processor::{PythonExecutor, RetryConfig};
let executor = PythonExecutor::new()?;
let config = RetryConfig::new(3).with_delay(1000).with_max_delay(30000);
executor.run_with_retry(
"asr_processor.py",
&["--input", "/path/to/video"],
Some(&uuid),
"asr",
Some(Duration::from_secs(3600)),
Some(config),
).await?;
```
---
### 4. PyO3 整合
**說明**: Python/Rust 直接調用,移除子進程調用
**複雜度**: 高
**影響範圍**:
- `src/core/processor/executor.rs` (重寫)
- Python 模組 (修改為可直接 import)
**風險**:
- Python GIL 問題
- 依賴版本兼容性
- 需要大量重寫
**優先級**: 低 (長期目標)
---
### 5. HTTP 健康端點
**說明**: 添加 `/health` API 用於外部監控
**複雜度**: 低
**影響範圍**:
- `src/api/server.rs` (新增路由)
**風險**: 低
**優先級**: ✅ 已完成 (2026-03-21)
**實作內容**:
- `GET /health` - 基本健康檢查 (status, version, uptime)
- `GET /health/detailed` - 詳細健康檢查 (PostgreSQL, Redis, Qdrant 狀態和延遲)
---
### 6. Gitea Actions CI/CD
**說明**: 配置 Gitea Actions 自動化 CI/CD在合併前執行檢查
**複雜度**: 中
**影響範圍**:
- `.gitea/workflows/` (新增 workflow 文件)
**優點**:
- 強制執行檢查,無法跳過
- 跨設備一致
- PR 審查前自動檢查
**風險**: 低
**優先級**: 待評估
---
### 7. Commit Message Lint
**說明**: 規範化提交訊息格式 (Conventional Commits)
**複雜度**: 低
**影響範圍**:
- `.git/hooks/commit-msg` (新增 hook)
- `~/dotfiles/hooks/commit-msg`
**風險**: 低
**優先級**: ✅ 已完成 (2026-03-21)
**實作內容**:
- 驗證格式: `<type>(<scope>): <description>`
- 有效類型: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert
- 警告: 第一行超過 72 字符
**範例**:
```
feat(api): add health check endpoint
fix(db): resolve connection pool issue
docs: update README
```
---
### 8. 自動化安裝腳本
**說明**: 創建腳本一次安裝所有開發工具
**複雜度**: 低
**影響範圍**:
- `scripts/install-dev-tools.sh` (新增)
**風險**: 低
**優先級**: 待評估
---
## 評估標準
| 標準 | 說明 |
|------|------|
| 業務價值 | 對用戶有何幫助 |
| 技術風險 | 實現難度和潛在問題 |
| 維護成本 | 未來維護負擔 |
| 依賴性 | 對其他系統的影響 |
---
## 評估記錄
| 項目 | 評估日期 | 決策 | 原因 |
|------|----------|------|------|
| PostgreSQL → Redis 故障轉移 | 待評估 | - | - |
| 連接池監控 | 待評估 | - | - |
| Processor 重試機制 | 2026-03-21 | 已完成 | - |
| PyO3 整合 | 待評估 | - | - |
| HTTP 健康端點 | 2026-03-21 | 已完成 | - |
| Gitea Actions CI/CD | 待評估 | - | - |
| Commit Message Lint | 2026-03-21 | 已完成 | - |
| 自動化安裝腳本 | 待評估 | - | - |
---
## 9. TigerGraph / Knowledge Graph 圖譜說故事
**說明**: 使用知識圖譜 (Knowledge Graph) 增強視頻敘事 (Storytelling) 和 RAG 檢索
**複雜度**: 高
**研究來源**:
- [TigerGraph Agentic GraphRAG](https://www.tigergraph.com/blog/agentic-graphrag-gives-ai-a-playbook-for-smarter-retrieval/) (2025-12-15)
- [TigerGraph GraphRAG GitHub](https://github.com/tigergraph/graphrag) (v1.2.0, 2026-03-11)
- [GraphRAG in 2026: Practitioner's Guide](https://medium.com/graph-praxis/graph-rag-in-2026-a-practitioners-guide-to-what-actually-works-dca4962e7517) (2026-02-22)
- [GraphRAG Complete Guide](https://medium.com/@brian-curry-research/graphrag-the-complete-guide-to-graph-powered-retrieval-augmented-generation-eeb58a6bb4d1) (2026-02-11)
### 核心概念
| 概念 | 說明 |
|------|------|
| **GraphRAG** | 結合知識圖譜與 RAG比傳統向量檢索更智能 |
| **知識圖譜** | 實體 (Entity) + 關係 (Relationship) 的結構化表示 |
| **多跳推理** | Multi-hop traversal可連接多個相關節點 |
| **混合檢索** | Graph traversal + Vector similarity 結合 |
### 對 Momentry 的潛在應用
```
視頻場景 → 實體識別 → 關係建立 → 故事圖譜
↓ ↓ ↓ ↓
CUT [人物, 物品, 動作] [誰做了什麼, 什麼導致什麼] [敘事鏈]
```
**1. 敘事圖譜構建 (Narrative Graph)**
- 從 Story/Chunks 模組提取實體
- 建立場景之間的因果關係
- 追蹤角色互動和情節發展
**2. 故事檢索增強**
```python
# 現有: Parent-child chunks
parent_chunk: "場景描述"
child_chunks: [詳細內容]
# 加入圖譜:
場景A --led_to--> 場景B
角色X --interacted_with--> 角色Y
主題Y --related_to--> 主題Z
```
**3. 查詢模式**
| 查詢類型 | 傳統 RAG | GraphRAG |
|----------|----------|----------|
| 事實查找 | ✅ "這個場景在說什麼" | ✅ |
| 主題推理 | ❌ "這個視頻的主要情節" | ✅ Global search |
| 多跳關係 | ❌ | ✅ "A導致BB導致C" |
| 可解釋性 | ❌ | ✅ 關係路徑可追溯 |
### 實作方案
**方案 A: TigerGraph Cloud (推薦)**
- ✅ 原生 Graph + Vector 混合查詢
- ✅ GraphRAG 官方支援
- ✅ 200GB 免費額度
- ❌ 雲端依賴,延遲敏感場景需考慮
**方案 B: Neo4j + Qdrant**
- ✅ 成熟開源生態
- ✅ LangChain/LlamaIndex 整合
- ❌ 需要維護兩個系統
**方案 C: 自建混合架構**
- PostgreSQL + Neo4j (或Typesense)
- 利用現有 BM25 + 向量檢索基礎
- ❌ 開發成本高
### 技術棧整合建議
```rust
// 現有架構
Vector Search (Qdrant) BM25 (PostgreSQL)
// 加入 GraphRAG
Knowledge Graph (TigerGraph/Neo4j)
Vector + Graph traversal
```
### 優先級: 待評估
**考慮因素**:
- 用戶是否需要複雜的故事情節查詢?
- 實體識別 (NER) 成本是否可以接受?
- 與現有 BM25 + Vector 混合搜索的比較優勢?
---
## 10. LazyGraphRAG / FastGraphRAG 成本優化
**說明**: GraphRAG 索引成本高昂LazyGraphRAG 推遲圖譜構建到查詢時
**來源**: [GraphRAG in 2026](https://medium.com/graph-praxis/graph-rag-in-2026-a-practitioners-guide-to-what-actually-works-dca4962e7517)
**Microsoft GraphRAG 問題**: $33K 索引大型數據集
**替代方案**:
- **LazyGraphRAG**: 按需構建,查詢時再建立子圖
- **FastGraphRAG**: 優化索引管道10-90% 成本節省
- **HippoRAG**: 使用 Personalised PageRank 優化遍歷
**優先級**: 待評估 (作為 GraphRAG 的一部分)

View File

@@ -0,0 +1,667 @@
# Momentry Core 版本紀錄
## 版本命名規則
### Main Version (主版本)
```
v{major}.{minor}
例: v0.1, v0.2, v1.0
```
### Build Version (建置版本)
```
v{major}.{minor}.{YYYYMMDD_HHMMSS}
例: v0.1.20260325_143000
```
---
## 版本紀錄存放位置
```
/Users/accusys/momentry/versions/
├── current/ # 目前使用版本
│ ├── binary # 當前 binary
│ └── version.json # 版本資訊
├── releases/ # Release 版本存放
│ ├── v0.1/
│ │ ├── v0.1.20260325_143000/
│ │ │ ├── binary
│ │ │ └── version.json
│ │ ├── v0.1.20260324_100000/
│ │ │ └── ...
│ │ └── release.json # v0.1 版本總覽
│ │
│ └── v0.2/
│ └── ...
└── changelog.json # 全域版本變更記錄
```
---
## version.json 格式
```json
{
"version": "v0.1.20260325_143000",
"main_version": "v0.1",
"build_timestamp": "2026-03-25T14:30:00+08:00",
"git_commit": "83ae050",
"git_branch": "main",
"git_message": "fix: save probe.json to OUTPUT_DIR instead of current directory",
"features": [
"API Key Authentication",
"Job Worker System"
],
"fixes": [
"get_processor_results_by_job column mapping"
],
"deployed_at": "2026-03-25T15:00:00+08:00",
"deployed_by": "opencode",
"status": "production"
}
```
---
## release.json 格式 (主版本總覽)
```json
{
"version": "v0.1",
"status": "production",
"created_at": "2026-03-14T10:00:00+08:00",
"current_build": "v0.1.20260325_143000",
"builds": [
{
"build": "v0.1.20260325_143000",
"date": "2026-03-25",
"commits": ["83ae050", "171c36a"],
"summary": "fix: save probe.json, add v2 backup versioning"
},
{
"build": "v0.1.20260324_100000",
"date": "2026-03-24",
"commits": ["89fbfd6", "3edaf01"],
"summary": "feat: add POST /api/v1/probe endpoint"
}
],
"changelog": [
"## v0.1.20260325_143000",
"- 修復 processor_results 欄位映射錯誤",
"- 添加 API Key 認證",
"",
"## v0.1.20260324_100000",
"- 新增 Probe API"
]
}
```
---
## changelog.json 格式 (全域變更記錄)
```json
{
"updated_at": "2026-03-25T14:30:00+08:00",
"versions": {
"v0.1": {
"status": "production",
"current_build": "v0.1.20260325_143000",
"build_count": 12
},
"v0.0": {
"status": "deprecated",
"final_build": "v0.0.20260310_090000"
}
},
"recent_changes": [
{
"version": "v0.1.20260325_143000",
"date": "2026-03-25",
"changes": [
{"type": "fix", "description": "get_processor_results_by_job column mapping"},
{"type": "feat", "description": "API Key Authentication"}
]
}
]
}
```
---
## Release Script
### /Users/accusys/momentry/scripts/release.sh
```bash
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="/Users/accusys/momentry_core_0.1"
VERSIONS_DIR="/Users/accusys/momentry/versions"
BACKUP_DIR="/Users/accusys/momentry/backup/bin"
CURRENT_BIN="/Users/accusys/momentry/bin/momentry"
# 顏色輸出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# 解析命令列參數
MAIN_VERSION=""
while [[ $# -gt 0 ]]; do
case $1 in
-v|--version)
MAIN_VERSION="$2"
shift 2
;;
*)
log_error "Unknown option: $1"
exit 1
;;
esac
done
if [ -z "$MAIN_VERSION" ]; then
log_error "請指定主版本: ./release.sh -v v0.1"
exit 1
fi
log_info "開始 Release ${MAIN_VERSION}..."
# 1. 取得 Git 資訊
GIT_COMMIT=$(git -C "$PROJECT_DIR" rev-parse --short HEAD)
GIT_BRANCH=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD)
GIT_MESSAGE=$(git -C "$PROJECT_DIR" log -1 --pretty=%s)
BUILD_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BUILD_VERSION="${MAIN_VERSION}.${BUILD_TIMESTAMP}"
log_info "Build Version: ${BUILD_VERSION}"
log_info "Git Commit: ${GIT_COMMIT}"
# 2. 創建版本目錄
BUILD_DIR="${VERSIONS_DIR}/releases/${MAIN_VERSION}/${BUILD_VERSION}"
mkdir -p "$BUILD_DIR"
mkdir -p "${VERSIONS_DIR}/current"
mkdir -p "$BACKUP_DIR"
# 3. 停止 Production Service
log_info "停止 Production Service..."
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist 2>/dev/null || true
# 4. 備份當前 Binary
if [ -f "$CURRENT_BIN" ]; then
OLD_VERSION=$(cat "${VERSIONS_DIR}/current/version.json" 2>/dev/null | jq -r '.version // "unknown"')
log_info "備份當前版本: $OLD_VERSION"
cp "$CURRENT_BIN" "${BACKUP_DIR}/momentry_${OLD_VERSION}_$(date +%Y%m%d_%H%M%S)"
fi
# 5. 編譯 Release 版本
log_info "編譯 Release 版本..."
cd "$PROJECT_DIR"
cargo build --release --bin momentry
# 6. 複製到版本目錄
log_info "複製到版本目錄..."
cp target/release/momentry "${BUILD_DIR}/binary"
cp target/release/momentry "$CURRENT_BIN"
# 7. 生成 version.json
cat > "${BUILD_DIR}/version.json" << EOF
{
"version": "${BUILD_VERSION}",
"main_version": "${MAIN_VERSION}",
"build_timestamp": "$(date -Iseconds)",
"git_commit": "${GIT_COMMIT}",
"git_branch": "${GIT_BRANCH}",
"git_message": "${GIT_MESSAGE}",
"features": [],
"fixes": [],
"deployed_at": null,
"deployed_by": null,
"status": "built"
}
EOF
# 8. 更新 current
cp "${BUILD_DIR}/version.json" "${VERSIONS_DIR}/current/version.json"
# 9. 更新 changelog.json
UPDATE_CHANGELOG="
import json
from datetime import datetime
changelog_path = '${VERSIONS_DIR}/changelog.json'
build_info = {
'version': '${BUILD_VERSION}',
'date': datetime.now().strftime('%Y-%m-%d'),
'commit': '${GIT_COMMIT}',
'message': '${GIT_MESSAGE}'
}
try:
with open(changelog_path, 'r') as f:
changelog = json.load(f)
except FileNotFoundError:
changelog = {'updated_at': '', 'versions': {}, 'recent_changes': []}
changelog['updated_at'] = datetime.now().isoformat()
if '${MAIN_VERSION}' not in changelog['versions']:
changelog['versions']['${MAIN_VERSION}'] = {'status': 'building', 'build_count': 0}
changelog['versions']['${MAIN_VERSION}']['build_count'] += 1
changelog['versions']['${MAIN_VERSION}']['current_build'] = '${BUILD_VERSION}'
changelog['recent_changes'].insert(0, build_info)
with open(changelog_path, 'w') as f:
json.dump(changelog, f, indent=2, ensure_ascii=False)
"
python3 -c "$UPDATE_CHANGELOG"
# 10. 啟動 Production Service
log_info "啟動 Production Service..."
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 11. 驗證
sleep 3
if curl -s http://localhost:3002/health > /dev/null; then
log_info "✓ Release 成功!"
log_info "版本: ${BUILD_VERSION}"
log_info "目錄: ${BUILD_DIR}"
else
log_error "✗ Release 失敗!請檢查服務狀態。"
exit 1
fi
```
---
## 查詢版本指令
### 查詢目前版本
```bash
cat /Users/accusys/momentry/versions/current/version.json
```
### 查詢所有 Release
```bash
ls -la /Users/accusys/momentry/versions/releases/
```
### 查詢版本歷史
```bash
cat /Users/accusys/momentry/versions/changelog.json | python3 -m json.tool
```
### 查詢特定主版本
```bash
ls /Users/accusys/momentry/versions/releases/v0.1/
```
---
## 版本狀態
| 狀態 | 說明 |
|------|------|
| `building` | 建置中 |
| `built` | 已建置,未部署 |
| `testing` | 測試中 |
| `production` | 正式環境使用中 |
| `deprecated` | 已棄用 |
| `archived` | 已封存 |
---
## 版本流程圖
```
develop (git branch)
feature/bugfix commit
develop ──────────────────┐
│ │
│ (merge to main) │
▼ │
main (git) │
│ │
▼ │
Build v0.1.20260325_143000
│ │
├──► testing (3003) │
│ │
│ (approve) │
▼ ▼
v0.1 ───────────────────┘
├──► releases/v0.1/v0.1.20260325_143000/
├──► current/ (production)
changelog.json (update)
```
---
## Release Note (版本發布說明)
### Release Note 存放位置
```
/Users/accusys/momentry/versions/releases/{主版本}/{建置版本}/
├── binary
├── version.json
└── RELEASE_NOTE.md # 發布說明 (Markdown)
```
### Release Note 範本
```markdown
# Momentry Core v0.1.20260325_143000 Release Note
## 版本資訊
- **Build Version**: v0.1.20260325_143000
- **Main Version**: v0.1
- **Build Date**: 2026-03-25 14:30:00
- **Git Commit**: 83ae050
## 新功能 (Features)
### API Key 認證系統
- 添加 API Key 認證中介層
- 所有 `/api/v1/*` 端點需要 `X-API-Key` header
- 支援 API Key 使用追蹤和審計日誌
### Job Worker 系統
- 新增 Job Worker 二進位檔
- 支援最多 2 個並發處理器
- 新增 `/api/v1/jobs/:uuid` 端點查詢任務詳情
## 錯誤修復 (Bug Fixes)
| Issue | 描述 |
|-------|------|
| #001 | 修復 `get_processor_results_by_job` 欄位映射錯誤 |
| #002 | 修復 API Key 驗證時區處理問題 |
## API 變更 (API Changes)
### 新增端點
| Method | Endpoint | 說明 |
|--------|----------|------|
| GET | `/api/v1/jobs` | 取得所有任務列表 |
| GET | `/api/v1/jobs/:uuid` | 取得特定任務詳情 |
### 認證變更
| 端點 | 舊版 | 新版 |
|------|------|------|
| `/api/v1/*` | 公開 | 需要 API Key |
## 升級指南
### 從舊版升級
1. 備份當前版本
2. 停止服務
3. 替換 binary
4. 重啟服務
5. 更新 API Key 配置
### API Key 配置
```bash
# 請求範例
curl -H "X-API-Key: your_api_key" \
"http://localhost:3002/api/v1/videos"
```
## 已知問題 (Known Issues)
- 暫無
## 相關文檔
- [API 文檔](../docs/API_INDEX.md)
- [版本管理規範](../docs/VERSION_MANAGEMENT.md)
---
## Release Note 自動生成 Script
### /Users/accusys/momentry/scripts/generate_release_note.sh
```bash
#!/bin/bash
set -e
BUILD_VERSION=$1
MAIN_VERSION=$2
BUILD_DIR="/Users/accusys/momentry/versions/releases/${MAIN_VERSION}/${BUILD_VERSION}"
# 取得 Git 資訊
GIT_COMMITS=$(git log --oneline -10)
GIT_CHANGES=$(git diff --stat HEAD~5..HEAD)
cat > "${BUILD_DIR}/RELEASE_NOTE.md" << EOF
# Momentry Core ${BUILD_VERSION} Release Note
## 版本資訊
- **Build Version**: ${BUILD_VERSION}
- **Main Version**: ${MAIN_VERSION}
- **Build Date**: $(date '+%Y-%m-%d %H:%M:%S')
- **Git Commit**: $(git rev-parse --short HEAD)
## 變更內容
### Commit 記錄
\`\`\`
${GIT_COMMITS}
\`\`\`
### 變更統計
\`\`\`
${GIT_CHANGES}
\`\`\`
## 新功能
## 錯誤修復
## API 變更
## 升級指南
## 已知問題
EOF
echo "Release Note 生成完成: ${BUILD_DIR}/RELEASE_NOTE.md"
```
---
## Release Note 查詢
### 查詢所有 Release Note
```bash
find /Users/accusys/momentry/versions/releases -name "RELEASE_NOTE.md"
```
### 查看特定版本 Release Note
```bash
cat /Users/accusys/momentry/versions/releases/v0.1/v0.1.20260325_143000/RELEASE_NOTE.md
```
### 查詢最新版本 Release Note
```bash
cat /Users/accusys/momentry/versions/current/RELEASE_NOTE.md
```
---
## Release Note 範例
### 完整 Release Note 範例
\`\`\`markdown
# Momentry Core v0.1.20260325_143000 Release Note
## 版本資訊
| 項目 | 內容 |
|------|------|
| Build Version | v0.1.20260325_143000 |
| Main Version | v0.1 |
| Build Date | 2026-03-25 14:30:00 |
| Git Commit | 83ae050 |
| Status | ✅ Production |
## 新功能 (Features)
### 1. API Key 認證系統
添加完整的 API Key 認證系統,保護所有 API 端點。
**功能:**
- SHA256 key hash 驗證
- 使用統計追蹤
- 審計日誌記錄
- 異常檢測
**API 使用方式:**
\`\`\`bash
curl -H "X-API-Key: your_key" \\
"http://localhost:3002/api/v1/videos"
\`\`\`
### 2. Job Worker 系統
新增獨立的 Job Worker 處理影片處理任務。
**特性:**
- 最多 2 個並發處理器
- Polling-based 任務獲取
- 自動進度追蹤
## 錯誤修復 (Bug Fixes)
| Issue | 描述 | 嚴重性 |
|-------|------|--------|
| #001 | 修復 `get_processor_results_by_job` TIMESTAMP 欄位映射 | 🔴 高 |
| #002 | 修復 3002 port 衝突問題 | 🟡 中 |
## API 變更
### 新增端點
| Method | Endpoint | 說明 |
|--------|----------|------|
| GET | `/api/v1/jobs` | 取得任務列表 |
| GET | `/api/v1/jobs/:uuid` | 取得任務詳情 |
### 端點認證狀態
| 端點 | 認證需求 |
|------|----------|
| `/health` | ❌ 不需要 |
| `/api/v1/*` | ✅ 需要 `X-API-Key` |
## 升級指南
### 前置需求
- PostgreSQL 資料庫
- Redis 伺服器
- MongoDB 快取
### 升級步驟
1. **備份當前版本**
\`\`\`bash
cp /Users/accusys/momentry/bin/momentry \\
/Users/accusys/momentry/backup/bin/momentry_$(date +%Y%m%d)
\`\`\`
2. **停止服務**
\`\`\`bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
\`\`\`
3. **替換 Binary**
\`\`\`bash
cp v0.1.20260325_143000/binary /Users/accusys/momentry/bin/momentry
\`\`\`
4. **重啟服務**
\`\`\`bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
\`\`\`
5. **驗證**
\`\`\`bash
curl http://localhost:3002/health
\`\`\`
## 已知問題 (Known Issues)
- 暫無
## 技術細節
### 認證流程
\`\`\`
Client Request
[X-API-Key Header] ──► Middleware
│ │
│ ▼
│ Hash Key (SHA256)
│ │
│ ▼
│ DB Lookup
│ │
│ ▼
│ Validate Status
│ │
▼ ▼
Handler ◄────────────────────┘
\`\`\`
### 資料庫變更
\`\`\`sql
-- 新增 duration_secs 欄位
ALTER TABLE processor_results
ADD COLUMN IF NOT EXISTS duration_secs DOUBLE PRECISION;
\`\`\`
## 回滾指南
如需回滾到上一版本:
\`\`\`bash
# 1. 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 2. 恢復舊版
cp /Users/accusys/momentry/backup/bin/momentry_v0.1.20260324_100000 \\
/Users/accusys/momentry/bin/momentry
# 3. 重啟服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
\`\`\`
## 聯繫與支援
- **Issue Tracker**: https://gitea.momentry.ddns.net/momentry/momentry_core/issues
- **文檔**: https://docs.momentry.ddns.net
---
*Generated: $(date '+%Y-%m-%d %H:%M:%S')*
\`\`\`
```

File diff suppressed because it is too large Load Diff

534
docs/CHUNK_DESIGN.md Normal file
View File

@@ -0,0 +1,534 @@
# Momentry Core 數據管理設計文檔 (v4)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 0. 核心概念:雙 UUID 系統
為減少資料庫大小,在現有的 videos 表中增加內部 ID 映射:
### 0.1 設計原則
- **external_uuid**: 用戶可見的識別碼(如 UUID
- **id**: 資料庫自動產生的內部 ID (SERIAL),節省空間
- **映射關係**: 透過 videos 表的 `id` 欄位關聯
### 0.2 videos 表 (檔案映射表)
現有結構,增加 `id` 作為內部 ID
```sql
-- 現有 videos 表結構
CREATE TABLE videos (
id SERIAL PRIMARY KEY, -- 內部 ID (自動產生)
uuid VARCHAR(32) UNIQUE NOT NULL, -- 外部 UUID (用戶可見)
file_name VARCHAR(255) NOT NULL,
file_path TEXT,
duration DOUBLE PRECISION,
width INTEGER,
height INTEGER,
fps DOUBLE PRECISION,
probe_json JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_videos_uuid ON videos(uuid);
```
### 0.3 對照的好處
| 方式 | 儲存空間 (1000個視頻每個1000個chunk) |
|------|---------------------------------------|
| 直接用 uuid (32字元) | ~32MB |
| 使用 id (4字元) | ~4MB |
## 1. 數據流架構
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 輸入階段 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 視頻文件 │→ │ ffprobe │ │ ASR │ │ YOLO │ │
│ │ (.mp4) │→ │ (probe) │→ │ (asr) │→ │ (yolo) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ ASRX │ │ CUT │ │ OCR │ │ FACE │ │
│ │ (asrx) │→ │ (cut) │→ │ (ocr) │→ │ (face) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Pre-Chunk / Frame 階段 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ pre_chunks 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ type=sentence │ from asr, asrx │ 句子邊界範圍 │ │ │
│ │ │ type=cut │ from cut detection │ 場景切換範圍 │ │ │
│ │ │ type=time │ from time split │ 固定時間範圍 (10s) │ │ │
│ │ │ type=trace │ from yolo trace │ 物件追蹤範圍 │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ frames 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ - yolo 每幀識別結果 │ │
│ │ - ocr 每幀識別結果 │ │
│ │ - face 每幀識別結果 (如需要) │ │
│ │ - 單一圖像識別結果 → 直接入 frame │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Chunk 階段 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ chunks 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ │ │
│ │ 組合規則1: pre_chunk → chunk (直接轉換) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ sentence_pre_chunk → sentence_chunk │ │ │
│ │ │ cut_pre_chunk → cut_chunk │ │ │
│ │ │ time_pre_chunk → time_chunk │ │ │
│ │ │ trace_pre_chunk → trace_chunk │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 組合規則2: pre_chunk + frame 內容 → chunk (集合內容) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ sentence_pre_chunk + 涵蓋範圍內的 frames → 豐富的 sentence_chunk │ │ │
│ │ │ time_pre_chunk + 涵蓋範圍內的 frames → 豐富的 time_chunk │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Vector 階段 │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ PostgreSQL vectors │ │ Qdrant vectors │ │
│ │ (chunk_vectors) │ │ (chunk_v3) │ │
│ └──────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 2. Pre-Chunk 類型定義
### 2.1 Pre-Chunk 來源與類型對照表
| 來源類型 | source_type | 產出 Pre-Chunk Type | 說明 |
|---------|-------------|---------------------|------|
| ASR ( Whisper ) | asr | sentence | 句子邊界 |
| ASRX ( with timestamps ) | asrx | sentence | 帶時間戳的句子 |
| CUT (場景檢測) | cut | cut | 場景切換點 |
| TIME (固定時間) | time | time | 每 10 秒 |
| YOLO Trace | yolo_trace | trace | 物件追蹤軌跡 |
| YOLO (單幀) | yolo | **→ frame** | 不入 pre_chunk |
| OCR (單幀) | ocr | **→ frame** | 不入 pre_chunk |
| FACE (單幀) | face | **→ frame** | 不入 pre_chunk |
| PROBE | probe | metadata | 視頻元數據 |
### 2.2 Pre-Chunk Schema
```sql
CREATE TABLE pre_chunks (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
-- 來源識別
source_type VARCHAR(32) NOT NULL, -- 'asr', 'asrx', 'cut', 'time', 'yolo_trace', 'probe'
source_file TEXT, -- 原始 JSON 文件路徑
-- Chunk 類型
chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace'
-- 時間範圍
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
-- Frame 範圍 (精確到 frame)
start_frame INTEGER NOT NULL,
end_frame INTEGER NOT NULL,
-- FPS (用於 frame 計算)
fps DOUBLE PRECISION NOT NULL,
-- 原始 JSON 內容
raw_json JSONB NOT NULL,
-- 解析後的文字內容 (如有)
text_content TEXT,
-- 處理狀態
processed BOOLEAN DEFAULT FALSE,
chunk_id VARCHAR(64), -- 轉換後的 chunk_id
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, source_type, start_frame, end_frame)
);
CREATE INDEX idx_pre_chunks_file_id ON pre_chunks(file_id);
CREATE INDEX idx_pre_chunks_type ON pre_chunks(file_id, chunk_type);
CREATE INDEX idx_pre_chunks_time ON pre_chunks(file_id, start_time, end_time);
CREATE INDEX idx_pre_chunks_frame ON pre_chunks(file_id, start_frame, end_frame);
CREATE INDEX idx_pre_chunks_processed ON pre_chunks(file_id, processed);
```
## 3. Frame 管理原則
### 3.1 哪些數據進入 Frame
只儲存**單一圖像識別**的結果:
- YOLO 每幀檢測結果
- OCR 每幀識別結果
- FACE 每幀檢測結果
### 3.2 Frame Schema
```sql
CREATE TABLE frames (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
frame_number INTEGER NOT NULL,
timestamp DOUBLE PRECISION NOT NULL,
fps DOUBLE PRECISION NOT NULL,
-- YOLO 結果 (JSONB 陣列)
yolo_objects JSONB,
-- OCR 結果 (JSONB 陣列)
ocr_results JSONB,
-- Face 結果 (JSONB 陣列)
face_results JSONB,
-- 原始幀圖像路徑 (可選)
frame_path TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, frame_number)
);
CREATE INDEX idx_frames_file_id ON frames(file_id);
CREATE INDEX idx_frames_frame ON frames(file_id, frame_number);
CREATE INDEX idx_frames_timestamp ON frames(file_id, timestamp);
```
## 4. Chunk 組合規則
### 4.1 組合規則 1: 直接轉換 (rule_1)
將 pre_chunk 直接轉換為 chunk
```
sentence_pre_chunk → sentence_chunk (rule: "rule_1")
cut_pre_chunk → cut_chunk (rule: "rule_1")
time_pre_chunk → time_chunk (rule: "rule_1")
trace_pre_chunk → trace_chunk (rule: "rule_1")
```
### 4.2 組合規則 2: 集合內容 (rule_2)
將 pre_chunk 與其時間區間內的所有 frame 識別結果集合:
```
sentence_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 sentence_chunk (rule: "rule_2")
time_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 time_chunk (rule: "rule_2")
```
### 4.3 Chunk Schema
```sql
CREATE TABLE chunks (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace'
-- 組合規則 (payload 中記錄)
-- rule: 'rule_1' = 直接轉換, 'rule_2' = 集合內容
-- 時間範圍
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
-- Frame 範圍 (精確到 frame)
start_frame INTEGER NOT NULL,
end_frame INTEGER NOT NULL,
-- FPS
fps DOUBLE PRECISION NOT NULL,
-- 主要內容
text_content TEXT,
-- 完整內容 (JSONB) - 包含 rule 欄位
content JSONB NOT NULL,
-- 來源的 pre_chunk IDs
pre_chunk_ids INTEGER[],
-- 包含的 frame 數量
frame_count INTEGER DEFAULT 0,
-- 向量 ID
vector_id VARCHAR(64),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, chunk_id)
);
CREATE INDEX idx_chunks_file_id ON chunks(file_id);
CREATE INDEX idx_chunks_type ON chunks(file_id, chunk_type);
CREATE INDEX idx_chunks_time ON chunks(file_id, start_time, end_time);
CREATE INDEX idx_chunks_frame ON chunks(file_id, start_frame, end_frame);
CREATE INDEX idx_chunks_vector ON chunks(vector_id);
```
## 5. 處理流程範例
### 5.1 輸入數據
假設視頻長度 30 秒fps=30
| 來源 | 產出 |
|------|------|
| ASR | 3 個 sentence_pre_chunk (每句約 10s) |
| CUT | 2 個 cut_pre_chunk (場景 1, 場景 2) |
| TIME | 3 個 time_pre_chunk (0-10s, 10-20s, 20-30s) |
| YOLO | 900 個 frame 記錄 (每幀) |
| OCR | 依實際識別結果入 frame |
### 5.2 Chunk 產出
**使用規則 1 (直接轉換):**
- rule: "rule_1"
- 3 個 sentence_chunk
- 2 個 cut_chunk
- 3 個 time_chunk
**使用規則 2 (集合內容):**
- rule: "rule_2"
- 3 個 sentence_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果)
- 3 個 time_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果)
## 8. 數據示例
### 8.1 videos 表 (檔案映射)
```json
{
"id": 1,
"uuid": "abc123def456",
"file_name": "video_001.mp4",
"file_path": "/path/to/video_001.mp4",
"duration": 300.5,
"width": 1920,
"height": 1080,
"fps": 30.0
}
```
### 8.2 pre_chunks 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"source_type": "asr",
"chunk_type": "sentence",
"start_time": 0.0,
"end_time": 5.5,
"start_frame": 0,
"end_frame": 165,
"fps": 30.0,
"raw_json": {...},
"text_content": "This is the first sentence"
}
```
### 8.3 frames 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"frame_number": 300,
"timestamp": 10.0,
"fps": 30.0,
"yolo_objects": [
{"class": "person", "confidence": 0.9, "bbox": [100, 50, 200, 150]},
{"class": "car", "confidence": 0.85, "bbox": [50, 100, 150, 180]}
],
"ocr_results": [],
"face_results": []
}
```
### 8.4 chunks 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"rule": "rule_2",
"start_time": 10.0,
"end_time": 15.5,
"start_frame": 300,
"end_frame": 465,
"fps": 30.0,
"text_content": "The second sentence from the audio",
"content": {
"rule": "rule_2",
"asr_text": "The second sentence from the audio",
"objects": [
{"class": "person", "first_frame": 300, "last_frame": 450, "appears_in_frames": [300, 310, 320, ...]},
{"class": "car", "first_frame": 350, "last_frame": 465, "appears_in_frames": [350, 360, ...]}
],
"ocr": [...],
"faces": [...]
},
"pre_chunk_ids": [5],
"frame_count": 301
}
```
### 8.5 chunk_vectors 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 10.0,
"end_time": 15.5,
"embedding": "[0.1, 0.2, ...]",
"metadata": {"text": "The second sentence..."}
}
```
### 8.6 Qdrant Payload
```json
{
"file_uuid": "abc123def456",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 10.0,
"end_time": 15.5,
"text": "The second sentence from the audio"
}
```
## 7. 向量管理原則
### 7.1 Vector Schema
```sql
-- Chunk 向量表 (PostgreSQL)
CREATE TABLE chunk_vectors (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
chunk_id VARCHAR(64) NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
-- 向量數據
embedding TEXT, -- JSON 格式的向量
embedding_vector VECTOR(768), -- pgvector 類型 (如可用)
-- 時間範圍 (用於時間查詢)
start_time DOUBLE PRECISION,
end_time DOUBLE PRECISION,
-- Metadata
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(chunk_id)
);
-- 索引
CREATE INDEX idx_chunk_vectors_file_id ON chunk_vectors(file_id);
```
### 7.2 Qdrant Collection
- Collection 名稱: `chunks_v3`
- Vector 維度: 768 (nomic-embed-text)
- Payload 包含: `file_uuid`, `chunk_id`, `chunk_type`, `start_time`, `end_time`, `text`
> **注意**: Qdrant 中仍使用 uuid (字串)因為需要可讀性和跨系統整合。PostgreSQL 內部使用 videos.id (整數) 以節省空間。
## 9. 設計原則總結
1. **單一圖像識別 → Frame**: yolo, ocr, face 等單幀識別結果直接入 frame 表
2. **時間序列識別 → Pre-Chunk**: asr, asrx, cut, time, trace 等有時間範圍的結果入 pre_chunk 表
3. **組合規則 1 (直接)**: pre_chunk → chunk (保持原有邊界)
4. **組合規則 2 (集合)**: pre_chunk + frames → chunk (加入識別內容)
5. **精確到 Frame**: 所有時間範圍都記錄 start_frame, end_frame
6. **雙向量存儲**: 同時支持 PostgreSQL 和 Qdrant
7. **跨視頻搜索**: 透過 videos 表的 uuid 進行搜索,內部使用 id 節省空間
8. **空間優化**: 內部表使用 videos.id (4 bytes) 而非 uuid (32 bytes)
## 10. 查詢範例
### 10.1 跨視頻搜索所有 chunk
```sql
-- 搜索所有視頻中包含 "hello" 的 chunk
SELECT c.*, v.uuid, v.file_name
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE c.text_content ILIKE '%hello%';
```
### 10.2 查詢特定視頻的 chunk
```sql
-- 查詢 uuid 為 'abc123' 的視頻的所有 chunk
SELECT c.*
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE v.uuid = 'abc123';
```
### 10.3 按時間範圍搜索
```sql
-- 搜索所有視頻在 10-20 秒範圍內的 chunk
SELECT c.*, v.uuid
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE c.start_time >= 10.0 AND c.end_time <= 20.0;
```

View File

@@ -1,5 +1,21 @@
# Video Chunk 切分規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中影片 chunks 的切分原則與資料結構。
---
@@ -579,7 +595,518 @@ TimeBased Chunks (4 個, 重疊 2秒):
---
## 10. 相關文件
## 10. 資料庫儲存
### 10.1 PostgreSQL 儲存
#### Table Schema
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
start_frame BIGINT NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
end_frame BIGINT NOT NULL,
fps VARCHAR(16) NOT NULL,
fps_value DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
metadata JSONB,
vector_id VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
-- 索引
CREATE INDEX idx_chunks_uuid ON chunks(uuid);
CREATE INDEX idx_chunks_type ON chunks(chunk_type);
CREATE INDEX idx_chunks_time ON chunks(start_time, end_time);
CREATE INDEX idx_chunks_uuid_type ON chunks(uuid, chunk_type);
CREATE INDEX idx_chunks_vector_id ON chunks(vector_id);
```
#### 儲存範例
```rust
pub async fn store_chunk_to_postgres(db: &PostgresDb, chunk: &Chunk) -> Result<()> {
sqlx::query!(
r#"
INSERT INTO chunks (
uuid, chunk_id, chunk_index, chunk_type,
start_time, start_frame, end_time, end_frame,
fps, fps_value, content, metadata, vector_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (uuid, chunk_id) DO UPDATE SET
content = EXCLUDED.content,
metadata = EXCLUDED.metadata,
vector_id = EXCLUDED.vector_id,
updated_at = NOW()
"#,
chunk.uuid,
chunk.chunk_id,
chunk.chunk_index as i32,
chunk.chunk_type.as_str(),
chunk.start_time,
chunk.start_frame,
chunk.end_time,
chunk.end_frame,
chunk.fps,
chunk.fps_value,
serde_json::to_value(&chunk.content)?,
serde_json::to_value(&chunk.metadata)?,
chunk.vector_id,
)
.execute(&db.pool)
.await?;
Ok(())
}
```
---
### 10.2 MongoDB 儲存
#### Collection Schema
```javascript
// chunks collection
{
_id: ObjectId,
uuid: "1636719dc31f78ac",
chunk_id: "sentence_0001",
chunk_index: 1,
chunk_type: "sentence",
start_time: 10.5,
start_frame: 252,
end_time: 15.75,
end_frame: 378,
fps: "24/1",
fps_value: 24.0,
content: {
text: "Hello world, this is a test",
text_normalized: "hello world this is a test",
word_count: 7,
char_count: 34
},
metadata: {
source: "asr",
confidence: 0.95,
language: "en"
},
vector_id: "vec_sentence_0001",
created_at: ISODate("2026-03-16T10:00:00Z"),
updated_at: ISODate("2026-03-16T10:00:00Z")
}
// 索引
db.chunks.createIndex({ uuid: 1 })
db.chunks.createIndex({ chunk_type: 1 })
db.chunks.createIndex({ start_time: 1, end_time: 1 })
db.chunks.createIndex({ vector_id: 1 })
db.chunks.createIndex({ uuid: 1, chunk_type: 1 })
```
#### 儲存範例
```rust
pub async fn store_chunk_to_mongodb(db: &MongoDb, chunk: &Chunk) -> Result<()> {
let doc = bson::doc! {
"uuid": chunk.uuid,
"chunk_id": chunk.chunk_id,
"chunk_index": chunk.chunk_index,
"chunk_type": chunk.chunk_type.as_str(),
"start_time": chunk.start_time,
"start_frame": chunk.start_frame,
"end_time": chunk.end_time,
"end_frame": chunk.end_frame,
"fps": chunk.fps,
"fps_value": chunk.fps_value,
"content": serde_json::to_value(&chunk.content)?,
"metadata": serde_json::to_value(&chunk.metadata)?,
"vector_id": chunk.vector_id,
"created_at": chrono::Utc::now(),
"updated_at": chrono::Utc::now()
};
let collection = db.database("momentry").collection("chunks");
collection.update_one(
doc! { "uuid": &chunk.uuid, "chunk_id": &chunk.chunk_id },
doc! { "$set": doc },
UpdateOptions::builder().upsert(true).build(),
).await?;
Ok(())
}
```
---
## 11. 向量儲存設計
### 11.1 設計原則
**統一向量 ID 格式**,確保 Qdrant 與 PostgreSQL 相容:
```
{chunk_type}_{chunk_index:04}
範例:
sentence_0001
cut_0002
time_based_0015
```
### 11.2 Qdrant Collection
#### 建立 Collection
```bash
# 使用 Qdrant client 建立 collection
curl -X PUT http://localhost:6333/collections/chunks \
-H "Content-Type: application/json" \
-H "api-key: Test3200Test3200Test3200" \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
}
}'
```
#### Point 結構
```json
{
"id": "sentence_0001",
"vector": [0.123, -0.456, ...],
"payload": {
"uuid": "1636719dc31f78ac",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"chunk_index": 1,
"start_time": 10.5,
"end_time": 15.75,
"text": "Hello world, this is a test",
"metadata": {
"confidence": 0.95,
"language": "en"
}
}
}
```
#### Rust 結構
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorPoint {
pub id: String,
pub vector: Vec<f32>,
pub payload: VectorPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorPayload {
pub uuid: String,
pub chunk_id: String,
pub chunk_type: String,
pub chunk_index: u32,
pub start_time: f64,
pub end_time: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scene_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub segment_number: Option<i32>,
pub metadata: Option<serde_json::Value>,
}
```
### 11.3 PostgreSQL Vector 儲存
#### Table Schema
```sql
-- 使用 pgvector 擴展
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunk_vectors (
id BIGSERIAL PRIMARY KEY,
vector_id VARCHAR(64) NOT NULL UNIQUE,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
chunk_index INTEGER NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
embedding vector(768) NOT NULL,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
FOREIGN KEY (uuid, chunk_id) REFERENCES chunks(uuid, chunk_id)
);
-- 向量檢索索引 (IVFFlat)
CREATE INDEX idx_chunk_vectors_embedding
ON chunk_vectors
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 查詢索引
CREATE INDEX idx_chunk_vectors_uuid ON chunk_vectors(uuid);
CREATE INDEX idx_chunk_vectors_type ON chunk_vectors(chunk_type);
```
#### 儲存範例
```rust
pub async fn store_vector_to_postgres(db: &PostgresDb, point: &VectorPoint) -> Result<()> {
sqlx::query!(
r#"
INSERT INTO chunk_vectors (
vector_id, uuid, chunk_id, chunk_type, chunk_index,
start_time, end_time, embedding, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (vector_id) DO UPDATE SET
embedding = EXCLUDED.embedding,
metadata = EXCLUDED.metadata
"#,
point.id,
point.payload.uuid,
point.payload.chunk_id,
point.payload.chunk_type,
point.payload.chunk_index as i32,
point.payload.start_time,
point.payload.end_time,
point.vector,
serde_json::to_value(&point.payload.metadata)?,
)
.execute(&db.pool)
.await?;
Ok(())
}
```
---
## 12. 查詢範例
### 12.1 語義搜尋 (Semantic Search)
#### 查詢類型 1: 相似文字搜尋
```rust
// 搜尋與問句相似的 chunks
pub async fn semantic_search(
qdrant: &QdrantDb,
query: &str,
limit: usize,
) -> Result<Vec<SearchResult>> {
// 1. 將問句向量化
let query_vector = embed_text(query).await?;
// 2. 搜尋 Qdrant
let results = qdrant.search(
"chunks",
&query_vector,
limit,
Some(&Filter::must([
Condition::Match("chunk_type", "sentence"),
])),
).await?;
Ok(results)
}
// 使用範例
let results = semantic_search(&qdrant, "找出有人在說話的片段", 10).await?;
for r in results {
println!("{}: {:.3}", r.payload.chunk_id, r.score);
println!(" Time: {}s - {}s", r.payload.start_time, r.payload.end_time);
println!(" Text: {:?}", r.payload.text);
}
```
#### 查詢類型 2: 語音/文字混合搜尋
```sql
-- PostgreSQL: 搜尋特定文字的 chunks
SELECT
c.chunk_id,
c.chunk_type,
c.start_time,
c.end_time,
c.content->>'text' as text,
v.embedding <=> query_embedding('找出開車的場景') as similarity
FROM chunks c
LEFT JOIN chunk_vectors v ON c.chunk_id = v.chunk_id
WHERE c.chunk_type = 'sentence'
AND c.content->>'text' ILIKE '%car%'
ORDER BY v.embedding <=> query_embedding('找出開車的場景')
LIMIT 10;
```
### 12.2 時間範圍搜尋
#### 查詢類型 3: 特定時間範圍
```rust
// 找出 30-60 秒之間的所有 chunks
pub async fn search_by_time_range(
db: &PostgresDb,
uuid: &str,
start: f64,
end: f64,
) -> Result<Vec<Chunk>> {
let chunks = sqlx::query_as!(
Chunk,
r#"
SELECT * FROM chunks
WHERE uuid = $1
AND start_time < $3
AND end_time > $2
ORDER BY chunk_type, chunk_index
"#,
uuid, start, end
)
.fetch_all(&db.pool)
.await?;
Ok(chunks)
}
// 使用範例
let chunks = search_by_time_range(&db, "1636719dc31f78ac", 30.0, 60.0).await?;
```
```javascript
// MongoDB: 時間範圍查詢
db.chunks.find({
uuid: "1636719dc31f78ac",
start_time: { $lt: 60 },
end_time: { $gt: 30 }
}).sort({ chunk_type: 1, chunk_index: 1 })
```
### 12.3 混合搜尋 (Hybrid Search)
#### 查詢類型 4: 文字關鍵詞 + 向量相似度
```rust
// 結合關鍵詞匹配與向量相似度
pub async fn hybrid_search(
db: &PostgresDb,
qdrant: &QdrantDb,
query: &str,
keywords: &[&str],
limit: usize,
) -> Result<Vec<HybridResult>> {
// 1. 向量搜尋
let query_vector = embed_text(query).await?;
let vector_results = qdrant.search("chunks", &query_vector, limit * 2, None).await?;
// 2. 關鍵詞過濾
let keyword_filter: Vec<_> = keywords.iter()
.map(|k| format!("%{}%", k))
.collect();
let filtered: Vec<_> = vector_results.into_iter()
.filter(|r| {
if let Some(text) = &r.payload.text {
keyword_filter.iter().any(|k| text.contains(k.as_str()))
} else {
false
}
})
.take(limit)
.collect();
Ok(filtered)
}
```
### 12.4 場景搜尋
#### 查詢類型 5: 找出特定場景
```sql
-- PostgreSQL: 找出特定場景 ID 的 chunks
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
AND (content->>'scene_id')::int = 5;
-- 找出包含轉場效果的 chunks
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
AND content->>'transition_type' = 'dissolve';
```
### 12.5 影片摘要
#### 查詢類型 6: 產生影片摘要
```sql
-- 合併影片所有語句
SELECT
string_agg(content->>'text', ' ' ORDER BY start_time) as full_transcript
FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'sentence'
AND content->>'text' IS NOT NULL;
-- 按場景聚合文字
SELECT
content->>'scene_id' as scene,
string_agg(content->>'text', ' ' ORDER BY start_time) as scene_text
FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
GROUP BY content->>'scene_id'
ORDER BY MIN(start_time);
```
### 12.6 常見查詢模式
| 查詢類型 | 描述 | 資料庫 | SQL/程式碼 |
|----------|------|--------|-------------|
| 語義搜尋 | 找相似內容 | Qdrant | `search(vector, limit)` |
| 關鍵詞搜尋 | 精確文字匹配 | PostgreSQL | `ILIKE '%keyword%'` |
| 時間範圍 | 特定時段 | Both | `start_time < end AND end_time > start` |
| 場景搜尋 | 特定鏡頭 | PostgreSQL | `scene_id = N` |
| 混合搜尋 | 向量+關鍵詞 | Both |結合以上兩種 |
| 摘要產生 | 合併文字 | PostgreSQL | `string_agg()` |
---
## 13. 資料庫選擇建議
### 13.1 儲存策略
| 資料類型 | 主要儲存 | 備份/查詢 | 說明 |
|----------|----------|-----------|------|
| **Chunk 元數據** | PostgreSQL | MongoDB | 結構化查詢為主 |
| **向量資料** | Qdrant | PostgreSQL | 向量搜尋為主 |
| **全文檢索** | PostgreSQL | - | 關鍵詞搜尋 |
| **日誌/歷史** | MongoDB | - | 靈活性為主 |
### 13.2 讀寫模式
| 場景 | 寫入 | 讀取 |
|------|------|------|
| **影片處理** | PostgreSQL + Qdrant | - |
| **語義搜尋** | - | Qdrant |
| **時間軸瀏覽** | - | PostgreSQL |
| **系統分析** | MongoDB | MongoDB |
---
## 14. 相關文件
- [JSON_OUTPUT_SPEC.md](./JSON_OUTPUT_SPEC.md) - JSON 輸出規範
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範

674
docs/DEMO_MANUAL.md Normal file
View File

@@ -0,0 +1,674 @@
# Momentry Core API 示範手冊
| 項目 | 內容 |
|------|------|
| 版本 | V1.0 |
| 日期 | 2026-03-25 |
| 狀態 | 完成 |
---
## 快速開始
### Demo API Key
```
API Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
Key ID: muser_68600856036340bcafc01930eb4bd839
過期日: 2027-03-25
```
### 測試連線
```bash
curl http://localhost:3002/health
```
```json
{"status":"ok","version":"0.1.0","uptime_ms":456464}
```
### 測試認證
```bash
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3002/api/v1/videos | jq '.videos | length'
```
```json
13
```
---
## 環境 URL
| 環境 | URL | 用途 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 本機開發測試 |
| **外部訪問** | `https://api.momentry.ddns.net` | n8n/WordPress/curl 生產環境 |
---
## 端點總覽
| 方法 | 端點 | 說明 | 認證 |
|------|------|------|------|
| GET | `/health` | 健康檢查 | 公開 |
| GET | `/health/detailed` | 詳細健康檢查 | 公開 |
| POST | `/api/v1/register` | 註冊影片 | 需要 |
| POST | `/api/v1/probe` | 探測影片資訊 | 需要 |
| POST | `/api/v1/search` | 語意搜尋 | 需要 |
| POST | `/api/v1/n8n/search` | n8n 格式搜尋 | 需要 |
| POST | `/api/v1/search/hybrid` | 混合搜尋 | 需要 |
| GET | `/api/v1/videos` | 列出所有影片 | 需要 |
| GET | `/api/v1/lookup` | 查詢影片 UUID | 需要 |
| GET | `/api/v1/progress/:uuid` | 處理進度 | 需要 |
| GET | `/api/v1/jobs` | 任務列表 | 需要 |
| GET | `/api/v1/jobs/:uuid` | 任務詳情 | 需要 |
---
## 1. curl 範例
### 基本格式
```bash
curl -H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
URL
```
### 1.1 健康檢查(公開)
```bash
# 基本健康檢查
curl http://localhost:3002/health
# 詳細健康檢查(含服務狀態)
curl http://localhost:3002/health/detailed
```
### 1.2 列出影片
```bash
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3002/api/v1/videos | jq '.'
```
```json
{
"videos": [
{
"uuid": "952f5854b9febad1",
"file_name": "ExaSAN PCIe series - Director Ou Yu-Zhi Shares His Experience.mp4",
"duration": 159.637188,
"width": 640,
"height": 360
},
...
]
}
```
### 1.3 搜尋影片
```bash
curl -X POST \
-H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
-H "Content-Type: application/json" \
-d '{"query": "ExaSAN", "limit": 5}' \
http://localhost:3002/api/v1/search | jq '.'
```
```json
{
"results": [
{
"uuid": "952f5854b9febad1",
"chunk_id": "...",
"text": "...",
"score": 0.85,
"start_time": 0.0,
"end_time": 5.0
}
],
"total": 1,
"query": "ExaSAN",
"took_ms": 123
}
```
### 1.4 查詢進度
```bash
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3002/api/v1/progress/952f5854b9febad1 | jq '.'
```
```json
{
"uuid": "952f5854b9febad1",
"overall_progress": 67,
"current_processor": "yolo",
"processors": [
{"name": "asr", "status": "completed"},
{"name": "cut", "status": "completed"},
{"name": "yolo", "status": "running"}
]
}
```
---
## 2. n8n 範例
### 2.1 HTTP Request 節點設定
```
Method: POST
URL: https://api.momentry.ddns.net/api/v1/search
Authentication: None (使用 Header)
Headers:
┌─────────────────────┬──────────────────────────────────────────────────┐
│ Name │ Value │
├─────────────────────┼──────────────────────────────────────────────────┤
│ X-API-Key │ muser_68600856036340bcafc01930eb4bd839_... │
│ Content-Type │ application/json │
└─────────────────────┴──────────────────────────────────────────────────┘
Body Content (JSON):
{
"query": "{{ $json.search_term }}",
"limit": 5
}
```
### 2.2 n8n 搜尋 Workflow
```json
{
"nodes": [
{
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [250, 300]
},
{
"name": "Set Search Term",
"type": "n8n-nodes-base.set",
"parameters": {
"values": {
"json": {
"search_term": "ExaSAN"
}
}
},
"position": [450, 300]
},
{
"name": "Search Videos",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "POST",
"url": "https://api.momentry.ddns.net/api/v1/search",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-API-Key",
"value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
}
]
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={{ { \"query\": $json.search_term, \"limit\": 5 } }}"
},
"position": [650, 300]
},
{
"name": "Process Results",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "// Extract video results\nconst results = $input.first().json.results;\nreturn results.map(r => ({\n uuid: r.uuid,\n text: r.text,\n score: r.score,\n time: `${r.start_time}s - ${r.end_time}s`\n}));"
},
"position": [850, 300]
}
],
"connections": {
"Manual Trigger": {
"main": [[{"node": "Set Search Term"}]]
},
"Set Search Term": {
"main": [[{"node": "Search Videos"}]]
},
"Search Videos": {
"main": [[{"node": "Process Results"}]]
}
}
}
```
### 2.3 n8n 列出影片 Workflow
```json
{
"nodes": [
{
"name": "Get Videos",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "GET",
"url": "https://api.momentry.ddns.net/api/v1/videos",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "X-API-Key",
"value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
}
]
}
},
"position": [450, 300]
},
{
"name": "Extract Video List",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const videos = $input.first().json.videos;\nreturn videos.map(v => ({\n json: {\n uuid: v.uuid,\n name: v.file_name,\n duration: Math.round(v.duration) + 's',\n resolution: `${v.width}x${v.height}`\n }\n}));"
},
"position": [650, 300]
},
{
"name": "Slack Notification",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#momentry",
"text": "=Found {{ $json.length }} videos:\n{{ $json.map(v => `• ${v.name} (${v.duration})`).join(`\n`) }}"
},
"position": [850, 300]
}
]
}
```
### 2.4 n8n 定時同步 Workflow
```json
{
"nodes": [
{
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [{"field": "hours", "hours": 1}]
}
},
"position": [250, 300]
},
{
"name": "Get Pending Videos",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "GET",
"url": "https://api.momentry.ddns.net/api/v1/videos"
},
"position": [450, 300]
},
{
"name": "Filter Processing",
"type": "n8n-nodes-base.filter",
"parameters": {
"conditions": {
"options": {"caseSensitive": true},
"conditions": [
{"id": "status", "leftValue": "{{ $json.status }}", "rightValue": "processing"}
]
}
},
"position": [650, 300]
}
]
}
```
---
## 3. WordPress 範例
### 3.1 PHP 函數庫
```php
<?php
/**
* Momentry API Client
*/
class Momentry_API {
private const API_URL = 'https://api.momentry.ddns.net';
private const API_KEY = 'muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69';
/**
* 發送 API 請求
*/
private function request(string $endpoint, array $data = [], string $method = 'GET'): array {
$url = self::API_URL . $endpoint;
$args = [
'headers' => [
'X-API-Key' => self::API_KEY,
'Content-Type' => 'application/json',
],
'timeout' => 30,
];
if ($method === 'POST') {
$args['method'] = 'POST';
$args['body'] = json_encode($data);
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message());
}
return json_decode(wp_remote_retrieve_body($response), true);
}
/**
* 列出所有影片
*/
public function list_videos(): array {
return $this->request('/api/v1/videos');
}
/**
* 搜尋影片內容
*/
public function search(string $query, int $limit = 10): array {
return $this->request('/api/v1/search', [
'query' => $query,
'limit' => $limit,
], 'POST');
}
/**
* 取得影片進度
*/
public function get_progress(string $uuid): array {
return $this->request("/api/v1/progress/{$uuid}");
}
/**
* 檢查健康狀態
*/
public function health_check(): array {
return $this->request('/health');
}
}
```
### 3.2 短代碼 (Shortcode)
```php
<?php
/**
* WordPress 短代碼範例
*/
// 註冊短代碼
add_shortcode('momentry_videos', function($atts) {
$atts = shortcode_atts([
'limit' => 10,
], $atts);
$api = new Momentry_API();
try {
$result = $api->list_videos();
$videos = array_slice($result['videos'], 0, $atts['limit']);
ob_start();
?>
<div class="momentry-videos">
<h3>影片列表</h3>
<ul>
<?php foreach ($videos as $video): ?>
<li>
<strong><?= esc_html($video['file_name']) ?></strong>
<br>
<small>
UUID: <?= esc_html($video['uuid']) ?>
| 時長: <?= gmdate("H:i:s", $video['duration']) ?>
</small>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php
return ob_get_clean();
} catch (Exception $e) {
return '<p class="error">載入失敗: ' . esc_html($e->getMessage()) . '</p>';
}
});
// 搜尋短代碼
add_shortcode('momentry_search', function($atts, $content = '') {
$query = sanitize_text_field($content);
if (empty($query)) {
return '<p>請提供搜尋關鍵字</p>';
}
$api = new Momentry_API();
try {
$result = $api->search($query);
ob_start();
?>
<div class="momentry-search-results">
<h3>「<?= esc_html($query) ?>」搜尋結果</h3>
<?php if (empty($result['results'])): ?>
<p>沒有找到相關結果</p>
<?php else: ?>
<ul>
<?php foreach ($result['results'] as $item): ?>
<li>
<a href="/video/<?= esc_attr($item['uuid']) ?>?t=<?= (int)$item['start_time'] ?>">
<?= esc_html($item['text']) ?>
</a>
<br>
<small>相似度: <?= round($item['score'] * 100) ?>%</small>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
} catch (Exception $e) {
return '<p class="error">搜尋失敗: ' . esc_html($e->getMessage()) . '</p>';
}
});
```
### 3.3 使用方式
在 WordPress 頁面或文章中:
```
[momentry_videos limit="5"]
[momentry_search]ExaSAN[/momentry_search]
```
### 3.4 REST API 整合
```php
<?php
/**
* 註冊 WordPress REST API 端點
*/
add_action('rest_api_init', function() {
register_rest_route('momentry/v1', '/search', [
'methods' => 'GET',
'callback' => function(WP_REST_Request $request) {
$query = sanitize_text_field($request->get_param('q'));
if (empty($query)) {
return new WP_Error('missing_query', '需要搜尋關鍵字', ['status' => 400]);
}
$api = new Momentry_API();
$result = $api->search($query);
return new WP_REST_Response($result, 200);
},
'permission_callback' => '__return_true',
]);
});
// 使用方式: GET /wp-json/momentry/v1/search?q=ExaSAN
```
---
## 4. 疑難排解
### 4.1 常見錯誤
| 錯誤 | 原因 | 解決方案 |
|------|------|----------|
| `401 Unauthorized` | API Key 無效或過期 | 檢查 API Key 是否正確 |
| `500 Internal Server Error` | 伺服器錯誤 | 檢查 `/health/detailed` 服務狀態 |
| `Connection Timeout` | 網路問題 | 確認 `api.momentry.ddns.net` 可達 |
### 4.2 測試腳本
```bash
#!/bin/bash
# test_api.sh - Momentry API 測試腳本
API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE_URL="http://localhost:3002"
echo "=== 1. 健康檢查 ==="
curl -s "$BASE_URL/health" | jq .
echo ""
echo "=== 2. 列出影片 ==="
curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos" | jq '.videos | length'
echo ""
echo "=== 3. 搜尋測試 ==="
curl -s -X POST -H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "test", "limit": 3}' \
"$BASE_URL/api/v1/search" | jq '.results | length'
echo ""
echo "=== 完成 ==="
```
### 4.3 驗證腳本
```bash
#!/bin/bash
# verify_auth.sh - 驗證 API Key
API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE_URL="http://localhost:3002"
# 測試 1: 無 API Key
echo "測試 1: 無 API Key"
RESULT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/v1/videos")
[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401實際 $RESULT"
# 測試 2: 有 API Key
echo "測試 2: 有 API Key"
RESULT=$(curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos")
echo "$RESULT" | jq -e '.videos' > /dev/null && echo "✅ 成功取得資料" || echo "❌ 取得資料失敗"
# 測試 3: 無效 API Key
echo "測試 3: 無效 API Key"
RESULT=$(curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: invalid_key" "$BASE_URL/api/v1/videos")
[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401實際 $RESULT"
```
---
## 5. API Key 管理
### 5.1 建立新 API Key
```bash
# 本地建立
./target/release/momentry api-key create "My App" --key-type user --ttl 90
```
### 5.2 列出 API Keys
```bash
./target/release/momentry api-key list
```
### 5.3 驗證 API Key
```bash
./target/release/momentry api-key validate --key "YOUR_API_KEY"
```
### 5.4 撤銷 API Key
```bash
./target/release/momentry api-key revoke --key "YOUR_API_KEY"
```
---
## 附錄
### A. 影片 UUID 說明
UUID 是基於檔案路徑的 SHA256 哈希前 16 位:
```
/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4
SHA256 Hash
9760d0820f0cf9a7
```
### B. 處理器狀態
| 狀態 | 說明 |
|------|------|
| `pending` | 等待處理 |
| `running` | 處理中 |
| `completed` | 已完成 |
| `failed` | 失敗 |
### C. 支援的處理器
- **ASR**: 語音識別
- **CUT**: 場景剪切
- **YOLO**: 物件偵測
### D. 聯絡支援
- Email: support@momentry.ddns.net
- 文件: https://docs.momentry.ddns.net
- GitHub: https://github.com/anomalyco/momentry

View File

@@ -1,5 +1,21 @@
# Momentry Core 開發日誌
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
> **文檔維護開始**2026-03-18
> **⚠️ 補充說明**事後補記2026-03-18 以前),僅供參考。未來紀錄將即時記錄,參考價值較高。
@@ -422,3 +438,103 @@ cargo run --bin momentry -- process <uuid>
# 查詢進度
curl http://127.0.0.1:3002/api/v1/progress/<uuid>
```
---
## 2026-03-18 (Dashboard)
### Web Dashboard 實作
**目標**:建立 Web 介面監控 momentry_core 處理進度
**技術選擇**Static HTML + JavaScript (非 WASM)
**實作內容**
| 元件 | 檔案 | 說明 |
|------|------|------|
| Dashboard | `momentry_dashboard/dist/index.html` | 靜態 HTML 頁面 |
| API 代理 | Caddyfile port 3200 | 反向代理到 API server |
**功能**
- 影片列表顯示
- 即時進度條 (每 5 秒自動刷新)
- 搜尋功能
- 處理器狀態 (ASR/CUT/YOLO/OCR/Face/Pose)
**訪問**
- Dashboard: http://localhost:3200
- API: http://localhost:3200/api/v1/*
---
## 發生問題記錄
### HTTP API 問題
1. **語法錯誤** (main.rs)
- 位置lines 297-322
- 原因:重複的程式碼區塊
- 解決:移除重複區塊
2. **DB 連線池耗盡**
- 原因:預設 5 個連線不足
- 解決:增加到 10 個連線
3. **PostgreSQL shutdown 狀態**
- 原因:共享記憶體未釋放
- 解決:殺掉 stale 連線
### WASM Dashboard 問題
1. **Yew 版本問題**
- 嘗試yew 0.21 → 0.23
- 問題feature 名稱變更 (`web-sys``web_sys``csr`)
- 解決:放棄 WASM改用靜態 HTML
2. **編譯錯誤**
- `wasm32-unknown-unknown` target 未安裝
- 解決:`rustup target add wasm32-unknown-unknown`
3. **Yew 0.23 API 變更**
- Properties 需要 PartialEq derive
- 多處 API 語法變更
- 放棄 WASM 方案
### Gitea Push 問題
1. **Remote URL 錯誤**
- 原因:使用 localhost:3000 而非 gitea.momentry.ddns.net
- 解決:建立新 repo `momentry_core_0_1`
2. **認證問題**
- SSH key 未授權
- 密碼認證成功推送
### Caddy 設定問題
1. **API 代理順序**
- 問題try_files 在 reverse_proxy 之前導致 API 回傳 HTML
- 解決:使用 `handle` 區塊明確定義順序
```caddyfile
:3200 {
handle /api/* {
reverse_proxy localhost:3002
}
handle {
root * /Users/accusys/momentry_dashboard/dist
try_files {path} /index.html
file_server
}
}
```
---
## 未來工作
- [ ] 修復 WASM Dashboard (Yew 0.23 相容性)
- [ ] 新增影片播放器整合
- [ ] WebSocket 實時推送
- [ ] 移動端響應式設計

474
docs/DOCS_STANDARD.md Normal file
View File

@@ -0,0 +1,474 @@
# 文件創建規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件規範 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 專案中文件的命名規範、格式標準和結構要求。
---
## 1. 檔案命名規範
### 命名模式
所有文件必須使用以下命名模式:
| 文件類型 | 模式 | 範例 |
|----------|------|------|
| 安裝指南 | `INSTALL_<NAME>.md` | `INSTALL_POSTGRESQL.md` |
| 開發指南 | `DEVELOP_<NAME>.md` | `DEVELOP_API.md` |
| API 參考 | `API_REFERENCE.md` | `API_REFERENCE.md` |
| 規格文件 | `<NAME>_SPEC.md` | `CHUNK_SPEC.md` |
| 設計文件 | `<NAME>_DESIGN.md` | `CHUNK_DESIGN.md` |
| 服務總覽 | `SERVICES.md` | `SERVICES.md` |
| 其他文件 | `<NAME>.md` | `README.md` |
### 命名規則
- 使用 **大駝峰** (PascalCase) 命名法
- 服務名稱使用 **全大寫** (e.g., `POSTGRESQL`, `SFTPGO`)
- 英文優先,縮寫保持大寫
- 使用底線 `_` 作為單詞分隔符
- 副檔名統一使用 `.md` (Markdown)
### 禁止事項
- 不允許使用中文檔名
- 不允許空格
- 不允許混合大小寫 (如 `Install_PostgreSQL.md`)
---
## 2. 文件結構模板
### 安裝指南結構
```markdown
# <服務名稱> 安裝指南 (部署類型)
## 概述
本文檔說明如何...
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| <服務名> | ✅ 已安裝 v<版本號> |
| Port | <端口號> |
| ... | ... |
---
## 安裝步驟
### Step 1: <步驟名稱>
<說明內容>
```bash
# 代碼範例
command --option value
```
### Step 2: <步驟名稱>
...
---
## 卸載步驟
### Step 1: <步驟名稱>
...
---
## 故障排除
### <問題名稱>
<解決方案>
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | /path/to/install | 說明 |
...
---
## 常用指令
```bash
# 驗證
command verify
# 查看版本
command --version
```
---
## 版本資訊
- 版本: <版本號>
- 安裝日期: <日期>
```
---
### 規格文件結構
```markdown
# <名稱> 規格文件
## 概述
<簡短描述>
---
## 詳細規格
### 1. <功能模組>
#### 欄位定義
| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| field1 | string | Yes | 說明 |
#### 資料結構
```json
{
"example": "data"
}
```
---
## 限制條件
- <限制1>
- <限制2>
---
## 相關文件
- `RELATED_FILE.md` - 相關說明
```
---
## 3. 格式標準
### Markdown 格式
| 項目 | 標準 |
|------|------|
| 標題層級 | H1 (`#`) → H2 (`##`) → H3 (`###`) |
| 水平線 | 使用 `---` 分隔主要章節 |
| 程式碼區塊 | 使用三個反引號 ``` 並標註語言 |
| 表格 | 使用 `|` 和 `-` 對齊 |
| 強調 | 使用 `**粗體**` 和 `*斜體*` |
### 程式碼區塊語言標註
```bash
# Bash
```bash
command
```
```json
# JSON
```json
{"key": "value"}
```
```rust
# Rust
```rust
fn main() {}
```
```yaml
# YAML
key: value
```
### 表格格式
```markdown
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
```
### 列表格式
- 使用 `-` 作為無序列表標記
- 使用數字 `1.` 作為有序列表標記
- 縮進使用 2 個空格
---
## 4. 語言規範
### 標題語言
| 區域 | 語言 |
|------|------|
| 主要內容 | 繁體中文 |
| 技術術語 | 英文保留 |
| 命令和代碼 | 英文 |
| 文件標題 | 繁體中文 |
### 常用術語對照
| 英文 | 中文 |
|------|------|
| Install | 安裝 |
| Configure/Config | 配置/設定 |
| Uninstall | 卸載 |
| Troubleshooting | 故障排除 |
| Status | 狀態 |
| Documentation | 文件 |
| Guide | 指南 |
| Overview | 概述 |
| Specification | 規格 |
| Current Status | 當前狀態 |
| Default | 預設 |
| Required | 必填 |
| Optional | 選填 |
| Example | 範例 |
### 標點符號
- 中文內容使用全形標點:```。```````
- 英文/程式內容使用半形標點:`:``(``)`
- 命令行使用 `` `command` `` 格式
---
## 5. 內容要求
### 必需章節
每份文件必須包含:
1. **標題** - 文件名稱
2. **概述** - 檔案用途說明
3. **版本/狀態資訊** - 當前狀態
4. **檔案位置** - 重要路徑列表
5. **常用指令** - 基本操作命令
### 版本資訊格式
每份文件頂部必須包含以下資訊:
```markdown
| 項目 | 內容 |
|------|------|
| 建立者 | <姓名> |
| 建立時間 | <YYYY-MM-DD> |
| 文件版本 | V1.0 |
```
版本歷史表:
```markdown
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
```
---
### 版本資訊章節格式
```markdown
---
## 版本資訊
- 版本: <版本號>
- 安裝日期: <YYYY-MM-DD>
- 文件更新: <YYYY-MM-DD>
```
### 狀態標記
| 狀態 | 標記 |
|------|------|
| 已安裝 | ✅ 已安裝 v<x.x.x> |
| 未安裝 | ❌ 未安裝 |
| 可選 | ⚙️ 可選 |
| 進行中 | 🔄 進行中 |
---
## 6. 示例文件
### 正確範例
```markdown
# PostgreSQL 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 PostgreSQL...
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| PostgreSQL | ✅ 已安裝 v16.2 |
| Port | 5432 |
---
## 安裝步驟
### Step 1: 安裝 PostgreSQL
```bash
brew install postgresql@16
```
### Step 2: 啟動服務
```bash
brew services start postgresql@16
```
---
## 檔案位置
| 類型 | 路徑 |
|------|------|
| 配置文件 | /path/to/config |
| 數據目錄 | /path/to/data |
---
## 版本資訊
- 版本: 16.2
- 安裝日期: 2026-03-01
```
### 錯誤範例
```
❌ PostgreSQL安裝.md # 中文檔名
❌ install-postgresql.md # 全部小寫
❌ Install PostgreSQL.md # 空格
❌ postgresql_install.md # 非標準命名
```
---
## 7. 文件審查清單
創建新文件時,請確認:
- [ ] 檔案命名符合 `INSTALL_*.md` 或其他標準模式
- [ ] 文件包含頂部資訊表(建立者、建立時間、版本)
- [ ] 文件包含版本歷史表
- [ ] 文件包含概述章節
- [ ] 文件包含當前狀態/版本資訊
- [ ] 文件包含檔案位置章節
- [ ] 文件包含常用指令章節
- [ ] 使用統一的 Markdown 格式
- [ ] 使用繁體中文作為主要語言
- [ ] 程式碼區塊標註語言類型
- [ ] 表格格式正確
- [ ] 章節使用 `---` 分隔
### 頂部資訊表範本
```markdown
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
```
### 版本歷史表範本
```markdown
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
```
---
## 8. 更新現有文件
當更新現有文件時:
1. 更新 **版本資訊** 中的日期
2. 如有必要,更新版本號
3. 記錄重大變更於 `CHANGELOG.md``DEVELOPMENT_LOG.md`
---
## 附錄:文件類型參考
| 前綴 | 用途 | 位置 |
|------|------|------|
| `INSTALL_` | 服務安裝指南 | `/docs/` |
| `DEVELOP_` | 開發指南 | `/docs/` |
| `*_SPEC.md` | 規格定義 | `/docs/` |
| `*_DESIGN.md` | 設計文件 | `/docs/` |
| `API_REFERENCE.md` | API 參考文件 | `/docs/` |
| `README.md` | 專案總覽 | `/` |
| `AGENTS.md` | AI 代理指令 | `/` |
| `CHANGELOG.md` | 變更日誌 | `/` |

View File

@@ -0,0 +1,151 @@
# Document Embedding Strategy - Parent-Child Chunks
## Overview
Momentry uses a **parent-child chunk hierarchy** for improved RAG retrieval. This document describes the embedding strategy for this hierarchy.
## Chunk Structure
### Parent Chunk
- **Purpose**: Summarize multiple child chunks with narrative description
- **Content**: High-level description of multiple scenes/segments
- **Example**:
```json
{
"chunk_id": "story_asr_0000",
"chunk_type": "story",
"text_content": "[0s-125s] A man enters a building. He walks down a hallway.",
"child_chunk_ids": ["asr_0001", "asr_0002", "asr_0003", "asr_0004", "asr_0005"]
}
```
### Child Chunk
- **Purpose**: Individual segments from ASR, scenes from CUT, etc.
- **Content**: Raw transcription or detection results
- **Example**:
```json
{
"chunk_id": "asr_0001",
"chunk_type": "sentence",
"text_content": "Hello world",
"parent_chunk_id": "story_asr_0000"
}
```
## Embedding Strategy
### For Vector Search
When embedding chunks for vector search, we combine **parent description + child content** to provide both context and detail.
#### Parent Chunk Embedding
```
embedding_text = f"Summary: {parent.text_content}
Children: {child_text_1}. {child_text_2}. {child_text_3}..."
```
**Prefix**: `search_document: ` (for documents in Qdrant)
**Example**:
```
search_document: Summary: A man enters a building. He walks down a hallway.
Children: Hello, how are you? I'm fine thank you. The weather is nice today.
```
#### Child Chunk Embedding
```
embedding_text = f"[{child.chunk_type}] {child.text_content}
Parent: {parent.description}"
```
**Prefix**: `search_document: `
**Example**:
```
search_document: [sentence] Hello, how are you?
Parent: A man enters a building. He walks down a hallway.
```
### For BM25 Text Search
BM25 operates on raw text with PostgreSQL full-text search.
- **Index**: `search_vector` (TSVECTOR) on `chunks.text_content`
- **Search**: Uses `ts_rank_cd()` for ranking
## Hybrid Search Ranking
Combined score = `(vector_score * 0.7) + (bm25_score * 0.3)`
### Why 0.7/0.3?
| Weight | Vector | BM25 |
|--------|--------|------|
| Pros | Semantic similarity | Exact keyword match |
| Cons | May miss specific terms | No semantic understanding |
| Best for | Thematic queries | Fact lookup |
## Query Patterns
### Thematic Query ("What are the main themes?")
- Use higher `vector_weight` (0.8-0.9)
- Vector search finds semantically similar content
### Fact Lookup ("Who said X?")
- Use higher `bm25_weight` (0.5-0.7)
- BM25 finds exact matches
### Balanced ("Tell me about scene 5")
- Use default 0.7/0.3
## Implementation
### Embedding Generation
```rust
fn build_embedding_text(chunk: &Chunk, parent_text: Option<&str>) -> String {
match chunk.chunk_type {
ChunkType::Story => {
format!(
"Summary: {}\nChildren: {}",
chunk.text_content,
get_children_text(chunk)
)
}
_ => {
format!(
"[{}] {}\nParent: {}",
chunk.chunk_type.as_str(),
chunk.text_content,
parent_text.unwrap_or("N/A")
)
}
}
}
```
### Storage
- Parent chunks stored with their `child_chunk_ids`
- Child chunks reference `parent_chunk_id`
- Both stored in PostgreSQL with full-text index
- Vectors stored in Qdrant
## Example Flow
1. **Story Processing** generates parent-child hierarchy
2. **Embedding** creates vector for each chunk
3. **Storage** saves to PostgreSQL + Qdrant
4. **Search** retrieves using hybrid search
5. **Results** include both parent context and child details
## Best Practices
1. **Chunk Size**: 5 child chunks per parent (configurable)
2. **Text Length**: Keep embeddings under 512 tokens
3. **Parent Description**: Include temporal markers (timestamps)
4. **Child Content**: Preserve original transcription
## Future Enhancements
- [ ] GraphRAG integration for relationship traversal
- [ ] Cross-chunk entity linking
- [ ] Temporal graph building

View File

@@ -0,0 +1,323 @@
# 文件修改管理規範 v1.0
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.0 |
---
## 1. 概述
本文檔定義 Momentry 專案的文件修改流程,確保不同工具/模型對文件的一致性理解,防止誤修改並保留完整的修改紀錄。
### 1.1 適用範圍
- 所有 `.md` 文件技術文檔、安裝指南、API 文件等)
- 所有 `.rs` 文件Rust 源代碼)
- 所有 `.sh` 文件Shell 腳本)
- 所有 `.yaml` / `.yml` 文件(配置文件)
- 所有 `.json` 文件(配置及數據文件)
### 1.2 核心原則
1. **先讀後改**:修改前必須完整閱讀相關文件
2. **預檢清單**:修改前執行預檢查步驟
3. **變更對照**:修改後必須比對差異
4. **驗證確認**:變更後執行驗證測試
5. **完整紀錄**:所有修改必須記錄於版本歷史
---
## 2. 修改前預檢清單
### 2.1 文件閱讀要求
修改文件前,必須完成以下閱讀:
| 步驟 | 項目 | 說明 |
|------|------|------|
| 1 | 閱讀完整文件 | 不可僅閱讀部分章節 |
| 2 | 理解文件用途 | 確認文件的目標讀者 |
| 3 | 確認現有術語 | 使用一致的術語和命名 |
| 4 | 查閱相關文件 | 確認相關聯的文件 |
### 2.2 預檢問題清單
在修改前回答以下問題:
```
□ 1. 此修改是否影響其他文件?
□ 2. 此修改是否與現有規範衝突?
□ 3. 此修改是否需要更新版本歷史?
□ 4. 此修改是否需要新增測試?
□ 5. 此修改是否需要通知相關人員?
□ 6. 此修改是否有破壞性變更Breaking Change
```
### 2.3 預檢命令
修改前執行以下命令確認現有狀態:
```bash
# 1. 確認 git 狀態
git status
# 2. 檢查相關文件的最新版本
git log -3 --oneline <file_path>
# 3. 查看現有版本歷史
cat docs/<file>.md | grep -A 20 "版本歷史"
```
---
## 3. 文件修改流程
### 3.1 標準修改流程
```
┌─────────────────────────────────────────────────────────────┐
│ Step 1: 閱讀 │
│ ├─ 完整閱讀目標文件 │
│ └─ 閱讀相關聯文件 │
├─────────────────────────────────────────────────────────────┤
│ Step 2: 預檢 │
│ ├─ 回答預檢問題清單 │
│ └─ 執行預檢命令 │
├─────────────────────────────────────────────────────────────┤
│ Step 3: 規劃 │
│ ├─ 說明修改內容 │
│ └─ 列出變更差異 │
├─────────────────────────────────────────────────────────────┤
│ Step 4: 修改 │
│ ├─ 執行修改 │
│ └─ 更新版本歷史 │
├─────────────────────────────────────────────────────────────┤
│ Step 5: 驗證 │
│ ├─ 執行 lint/format 檢查 │
│ └─ 執行相關測試 │
├─────────────────────────────────────────────────────────────┤
│ Step 6: 提交 │
│ └─ 撰寫清晰的 commit message │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 預修改彙報格式
在執行修改前,必須先彙報以下內容:
```markdown
## 檔案
`<file_path>`
## 修改原因
<說明修改的目的>
## 變更內容
```diff
- <刪除的內容>
+ <新增的內容>
```
## 版本歷史更新
| 版本 | 日期 | 內容 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| Vx.x | YYYY-MM-DD | <修改說明> | <操作者> | <使用的工具> |
```
### 3.3 版本歷史格式
每個文件頂部必須包含版本歷史表:
```markdown
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-22 | 更新內容 | Warren | OpenCode / big-pickle |
```
---
## 4. 變更對照
### 4.1 diff 對照
修改後必須提供 diff 對照:
```bash
git diff <file_path>
```
### 4.2 變更類型分類
| 類型 | 標記 | 說明 |
|------|------|------|
| 新增 | `+` | 新增內容 |
| 刪除 | `-` | 刪除內容 |
| 修改 | `~` | 修改內容 |
| 移動 | `↕` | 移動位置 |
| 格式 | `@` | 格式變更 |
### 4.3 變更確認清單
```
□ 1. diff 輸出已確認
□ 2. 變更符合預期
□ 3. 無意外變更
□ 4. 版本歷史已更新
□ 5. 其他關聯文件已檢查
```
---
## 5. 驗證流程
### 5.1 自動化驗證
修改後執行以下自動化檢查:
```bash
# Rust 文件
cargo fmt -- --check
cargo clippy --lib
cargo test --lib
# Python 文件
ruff check <files>
ruff format --check <files>
# Markdown 文件
markdownlint <files>
# Shell 文件
shellcheck -S error <files>
```
### 5.2 手動驗證清單
```
□ 1. 文件語法正確
□ 2. 連結有效
□ 3. 格式一致
□ 4. 術語一致
□ 5. 版本歷史完整
□ 6. 變更記錄清晰
```
---
## 6. 提交規範
### 6.1 Commit Message 格式
```
<type>: <subject>
<body>
<footer>
```
### 6.2 Type 類型
| Type | 說明 |
|------|------|
| `feat` | 新功能 |
| `fix` | 錯誤修復 |
| `refactor` | 重構 |
| `docs` | 文檔更新 |
| `style` | 格式變更 |
| `test` | 測試相關 |
| `chore` | 維護工作 |
### 6.3 Commit Message 範例
```bash
# 文檔更新
git commit -m "docs: update INSTALL_MONGODB.md with LaunchAgent instructions
- Add LaunchDaemon plist installation steps
- Update management commands section
- Add version history entry
Closes: #xxx"
# 配置文件更新
git commit -m "fix: remove duplicate mongodb entry in monitor_config.yaml
The backup section had two mongodb entries which caused confusion.
Removed the duplicate config entry at line 408-414."
```
---
## 7. AI 工具修改額外規範
### 7.1 修改前確認
AI 工具修改文件前,必須:
1. **完整閱讀**:閱讀完整文件(不可只讀取部分)
2. **理解語境**:理解文件的用途和目標讀者
3. **查閱相關**:查閱相關聯文件確保一致性
4. **提出計畫**:修改前先提出變更計畫供確認
### 7.2 修改後確認
AI 工具修改文件後,必須:
1. **展示差異**:顯示修改的 diff 內容
2. **說明變更**:解釋每項變更的目的
3. **執行驗證**:執行相關的 lint/test 命令
4. **更新歷史**:更新版本歷史表
### 7.3 禁止事項
AI 工具**禁止**以下行為:
```
□ 未閱讀完整文件即進行修改
□ 未經確認直接執行大規模修改
□ 未提供 diff 即提交變更
□ 未更新版本歷史
□ 未執行驗證即聲稱完成
□ 擅自刪除其他工具/模型添加的內容
□ 無視現有文件規範
```
---
## 8. 審查清單
### 8.1 提交前審查
```
□ 1. 修改內容已完整閱讀
□ 2. 預檢問題清單已回答
□ 3. diff 對照已確認
□ 4. 版本歷史已更新
□ 5. 自動化驗證已通過
□ 6. 手動驗證清單已完成
□ 7. Commit message 符合規範
□ 8. 無敏感資訊泄露
```
### 8.2 Code Review 關注點
- [ ] 修改是否影響現有功能?
- [ ] 修改是否與專案規範一致?
- [ ] 是否有遺漏的關聯修改?
- [ ] 測試是否足夠?
- [ ] 文檔是否同步更新?
---
## 9. 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode / big-pickle |

View File

@@ -0,0 +1,707 @@
# Momentry 系統全新 Mac 安裝指南
| 項目 | 內容 |
|------|------|
| 建立時間 | 2026-03-23 |
| 文件版本 | V1.0 |
| 適用對象 | 全新 Mac (Intel 或 Apple Silicon) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-23 | 創建文件 | OpenCode |
---
## 快速概覽
### 安裝時間估算
| 項目 | 時間 |
|------|------|
| 系統準備 | 30 分鐘 |
| Homebrew 安裝 | 15 分鐘 |
| 資料庫服務 | 30 分鐘 |
| 應用服務 | 60 分鐘 |
| Momentry Core | 20 分鐘 |
| **總計** | **~2.5 小時** |
### 系統需求
| 項目 | 最低需求 | 推薦需求 |
|------|----------|----------|
| macOS | 13.0 Ventura | 14.0+ Sonoma |
| 記憶體 | 8GB | 16GB+ |
| 儲存空間 | 100GB | 500GB+ |
| CPU | Apple Silicon M1 或 Intel i5 | M2/M3 或 Intel i7+ |
---
## 第一部分:系統準備
### 1.1 macOS 初始設定
首次開機後,執行以下設定:
```bash
# 1. 設定電腦名稱
sudo scutil --set ComputerName "Momentry"
sudo scutil --set LocalHostName "momentry"
# 2. 啟用 SSH 遠端登入
sudo systemsetup -setremotelogin on
# 3. 關閉休眠 (防止遠端斷線)
sudo pmset -a sleep 0
sudo pmset -a hibernatemode 0
# 4. 允許任何來源 App (安裝開發工具用)
sudo spctl --master-disable
# 5. 設定時區
sudo systemsetup -settimezone "Asia/Taipei"
```
### 1.2 建立管理員帳戶
```bash
# 建立 Momentry 管理員帳戶 (可選)
sudo dscl . -create /Users/momentry
sudo dscl . -create /Users/momentry UserShell /bin/zsh
sudo dscl . -create /Users/momentry RealName "Momentry Admin"
sudo dscl . -create /Users/momentry PrimaryGroupID 80
sudo dscl . -create /Users/momentry UniqueID 1001
sudo dscl . -passwd /Users/momentry "momentry_password"
sudo dscl . -append /Groups/admin GroupMembership momentry
```
---
## 第二部分Homebrew 安裝
### 2.1 安裝 Homebrew
```bash
# 安裝 Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Apple Silicon 設定
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Intel Mac 設定
# echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile
# eval "$(/usr/local/bin/brew shellenv)"
# 驗證
brew --version
```
### 2.2 安裝基礎工具
```bash
# 開發工具
brew install \
git \
curl \
wget \
jq \
yq \
tree \
htop \
tmux \
zsh \
zsh-completions \
ncdu \
httpie \
openssl \
readline \
sqlite \
python@3.11 \
rustup-init
# Rust 初始化
rustup-init -y
source "$HOME/.cargo/env"
# Python 虛擬環境
python3.11 -m venv ~/.venv
source ~/.venv/bin/activate
pip install --upgrade pip
```
---
## 第三部分:建立目錄結構
```bash
# 建立目錄結構
mkdir -p /Users/accusys/momentry/{var,etc,log,scripts,backup}
mkdir -p /Users/accusys/momentry/var/{postgresql,mongodb,mariadb,redis,qdrant,n8n,ollama,sftpgo}
mkdir -p /Users/accusys/momentry/etc/{sftpgo,caddy,gitea,php}
mkdir -p /Users/accusys/momentry/scripts
mkdir -p /Users/accusys/momentry/backup/{daily,weekly,monthly}
mkdir -p /Users/accusys/workspace/sftpgo
mkdir -p /Users/accusys/sftpgo_test/{demo,uploads}
# 設定權限
chown -R accusys:staff /Users/accusys/momentry
chown -R accusys:staff /Users/accusys/workspace
chown -R accusys:staff /Users/accusys/sftpgo_test
```
---
## 第四部分:資料庫服務安裝
### 4.1 PostgreSQL
```bash
# 安裝
brew install postgresql@18
# 啟動服務
brew services start postgresql@18
# 建立資料庫
createdb momentry
createdb video_register
createdb n8n
createdb sftpgo
# 建立用戶
psql -U accusys -d postgres << 'EOF'
CREATE USER sftpgo WITH PASSWORD 'sftpgo_pass_2026';
GRANT ALL PRIVILEGES ON DATABASE sftpgo TO sftpgo;
GRANT ALL PRIVILEGES ON DATABASE momentry TO accusys;
GRANT ALL PRIVILEGES ON DATABASE video_register TO accusys;
GRANT ALL PRIVILEGES ON DATABASE n8n TO accusys;
EOF
# 驗證
pg_isready -h 127.0.0.1 -p 5432
```
### 4.2 MongoDB
```bash
# 安裝
brew tap mongodb/brew
brew install mongodb-community
# 建立資料目錄
mkdir -p /Users/accusys/momentry/var/mongodb
chown -R accusys:staff /Users/accusys/momentry/var/mongodb
# 啟動服務
brew services start mongodb-community
# 建立用戶
mongosh admin --eval "
db.createUser({
user: 'accusys',
pwd: 'Test3200Test3200',
roles: [
{ role: 'readWriteAnyDatabase', db: 'admin' },
{ role: 'dbAdminAnyDatabase', db: 'admin' }
]
});
"
# 驗證
mongosh --quiet --eval "db.adminCommand('ping')"
```
### 4.3 Redis
```bash
# 安裝
brew install redis
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/redis
# 建立配置文件
cat > /Users/accusys/momentry/etc/redis/redis.conf << 'EOF'
requirepass accusys
dir /Users/accusys/momentry/var/redis
logfile /Users/accusys/momentry/log/redis.log
daemonize no
port 6379
bind 127.0.0.1
EOF
# 啟動服務
redis-server /Users/accusys/momentry/etc/redis/redis.conf &
sleep 2
# 驗證
redis-cli -a accusys ping
```
### 4.4 MariaDB (WordPress)
```bash
# 安裝
brew install mariadb
# 啟動服務
brew services start mariadb
# 安全設定
mysql_secure_installation
# 建立資料庫
mysql -u root << 'EOF'
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress'@'localhost' IDENTIFIED BY 'wordpress_password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';
FLUSH PRIVILEGES;
EOF
# 驗證
mysql -u root -e "SHOW DATABASES;"
```
---
## 第五部分:應用服務安裝
### 5.1 Ollama (LLM)
```bash
# 安裝
brew install ollama
# 建立資料目錄
mkdir -p /Users/accusys/momentry/var/ollama
mkdir -p ~/.ollama/models
# 啟動服務
brew services start ollama
# 下載模型
ollama pull llama3.2
ollama pull nomic-embed-text
# 驗證
curl -s http://localhost:11434/api/tags | jq '.models[].name'
```
### 5.2 Caddy (反向代理)
```bash
# 安裝
brew install caddy
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/caddy
# 建立 Caddyfile
cat > /Users/accusys/momentry/etc/caddy/Caddyfile << 'EOF'
{
admin off
auto_https off
}
# 範例:本地開發
:8080 {
respond "Momentry Server" 200
}
EOF
# 啟動服務
brew services start caddy
# 驗證
curl -s http://localhost:8080
```
### 5.3 Gitea (Git 服務)
```bash
# 安裝
brew install gitea
# 建立資料目錄
mkdir -p /Users/accusys/momentry/var/gitea
mkdir -p /Users/accusys/momentry/etc/gitea
# 複製設定檔
cp /opt/homebrew/etc/gitea/conf/app.ini /Users/accusys/momentry/etc/gitea/
# 啟動服務
brew services start gitea
# 驗證
curl -s http://localhost:3000
```
### 5.4 n8n (工作流程自動化)
```bash
# 安裝 Node.js (如果尚未安裝)
brew install node@22
# 全域安裝 n8n
npm install -g n8n
# 建立資料目錄
mkdir -p /Users/accusys/momentry/var/n8n
# 設定環境變數
export N8N_DATA_DIR=/Users/accusys/momentry/var/n8n
export DB_TYPE=postgresdb
export DB_POSTGRES_HOST=127.0.0.1
export DB_POSTGRES_PORT=5432
export DB_POSTGRES_DATABASE=n8n
export DB_POSTGRES_USER=accusys
export DB_POSTGRES_PASSWORD=accusys
export N8N_ENCRYPTION_KEY=Test3200Test3200Test3200
# 啟動服務
n8n start &
# 驗證
curl -s http://localhost:5678
```
### 5.5 SFTPGo (SFTP 服務)
```bash
# 安裝
brew install sftpgo
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/sftpgo
# 建立配置文件
cat > /Users/accusys/momentry/etc/sftpgo/sftpgo.json << 'EOF'
{
"data_provider": {
"driver": "postgresql",
"host": "127.0.0.1",
"port": 5432,
"username": "sftpgo",
"password": "sftpgo_pass_2026",
"name": "sftpgo",
"create_default_admin": true
},
"httpd": {
"bind_port": 8080,
"bind_address": "127.0.0.1"
},
"sftpd": {
"bindings": [
{"port": 2022}
]
},
"ftpd": {
"bindings": [
{"port": 0}
]
}
}
EOF
# 建立 launchd plist
cat > /Library/LaunchDaemons/com.momentry.sftpgo.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.sftpgo</string>
<key>UserName</key>
<string>accusys</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/workspace/sftpgo</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/sftpgo</string>
<string>serve</string>
<string>--config-file</string>
<string>/Users/accusys/momentry/etc/sftpgo/sftpgo.json</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/accusys</string>
<key>SFTPGO_DEFAULT_ADMIN_USERNAME</key>
<string>admin</string>
<key>SFTPGO_DEFAULT_ADMIN_PASSWORD</key>
<string>Test3200Test3200</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/sftpgo.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/sftpgo.error.log</string>
</dict>
</plist>
EOF
# 載入服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
# 驗證
sleep 3
curl -s -X POST http://localhost:8080/api/v2/token -u "admin:Test3200Test3200"
```
### 5.6 Qdrant (向量資料庫)
```bash
# 安裝 Rust (如果尚未安裝)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# 安裝 Qdrant
cargo install qdrant
# 建立資料目錄
mkdir -p /Users/accusys/momentry/var/qdrant/storage
# 啟動服務
qdrant --data-path /Users/accusys/momentry/var/qdrant/storage --api-key Test3200Test3200Test3200 &
# 驗證
sleep 3
curl -s http://localhost:6333/collections -H "api-key: Test3200Test3200Test3200"
```
---
## 第六部分Momentry Core 安裝
### 6.1 安裝依賴
```bash
# 安裝 Python 依賴
pip install \
openai-whisper \
ultralytics \
opencv-python \
pillow \
python-dotenv \
requests \
httpx
# 安裝 FFmpeg
brew install ffmpeg
```
### 6.2 安裝 Momentry Core
```bash
# 克隆或進入專案目錄
cd /Users/accusys/momentry_core_0.1
# 編譯專案
cargo build --release
# 複製執行檔
cp target/release/momentry /usr/local/bin/
# 建立 launchd plist
cat > /Library/LaunchDaemons/com.momentry.api.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.api</string>
<key>UserName</key>
<string>accusys</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/momentry</string>
<string>server</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>3002</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>DATABASE_URL</key>
<string>postgres://accusys:accusys@127.0.0.1:5432/momentry</string>
<key>RUST_LOG</key>
<string>info</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/momentry.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/momentry.error.log</string>
</dict>
</plist>
EOF
# 載入服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 驗證
sleep 3
curl -s http://localhost:3002/health
```
---
## 第七部分:服務驗證
### 7.1 健康檢查腳本
```bash
#!/bin/bash
# health_check.sh
echo "=========================================="
echo "Momentry System Health Check"
echo "=========================================="
echo ""
# PostgreSQL
pg_isready -h 127.0.0.1 -p 5432 > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ PostgreSQL" || echo "❌ PostgreSQL"
# MongoDB
mongosh --quiet --eval "db.adminCommand('ping')" > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ MongoDB" || echo "❌ MongoDB"
# Redis
redis-cli -a accusys ping > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ Redis" || echo "❌ Redis"
# Ollama
curl -s http://localhost:11434/api/tags > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ Ollama" || echo "❌ Ollama"
# n8n
curl -s http://localhost:5678 > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ n8n" || echo "❌ n8n"
# SFTPGo
curl -s http://localhost:8080 > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ SFTPGo" || echo "❌ SFTPGo"
# Qdrant
curl -s http://localhost:6333/ > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ Qdrant" || echo "❌ Qdrant"
# Momentry API
curl -s http://localhost:3002/health > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ Momentry API" || echo "❌ Momentry API"
# Caddy
curl -sI http://localhost:8080 > /dev/null 2>&1
[ $? -eq 0 ] && echo "✅ Caddy" || echo "❌ Caddy"
echo ""
echo "=========================================="
```
### 7.2 執行驗證
```bash
# 儲存並執行
chmod +x health_check.sh
./health_check.sh
```
---
## 第八部分:重要憑證總覽
### 8.1 服務密碼
| 服務 | 用戶名 | 密碼 | 用途 |
|------|--------|------|------|
| **PostgreSQL** | accusys | `accusys` | 主要資料庫 |
| **PostgreSQL** | sftpgo | `sftpgo_pass_2026` | SFTPGo 資料庫 |
| **MongoDB** | accusys | `Test3200Test3200` | 文件資料庫 |
| **Redis** | - | `accusys` | 快取服務 |
| **n8n** | - | `Test3200Test3200Test3200` | n8n 加密金鑰 |
| **SFTPGo** | admin | `Test3200Test3200` | SFTPGo 管理員 |
| **Qdrant** | - | `Test3200Test3200Test3200` | Qdrant API Key |
| **MariaDB** | root | (設定時指定) | WordPress 資料庫 |
| **MariaDB** | wordpress | `wordpress_password` | WordPress 資料庫用戶 |
### 8.2 服務連接埠
| 服務 | 連接埠 | 協定 |
|------|--------|------|
| PostgreSQL | 5432 | TCP |
| MongoDB | 27017 | TCP |
| Redis | 6379 | TCP |
| Ollama | 11434 | HTTP |
| n8n | 5678 | HTTP |
| SFTPGo | 8080 | HTTP |
| SFTPGo | 2022 | SFTP |
| Qdrant | 6333 | HTTP |
| Momentry API | 3002 | HTTP |
| Caddy | 80/443 | HTTP/HTTPS |
| Gitea | 3000 | HTTP |
| PHP-FPM | 9000 | TCP |
---
## 第九部分:相關文檔
| 文檔 | 說明 |
|------|------|
| `SERVICES.md` | 服務詳細說明 |
| `INSTALL_POSTGRESQL.md` | PostgreSQL 安裝指南 |
| `INSTALL_MONGODB.md` | MongoDB 安裝指南 |
| `INSTALL_REDIS.md` | Redis 安裝指南 |
| `INSTALL_QDRANT.md` | Qdrant 安裝指南 |
| `INSTALL_N8N.md` | n8n 安裝指南 |
| `INSTALL_SFTPGO.md` | SFTPGo 安裝指南 |
| `SFTPGO_DEMO_USER.md` | SFTPGo Demo 用戶指南 |
| `MOMENTRY_CORE_MONITORING.md` | 監控規範 |
| `API_INDEX.md` | API 文件索引 |
---
## 附錄:常見問題
### Q1: n8n 無法連接 PostgreSQL
確保資料庫用戶有登入權限:
```bash
psql -U accusys -d postgres -c "ALTER USER accusys WITH LOGIN;"
```
### Q2: SFTPGo 無法啟動
檢查 PostgreSQL 是否運行:
```bash
pg_isready -h 127.0.0.1 -p 5432
```
### Q3: Qdrant API Key 無效
使用正確的 API Key
```bash
curl -H "api-key: Test3200Test3200Test3200" http://localhost:6333/collections
```
### Q4: Momentry API 無法啟動
檢查環境變數:
```bash
export DATABASE_URL="postgres://accusys:accusys@127.0.0.1:5432/momentry"
export RUST_LOG=debug
momentry server --host 0.0.0.0 --port 3002
```

View File

@@ -1,5 +1,21 @@
# Caddy 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 Caddy Web Server配置為本地部署作為反向代理伺服器。

View File

@@ -1,5 +1,21 @@
# Gitea 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 Gitea Git 服務,配置為本地部署。

393
docs/INSTALL_GITEA_MCP.md Normal file
View File

@@ -0,0 +1,393 @@
# Gitea MCP Server 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-03-24 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-24 | 創建文件 | OpenCode | OpenCode / big-pickle |
---
## 概述
本文檔說明如何在 macOS 上安裝 Gitea MCP Server配置為透過 OpenCode MCP 整合存取 Gitea API。
Gitea MCP Server 是一個 MCP (Model Context Protocol) 伺服器,提供對 Gitea API 的完整存取能力,包括 repos、issues、pull requests、workflows 等資源管理。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Gitea MCP Server | ✅ 已安裝 |
| 安裝方式 | Homebrew (`gitea-mcp-server`) |
| Plist | /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist |
| 執行身份 | accusys |
| 監聽端口 | 8787 |
| Gitea 主機 | http://localhost:3000 |
| Launchd 狀態 | ✅ 已註冊 |
| RunAtLoad | ✅ 已設定 |
| KeepAlive | ✅ 已設定 |
---
## 前置條件
- Gitea 服務已運行(端口 3000
- Homebrew 已安裝
- 管理員權限
---
## 安裝步驟
### Step 1: 安裝 Gitea MCP Server
```bash
brew install gitea-mcp-server
```
**驗證**:
```bash
which gitea-mcp-server
# /opt/homebrew/bin/gitea-mcp-server
/opt/homebrew/bin/gitea-mcp-server --help
```
---
### Step 2: 創建日誌目錄
```bash
mkdir -p /Users/accusys/momentry/log
touch /Users/accusys/momentry/log/gitea-mcp-server.log
touch /Users/accusys/momentry/log/gitea-mcp-server.error.log
```
---
### Step 3: 獲取 Gitea API Token
#### 步驟 3.1: 登入 Gitea
1. 開啟瀏覽器,訪問 http://localhost:3000
2. 使用管理員帳號登入
#### 步驟 3.2: 生成 Personal Access Token
1. 點擊右上角頭像 → **Settings**
2. 左側選單選擇 **Applications**
3.**Access Tokens** 區塊:
- **Token Name**: `opencode-mcp`
- **Expiration**: 選擇過期時間(如 365 days
- **Scopes**: 勾選需要的權限
- `repo` - 倉庫操作
- `read:user` - 讀取用戶資訊
- `read:org` - 讀取組織資訊
4. 點擊 **Generate Token**
5. **立即複製** 生成的 Token
#### 步驟 3.3: 驗證 Token
```bash
curl -H "Authorization: token <YOUR_TOKEN>" http://localhost:3000/api/v1/user
```
---
### Step 4: 創建 Plist 檔案
```bash
sudo tee /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.gitea-mcp-server</string>
<key>UserName</key>
<string>accusys</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/gitea-mcp-server</string>
<string>-transport</string>
<string>http</string>
<string>-port</string>
<string>8787</string>
<string>-host</string>
<string>http://localhost:3000</string>
<string>-token</string>
<string><GITEA_TOKEN></string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/gitea-mcp-server.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/gitea-mcp-server.error.log</string>
</dict>
</plist>
EOF
```
**注意**: 將 `<GITEA_TOKEN>` 替換為實際的 Token 值。
---
### Step 5: 設定權限
```bash
sudo chown root:wheel /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
sudo chmod 644 /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
```
---
### Step 6: 載入服務
```bash
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
```
---
### Step 7: 驗證服務
```bash
# 檢查服務狀態
sudo launchctl list | grep gitea-mcp-server
# 檢查進程
ps aux | grep gitea-mcp-server | grep -v grep
# 測試端點
curl -s http://localhost:8787/
```
---
### Step 8: 整合 OpenCode MCP
#### 步驟 8.1: 更新 OpenCode 配置
編輯 `~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
#### 步驟 8.2: 重啟 OpenCode
```bash
# 停止現有 OpenCode
pkill -f opencode
# 重新啟動
opencode
```
#### 步驟 8.3: 驗證 MCP 連接
在 OpenCode 中執行:
```
/mcps
```
確認 gitea 顯示為 `connected`
---
## Plist 參數說明
| 參數 | 說明 | 值 |
|------|------|-----|
| `-transport` | 傳輸類型 | `http` |
| `-port` | HTTP 監聽端口 | `8787` |
| `-host` | Gitea 主機 URL | `http://localhost:3000` |
| `-token` | Gitea API Token | 見 Step 3 |
---
## 管理指令
### 啟動服務
```bash
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
```
---
### 停止服務
```bash
sudo launchctl bootout system/com.momentry.gitea-mcp-server
```
---
### 重啟服務
```bash
sudo launchctl bootout system/com.momentry.gitea-mcp-server
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
```
---
### 查看日誌
```bash
# 即時查看日誌
tail -f /Users/accusys/momentry/log/gitea-mcp-server.log
# 錯誤日誌
tail -f /Users/accusys/momentry/log/gitea-mcp-server.error.log
```
---
## 卸載步驟
### Step 1: 停止服務
```bash
sudo launchctl bootout system/com.momentry.gitea-mcp-server
```
---
### Step 2: 移除 Plist
```bash
sudo rm /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist
```
---
### Step 3: 從 OpenCode 配置移除
編輯 `~/.config/opencode/opencode.json`,移除 `mcp.gitea` 區塊。
---
## 故障排除
### 服務無法啟動
1. 檢查 Token 是否正確
2. 檢查 Gitea 是否運行:`curl -s http://localhost:3000/`
3. 檢查日誌:`/Users/accusys/momentry/log/gitea-mcp-server.error.log`
---
### Token 無效
1. 確認 Token 未過期
2. 確認 Token 有足夠的權限
3. 重新生成 Token
---
### 端口被佔用
```bash
# 檢查端口占用
lsof -i :8787
# 修改 plist 中的端口後重新載入
```
---
### OpenCode MCP 未顯示
1. 確認 OpenCode 已重啟
2. 檢查 `~/.config/opencode/opencode.json` 格式正確
3. 確認 gitea-mcp-server 程序正在運行
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| Plist | /Library/LaunchDaemons/com.momentry.gitea-mcp-server.plist | Launchd 服務配置 |
| 日誌 | /Users/accusys/momentry/log/gitea-mcp-server.log | 標準輸出日誌 |
| 錯誤日誌 | /Users/accusys/momentry/log/gitea-mcp-server.error.log | 錯誤日誌 |
| OpenCode 配置 | ~/.config/opencode/opencode.json | MCP 設定檔 |
| Gitea | http://localhost:3000 | Gitea Web UI |
---
## 常用指令
```bash
# 驗證服務狀態
sudo launchctl list | grep gitea-mcp-server
# 查看服務 PID
ps aux | grep gitea-mcp-server | grep -v grep
# 測試端點
curl -s http://localhost:8787/
# 驗證 Gitea 連接
curl -H "Authorization: token <TOKEN>" http://localhost:3000/api/v1/user
# 查看即時日誌
tail -f /Users/accusys/momentry/log/gitea-mcp-server.log
```
---
## 版本資訊
- 版本: 1.0
- 安裝日期: 2026-03-24
- 文件更新: 2026-03-24
---
## 相關文件
- [OpenCode MCP 整合](./N8N_MCP_SETUP.md) - n8n MCP 設定說明
- [服務總覽](./SERVICES.md) - 所有服務狀態總覽
- [待解決問題](./PENDING_ISSUES.md) - MCP 安裝狀態追蹤

View File

@@ -1,5 +1,21 @@
# MariaDB 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 MariaDB配置為本地部署支援遠端訪問。

View File

@@ -0,0 +1,464 @@
# Momentry Core API 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-03-23 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-23 | 創建文件 | OpenCode | - |
---
## 概述
本文檔說明如何在 macOS 上安裝 Momentry Core API 服務,配置為本地部署,並設定開機自動啟動。
Momentry Core API 是一個 Rust 編寫的數位資產管理 API 服務,提供:
- 影片搜尋 API (`/api/v1/search`)
- n8n 整合 API (`/api/v1/n8n/search`)
- 健康檢查端點 (`/health`)
- 影片註冊與處理功能
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| Momentry Core API | ✅ 已安裝 v0.1.0 |
| Binary | `/Users/accusys/momentry_core_0.1/target/release/momentry` |
| Port | 3002 |
| 反向代理 | Caddy (`api.momentry.ddns.net`) |
| 數據庫 | PostgreSQL (momentry) |
| 向量庫 | Qdrant |
| Cache | Redis |
| launchd plist | ✅ 已建立 (/Library/LaunchDaemons/com.momentry.api.plist) |
---
## 系統需求
### 必要服務
| 服務 | 版本 | 用途 |
|------|------|------|
| PostgreSQL | 16+ | 主數據庫 |
| Redis | 1.0+ | 快取與佇列 |
| Qdrant | 1.7+ | 向量搜尋 |
| Ollama | 最新 | LLM 與 Embedding |
### Rust 環境
```bash
# 檢查 Rust 版本
rustc --version
cargo --version
# 如需安裝 Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
---
## 安裝步驟
### Step 1: 編譯 Momentry Core
```bash
# 進入專案目錄
cd /Users/accusys/momentry_core_0.1
# 編譯 release 版本
cargo build --release
# 驗證編譯結果
ls -la target/release/momentry
```
---
### Step 2: 設定環境變數
建立環境變數檔案:
```bash
# 建立執行目錄
mkdir -p /Users/accusys/momentry_core_0.1/momentry_runtime/env
# 建立環境變數檔案
cat > /Users/accusys/momentry_core_0.1/momentry_runtime/env/momentry.env << 'EOF'
# Database Configuration
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# Redis Configuration
REDIS_URL=redis://:accusys@localhost:6379
REDIS_PASSWORD=accusys
# API Server
API_HOST=127.0.0.1
API_PORT=3002
# Ollama (LLM)
OLLAMA_HOST=http://localhost:11434
# Qdrant (Vector Database)
QDRANT_URL=http://localhost:6333
QDRANT_COLLECTION=momentry_chunks
EOF
```
---
### Step 3: 手動啟動服務
```bash
# 啟動 API 服務
cd /Users/accusys/momentry_core_0.1
./target/release/momentry server --port 3002
# 驗證服務
curl http://localhost:3002/health
# {"status":"ok","version":"0.1.0","uptime_ms":1234}
```
---
### Step 4: 設定 Caddy 反向代理
`/Users/accusys/momentry/etc/Caddyfile` 中新增:
```caddy
# Momentry Core API
api.momentry.ddns.net {
reverse_proxy localhost:3002
import common_log momentry_api_access
}
```
重新載入 Caddy
```bash
# 重新載入配置
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
# 驗證
curl -sk https://api.momentry.ddns.net/health
```
---
### Step 5: 建立 launchd plist (開機自動啟動)
建立 plist 檔案:
```bash
sudo tee /Library/LaunchDaemons/com.momentry.api.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.api</string>
<key>UserName</key>
<string>accusys</string>
<key>GroupName</key>
<string>staff</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry_core_0.1</string>
<key>ProgramArguments</key>
<array>
<string>/Users/accusys/momentry_core_0.1/target/release/momentry</string>
<string>server</string>
<string>--port</string>
<string>3002</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>DATABASE_URL</key>
<string>postgres://accusys@localhost:5432/momentry</string>
<key>REDIS_URL</key>
<string>redis://:accusys@localhost:6379</string>
<key>REDIS_PASSWORD</key>
<string>accusys</string>
<key>OLLAMA_HOST</key>
<string>http://localhost:11434</string>
<key>QDRANT_URL</key>
<string>http://localhost:6333</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/momentry_api.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/momentry_api.error.log</string>
</dict>
</plist>
EOF
```
建立日誌檔案:
```bash
# 建立日誌目錄(如不存在)
mkdir -p /Users/accusys/momentry/log
# 建立日誌檔案
touch /Users/accusys/momentry/log/momentry_api.log
touch /Users/accusys/momentry/log/momentry_api.error.log
# 設定權限
chown accusys:staff /Users/accusys/momentry/log/momentry_api.log
chown accusys:staff /Users/accusys/momentry/log/momentry_api.error.log
```
載入服務:
```bash
# 載入服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 驗證服務
launchctl list | grep momentry.api
# 檢查服務狀態
curl http://localhost:3002/health
```
---
## 卸載步驟
### Step 1: 停止並移除服務
```bash
# 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 移除 plist
sudo rm /Library/LaunchDaemons/com.momentry.api.plist
```
### Step 2: 移除 Caddy 配置
`/Users/accusys/momentry/etc/Caddyfile` 中移除 `api.momentry.ddns.net` 區塊。
```bash
# 重新載入 Caddy
caddy reload --config /Users/accusys/momentry/etc/Caddyfile
```
---
## 故障排除
### API 返回 502 Bad Gateway
**問題**: `api.momentry.ddns.net` 返回 502 錯誤
**原因**: Momentry Core API 服務未啟動
**解決方案**:
```bash
# 檢查服務狀態
launchctl list | grep momentry.api
# 如服務未啟動,手動啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 或手動啟動測試
cd /Users/accusys/momentry_core_0.1
./target/release/momentry server --port 3002
```
---
### API 返回 404 Not Found
**問題**: 端點返回 404
**原因**: Binary 過舊,缺少該端點
**解決方案**:
```bash
# 重新編譯
cd /Users/accusys/momentry_core_0.1
cargo build --release
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
### 服務無法啟動
**問題**: launchd 無法啟動服務
**檢查步驟**:
```bash
# 檢查日誌
tail -50 /Users/accusys/momentry/log/momentry_api.error.log
# 檢查 plist 語法
plutil -lint /Library/LaunchDaemons/com.momentry.api.plist
# 檢查權限
ls -la /Users/accusys/momentry_core_0.1/target/release/momentry
# 手動測試
/Users/accusys/momentry_core_0.1/target/release/momentry server --port 3002
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| Binary | `/Users/accusys/momentry_core_0.1/target/release/momentry` | 執行檔 |
| 環境變數 | `/Users/accusys/momentry_core_0.1/momentry_runtime/env/momentry.env` | 環境設定 |
| launchd plist | `/Library/LaunchDaemons/com.momentry.api.plist` | 開機啟動配置 |
| 日誌 | `/Users/accusys/momentry/log/momentry_api.log` | 標準輸出 |
| 錯誤日誌 | `/Users/accusys/momentry/log/momentry_api.error.log` | 錯誤輸出 |
| Caddy 配置 | `/Users/accusys/momentry/etc/Caddyfile` | 反向代理配置 |
---
## 常用指令
### 服務管理
```bash
# 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 檢查服務狀態
launchctl list | grep momentry.api
```
### 健康檢查
```bash
# 本地健康檢查
curl http://localhost:3002/health
# 詳細健康檢查
curl http://localhost:3002/health/detailed
# 外部健康檢查
curl -sk https://api.momentry.ddns.net/health
```
### API 測試
```bash
# 搜尋 API
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query":"test"}'
# n8n 搜尋 API
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"test"}'
# 列出影片
curl http://localhost:3002/api/v1/videos
```
### 日誌查看
```bash
# 查看最近的日誌
tail -50 /Users/accusys/momentry/log/momentry_api.log
# 即時監控日誌
tail -f /Users/accusys/momentry/log/momentry_api.log
# 查看錯誤日誌
tail -50 /Users/accusys/momentry/log/momentry_api.error.log
```
### 重新編譯
```bash
# 編譯 release 版本
cd /Users/accusys/momentry_core_0.1
cargo build --release
# 編譯後重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## API 端點
| 端點 | 方法 | 說明 |
|------|------|------|
| `/health` | GET | 健康檢查 |
| `/health/detailed` | GET | 詳細健康檢查 |
| `/api/v1/register` | POST | 註冊影片 |
| `/api/v1/search` | POST | 搜尋影片 |
| `/api/v1/n8n/search` | POST | n8n 格式搜尋 |
| `/api/v1/search/hybrid` | POST | 混合搜尋 |
| `/api/v1/lookup` | GET | 查詢 UUID |
| `/api/v1/videos` | GET | 列出所有影片 |
| `/api/v1/progress/:uuid` | GET | 查詢處理進度 |
---
## 版本資訊
- 版本: 0.1.0
- 安裝日期: 2026-03-23
- Rust 版本: 1.xx
- 文件更新: 2026-03-23
---
## 相關文件
- `docs/SERVICES.md` - 服務總覽
- `docs/API_REFERENCE.md` - API 參考
- `docs/INSTALL_POSTGRESQL.md` - PostgreSQL 安裝
- `docs/INSTALL_REDIS.md` - Redis 安裝
- `docs/INSTALL_QDRANT.md` - Qdrant 安裝
- `docs/PENDING_ISSUES.md` - 待解決問題

View File

@@ -1,5 +1,21 @@
# MongoDB 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 MongoDB Community Edition配置為本地部署支援遠端訪問。
@@ -11,8 +27,8 @@
| 項目 | 狀態 |
|------|------|
| MongoDB (mongodb-community) | ✅ 已安裝 v8.2.6 |
| 數據目錄 | 保留 (/Users/accusys/momentry/var) - 共用 |
| 日誌目錄 | 保留 (/Users/accusys/momentry/log) - 共用 |
| 數據目錄 | /opt/homebrew/var/mongodb |
| 日誌目錄 | /Users/accusys/momentry/log |
---
@@ -40,9 +56,9 @@ sudo launchctl list | grep mongo
### Step 2: 數據目錄 (已存在 - 共用)
數據目錄已存在,無需建立:
- 數據目錄: `/Users/accusys/momentry/var`
- 配置目錄: `/Users/accusys/momentry/etc/mongodb`
數據目錄使用 homebrew 預設位置:
- 數據目錄: `/opt/homebrew/var/mongodb`
- 配置目錄: `/opt/homebrew/etc/mongod.conf`
- 日誌目錄: `/Users/accusys/momentry/log`
**建立配置目錄和日誌文件**:
@@ -61,15 +77,19 @@ chown -R accusys:staff /Users/accusys/momentry
---
### Step 3: 啟動 MongoDB (後台執行)
### Step 3: 使用 LaunchAgent 啟動 (開機自動)
```bash
nohup /opt/homebrew/bin/mongod \
--dbpath /Users/accusys/momentry/var \
--logpath /Users/accusys/momentry/log/mongodb.log \
--port 27017 \
--bind_ip 0.0.0.0 \
> /Users/accusys/momentry/log/mongodb.log 2>&1 &
# 複製 plist 到 LaunchDaemons 目錄 (開機自動需要 root 權限)
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mongodb.plist \
/Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
# 驗證
launchctl list | grep mongodb
pgrep -a mongod
```
---
@@ -90,14 +110,20 @@ db.createUser({
---
### Step 5: 使用 plist 開機自動啟動
### Step 4: 驗證安裝
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mongodb.plist /Library/LaunchDaemons/
# 檢查進程
pgrep -a mongod
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
# 檢查端口
lsof -i :27017
# 測試連線
mongosh --eval "db.adminCommand('ping')"
# 檢查 LaunchAgent
launchctl list | grep mongodb
```
---
@@ -265,12 +291,11 @@ tail -20 /Users/accusys/momentry/log/mongodb.error.log
### 啟動/停止
```bash
# 停止
pkill mongod
#
kill <PID>
# 使用 LaunchAgent (開機自動 - LaunchDaemons 目錄)
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist # 啟動
sudo launchctl unload /Library/LaunchDaemons/com.momentry.mongodb.plist # 停止
# 啟動 (後台)
# 手動啟動 (僅除錯用)
nohup /opt/homebrew/bin/mongod \
--dbpath /Users/accusys/momentry/var \
--logpath /Users/accusys/momentry/log/mongodb.log \
@@ -278,8 +303,8 @@ nohup /opt/homebrew/bin/mongod \
--bind_ip 0.0.0.0 \
> /Users/accusys/momentry/log/mongodb.log 2>&1 &
# 使用 plist (開機自動啟動)
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
# 強制停止
pkill mongod
```
---

View File

@@ -1,5 +1,21 @@
# n8n 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 n8n 工作流自動化平台,配置為本地部署,使用 Queue 模式。
@@ -10,13 +26,22 @@
| 項目 | 狀態 |
|------|------|
| n8n | ✅ 已安裝 v2.3.5 |
| n8n | ✅ 已安裝 v2.12.3 |
| 數據目錄 | /Users/accusys/momentry/var/n8n/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Main Plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist |
| Worker Plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist |
| 數據庫 | PostgreSQL (n8n) |
| 隊列 | Redis |
| Launchd 狀態 | ✅ Main + Worker 已註冊 |
| RunAtLoad | ✅ 已設定 |
| KeepAlive | ✅ 已設定 |
### 重要更新 (2026-03-24)
1. **n8n Main + Worker**: 兩個服務都使用自定義 plist
2. **Runner 禁用**: 為避免端口衝突Main 服務設定 `N8N_RUNNERS_ENABLED=false`
3. **Worker 端口**: Worker 使用 5681, 5682, 5690, 5691 端口
---
@@ -32,7 +57,7 @@ brew install n8n
**驗證**:
```bash
n8n --version
# 2.3.5
# 2.12.3
```
---
@@ -217,9 +242,9 @@ ps aux | grep "n8n.*worker" | grep -v grep && echo " ✗ 仍在運行" || echo
echo "3. Port 8085:"
lsof -i :8085 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. Port 5690-5691
echo "4. Port 5690-5691:"
lsof -i :5690 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. Port 5679 (Worker)
echo "4. Port 5679 (Worker):"
lsof -i :5679 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 4. n8n 命令
echo "5. n8n 命令:"
@@ -287,8 +312,7 @@ ps aux | grep n8n | grep -v grep
# 2. 檢查 Port
lsof -i :5678
lsof -i :5690
lsof -i :5691
lsof -i :5679
# 3. 測試連線
curl http://localhost:5678/
@@ -325,8 +349,7 @@ sudo launchctl list | grep n8n
| 服務 | Port |
|------|------|
| Main | 5678 |
| Worker Broker | 5690 |
| Worker Health Check | 5691 |
| Task Broker (Worker 連接) | 5679 |
---
@@ -459,7 +482,7 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
- 版本: 2.3.5
- Main Port: 5678
- Worker Ports: 5690-5691
- Task Broker (Worker): 5679
- 數據目錄: /Users/accusys/momentry/var/n8n/
- 日誌目錄: /Users/accusys/momentry/log/
- 數據庫: PostgreSQL n8n

View File

@@ -1,5 +1,21 @@
# Ollama 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 Ollama配置為本地部署用於運行大型語言模型 (LLM)。
@@ -12,7 +28,7 @@
|------|------|
| Ollama | ✅ 已安裝 v0.13.5 |
| Port | 11434 |
| Models 目錄 | /Users/accusys/.ollama/models/ |
| Models 目錄 | /Users/accusys/momentry/var/ollama/models |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.ollama.plist |

View File

@@ -1,5 +1,21 @@
# PHP 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 PHP 及 PHP-FPM配置為本地部署。

View File

@@ -1,5 +1,21 @@
# PostgreSQL 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 PostgreSQL配置為本地部署支援遠端訪問。
@@ -11,9 +27,18 @@
| 項目 | 狀態 |
|------|------|
| PostgreSQL | ✅ 已安裝 v18.1 |
| 數據目錄 | /Users/accusys/momentry/var/postgresql/ |
| 數據目錄 | /Users/accusys/momentry/var/postgresql |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.postgresql.plist |
| Launchd 狀態 | ✅ 已註冊 |
| RunAtLoad | ✅ 已設定 |
| KeepAlive | ✅ 已設定 |
### 重要更新 (2026-03-24)
1. **資料目錄已變更**: 從 `/opt/homebrew/var/postgresql@18` 遷移到 `/Users/accusys/momentry/var/postgresql`
2. **統一管理**: 所有 Momentry 服務現在都使用 `/Library/LaunchDaemons/` 下的自定義 plist
3. **避免衝突**: 刪除了 homebrew plist避免 reboot 後使用舊資料目錄
---

View File

@@ -1,5 +1,21 @@
# Qdrant 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 Qdrant Vector Database配置為本地部署支援遠端訪問。

View File

@@ -1,5 +1,23 @@
# Redis 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 更新 rust redis crate 版本至 0.32.7 | OpenCode | - |
| V1.2 | 2026-03-21 | 添加 Redis 用戶配置說明 | OpenCode | - |
---
## 概述
本文檔說明如何在 macOS 上安裝 Redis配置為本地部署支援遠端訪問。
@@ -11,7 +29,7 @@
| 項目 | 狀態 |
|------|------|
| Redis | ✅ 已安裝 v8.4.0 |
| 數據目錄 | /Users/accusys/momentry/var/redis/ |
| 數據目錄 | /opt/homebrew/var/db/redis/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.redis.plist |
@@ -360,8 +378,104 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
## 版本資訊
- 版本: 8.4.0
- Port: 6379
- Password: accusys
- 數據目錄: /Users/accusys/momentry/var/redis/
- 日誌目錄: /Users/accusys/momentry/log/
| 項目 | 值 |
|------|-----|
| Redis Server | 8.4.0 |
| Rust redis crate | 0.32.7 |
| Port | 6379 |
| Password | accusys |
| 數據目錄 | /Users/accusys/momentry/var/redis/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
---
## Rust redis crate 版本
Cargo.toml 中的 redis 依賴:
```toml
redis = { version = "0.32", features = ["tokio-comp"] }
```
### 版本歷史
| 版本 | 日期 | 變更 |
|------|------|-------|
| 0.25.4 | - | 原始版本(有未來相容性警告) |
| 0.32.7 | 2026-03-21 | **升級** - 修復 Rust 2024 never type 回退問題 |
### 升級說明
升級到 0.32.x 的優點:
- 修復 Rust 2024 edition 未來相容性問題
- API 完全向後相容
- 無需修改現有程式碼
---
## Redis 用戶配置說明
### 當前狀態
| 項目 | 狀態 |
|------|------|
| 用戶類型 | 僅有 `default` 用戶 |
| 自訂用戶 | ❌ 未配置 |
| ACL 持久化 | ❌ 未配置 |
### Redis ACL 狀態
```bash
# 查看 ACL
redis-cli -a accusys ACL LIST
# 輸出:
# user default on sanitize-payload #hash ~* &* +@all
```
### 連線格式說明
| 格式 | 狀態 | 說明 |
|------|------|------|
| `redis://:accusys@localhost:6379` | ✅ 正確 | 使用默認用戶 + 密碼 |
| `redis://accusys:accusys@localhost:6379` | ❌ 失敗 | 用戶 `accusys` 不存在 |
### 為何用戶名不可用
1. **Redis 啟動方式**:使用 `--requirepass` 參數,僅設定默認用戶密碼
2. **無 ACL 配置文件**:未指定 `--aclfile` 參數
3. **動態建立用戶**:手動建立的用戶不會持久化(重啟後消失)
### 解決方案
#### 方案 A使用默認用戶現行
```env
REDIS_URL=redis://:accusys@localhost:6379
```
**適用於**:單一應用、簡單部署
#### 方案 B建立 ACL 配置文件
```bash
# 1. 建立 ACL 文件
cat > /Users/accusys/momentry/etc/redis/users.acl << 'EOF'
user default on sanitize-payload ~* &* +@all >accusys
user accusys on sanitize-payload ~* &* +@all >accusys
EOF
# 2. 修改 plist (添加 --aclfile 參數)
# --aclfile /Users/accusys/momentry/etc/redis/users.acl
# 3. 重啟 Redis
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
**適用於**:多應用、需要用戶隔離
### 參考
- 問題追蹤:`docs/PENDING_ISSUES.md` 問題 #5
- 測試結果2026-03-21 Redis 認證測試

View File

@@ -1,5 +1,21 @@
# RustDesk 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明如何在 macOS 上安裝 RustDesk 遠端桌面服務,配置為本地部署。

View File

@@ -1,5 +1,32 @@
# SFTPGo 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-22 | 添加備份還原、API管理、Hook配置 | opencode | OpenCode |
---
## 更新紀錄
- **2026-03-22**:
- 成功使用統一備份系統還原 SFTPGo 到 20260321_101928 時間點
- 重置 admin 密碼為 `Test3200Test3200`
- 修復 demo 用戶權限格式 (從 `["*"]` 改為 `{" /":["*"]}`)
- 通过 API 创建并配置 `demo` 用户和 `demo`
- 配置 SFTPGo Hook 實現自動化檔案註冊
- 確認 API 認證方式為 Basic Auth (GET `/api/v2/token`)
## 概述
本文檔說明如何在 macOS 上安裝 SFTPGo配置為本地部署用於 SFTP/FTP/WebDAV 檔案傳輸服務。
@@ -15,6 +42,76 @@
| 配置目錄 | /Users/accusys/momentry/etc/sftpgo/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.sftpgo.plist |
| API 狀態 | ✅ 已啟用 (`enable_rest_api: true`) |
| Hook 狀態 | ✅ 已配置 (執行於檔案上傳時) |
---
## 備份與還原機制
SFTPGo 使用 Momentry 統一備份系統進行完整的配置與數據庫備份。
### 備份內容
```bash
# 備份目錄
/Users/accusys/momentry/backup/daily/sftpgo/
# 備份文件範例
sftpgo_cfg_20260321_101928.tar.gz # 配置文件
sftpgo_db_20260321_101928.sql.gz # PostgreSQL 數據庫
sftpgo_20260321_101928.sha256 # 校驗和
```
### 列出可用備份時間點
```bash
/Users/accusys/momentry/scripts/backup_all.sh list | grep sftpgo
```
### 執行備份
```bash
# 備份 SFTPGo (配置 + 數據庫)
/Users/accusys/momentry/scripts/backup_all.sh sftpgo full
# 手動備份單一服務
/Users/accusys/momentry/scripts/backup_all.sh sftpgo cfg
```
### 執行還原
```bash
# 1. 備份當前狀態 (安全措施)
/Users/accusys/momentry/scripts/backup_all.sh sftpgo full
# 2. 還原到指定時間點
/Users/accusys/momentry/scripts/backup_all.sh restore sftpgo 20260321_101928
# 3. 驗證還原結果
curl http://localhost:8080/api/v2/version
psql -U sftpgo -d sftpgo -c "SELECT username, status FROM admins;"
```
### 還原流程说明
`backup_all.sh``restore_sftpgo()` 函数会执行以下步骤 (第 517-546 行)
1. **停止 SFTPGo** - 使用 `pkill -f sftpgo`
2. **恢复配置文件** - 从 `sftpgo_cfg_*.tar.gz` 恢复到 `/Users/accusys/momentry/etc/sftpgo/`
3. **恢复 PostgreSQL 数据库**:
- 删除现有数据库 (如存在)
- 创建新数据库并设置所有者
-`sftpgo_db_*.sql.gz` 导入数据
4. **重启 SFTPGo** - 手动启动服务
### 備份狀態檢查
```bash
/Users/accusys/momentry/scripts/backup_all.sh status
```
此命令會顯示所有服務的最新備份狀態與大小。
---
@@ -111,9 +208,20 @@ chown -R accusys:staff /Users/accusys/workspace/sftpgo
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.sftpgo.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
#### Plist 環境變量說明
plist 包含以下環境變量用於自動創建 admin 用戶:
| 環境變量 | 值 | 說明 |
|----------|------|------|
| `SFTPGO_DEFAULT_ADMIN_USERNAME` | `admin` | 默認管理員用戶名 |
| `SFTPGO_DEFAULT_ADMIN_PASSWORD` | `Test3200Test3200` | 默認管理員密碼 |
**注意**: 這些變量僅在首次啟動時用於創建 admin 用戶。如果 admin 已存在,則不會覆蓋。
---
## 監控配置
@@ -134,6 +242,20 @@ service:
enabled: true
```
### API 監控端點
| 端點 | 說明 | 認證 |
|------|------|------|
| `/api/v2/info` | 伺服器資訊 | ❌ 不需要 |
| `/api/v2/healthz` | 健康檢查 | ❌ 不需要 |
| `/api/v2/version` | 版本資訊 | ❌ 不需要 |
| `/api/v2/token` | 獲取 Token | ✅ Basic Auth |
| `/api/v2/admins` | 管理員列表 | ✅ Bearer Token |
| `/api/v2/users` | 用戶列表 | ✅ Bearer Token |
| `/api/v2/groups` | 組列表 | ✅ Bearer Token |
---
---
## 卸載步驟
@@ -275,6 +397,581 @@ SFTPGO_DATA_DIR=/Users/accusys/workspace/sftpgo
---
## 管理員帳戶修復
### 透過 Web 管理面板重置密碼 (推薦)
1. 前往 **http://localhost:8080/web/admin/login**
2. 點擊 **Forgot password?**
3. 輸入 **Installation Code**: `Test3200Test3200`
4.`admin` 設定新密碼
### 透過 SQL 直接重建管理員帳戶
如果 Web 面板無法使用,可直接操作 PostgreSQL 資料庫:
#### 連線到資料庫
```bash
psql -U accusys -d sftpgo
```
#### 插入管理員帳戶
```sql
INSERT INTO admins (
username,
description,
password,
email,
status,
permissions,
filters,
additional_info,
last_login,
role_id,
created_at,
updated_at
) VALUES (
'admin',
'',
'$2a$10$3Boql8AHoxPXWWmgmJiN5.b8s1Z65gNp1yqKQvZ5SOEv7j8NRA58.',
'',
1,
'["*"]',
'{"require_two_factor":false,"totp_config":{"secret":{}},"preferences":{}}',
'',
0,
NULL,
EXTRACT(EPOCH FROM NOW())::bigint * 1000,
EXTRACT(EPOCH FROM NOW())::bigint * 1000
);
```
**預設密碼**: `Test3200Test3200`
#### 使用 bcrypt 生成密碼哈希
```bash
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test3200Test3200', bcrypt.gensalt(rounds=10)).decode())"
```
### 使用 CLI 重置密碼
```bash
# 停止 SFTPGo (如果正在运行)
pkill -f sftpgo
# 重置密碼 (互動式)
sftpgo resetpwd --admin admin --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 重新啟動 SFTPGo
/opt/homebrew/opt/sftpgo/bin/sftpgo serve --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json &
```
### 使用 Expect 自動化密碼重置
如果需要自動化重置,可以創建 expect 腳本:
```bash
# resetpwd.exp
#!/usr/bin/expect -f
set timeout 10
set admin_user [lindex $argv 0]
set new_password [lindex $argv 1]
spawn sftpgo resetpwd --admin $admin_user --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json
expect "Enter Password:"
send "$new_password\r"
expect "Enter Password:"
send "$new_password\r"
expect eof
```
執行:
```bash
chmod +x resetpwd.exp
expect resetpwd.exp admin Test3200Test3200
```
---
### 透過 CLI 重置密碼
```bash
# 停止 SFTPGo
kill $(pgrep -f "sftpgo serve")
# 重置密碼 (互動式)
sftpgo resetpwd --admin admin --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 重新啟動 SFTPGo
sftpgo serve --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json &
```
### 驗證管理員帳戶
```sql
-- 檢查 admin 是否存在
SELECT id, username, status, last_login FROM admins;
-- 檢查使用者是否存在 (SFTP/WebDAV 登入)
SELECT id, username, status, home_dir FROM users;
```
---
## API 管理用戶與組
SFTPGo 提供 RESTful API 用於管理用戶和組,支援自動化運維。
### API 認證方式
1. **獲取 Access Token** (使用 Basic Auth):
```bash
TOKEN=$(curl -s -X GET http://localhost:8080/api/v2/token \
-u "admin:Test3200Test3200" | jq -r '.access_token')
```
2. **使用 Token 調用 API**:
```bash
curl -X GET http://localhost:8080/api/v2/admins \
-H "Authorization: Bearer $TOKEN"
```
**注意**: `/api/v2/token` 端點使用 `GET` 方法,而非 `POST`
### 列出所有用戶
```bash
TOKEN=$(curl -s -X GET http://localhost:8080/api/v2/token -u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X GET "http://localhost:8080/api/v2/users?limit=100" \
-H "Authorization: Bearer $TOKEN" | jq -r '.[] | "\(.username) status=\(.status) home=\(.home_dir)"'
```
### 創建用戶
```bash
curl -s -X POST http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "demo",
"password": "demopassword123",
"email": "demo@momentry.local",
"status": 1,
"home_dir": "/Users/accusys/sftpgo_test/demo",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}'
```
**權限格式注意**: 必須為 map[string][]string 格式,例如:
```json
{
"/": ["*"],
"/upload": ["read", "write"]
}
```
### 修復用戶權限格式
如果權限存儲為數組 `["*"]` 而非 mapAPI 會返回錯誤:
```
unable to deserialize permissions for user "demo": json: cannot unmarshal array into Go value of type map[string][]string
```
修復方法:
```bash
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c \
"UPDATE users SET permissions = '{\" /\":[\"*\"]}'::jsonb WHERE username='demo';"
```
### 創建組
```bash
curl -s -X POST http://localhost:8080/api/v2/groups \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "demo",
"description": "Demo group for SFTP uploads"
}'
```
### 將用戶加入組
查看 User Schema 中的 `groups` 欄位為 `GroupMapping` 數組:
```json
{
"groups": [
{
"name": "demo",
"type": 1 // 1=主要組, 2=次要組, 3=僅成員身份
}
]
}
```
更新用戶組關聯:
```bash
curl -s -X PUT http://localhost:8080/api/v2/users/demo \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"groups": [
{
"name": "demo",
"type": 1
}
],
"permissions": {
"/": ["*"]
}
}'
```
**重要**: 更新用戶時必須提供完整的必要欄位 (如 `permissions`),否則會驗證失敗。
### API 端點對照表
| 功能 | 端點 | 方法 |
|------|------|------|
| 獲取 Token | `/api/v2/token` | GET (Basic Auth) |
| 列出管理員 | `/api/v2/admins` | GET |
| 列出用戶 | `/api/v2/users` | GET |
| 創建用戶 | `/api/v2/users` | POST |
| 獲取用戶 | `/api/v2/users/{username}` | GET |
| 更新用戶 | `/api/v2/users/{username}` | PUT |
| 刪除用戶 | `/api/v2/users/{username}` | DELETE |
| 列出組 | `/api/v2/groups` | GET |
| 創建組 | `/api/v2/groups` | POST |
| 獲取組 | `/api/v2/groups/{name}` | GET |
| 更新組 | `/api/v2/groups/{name}` | PUT |
| 刪除組 | `/api/v2/groups/{name}` | DELETE |
完整的 API 文件請參考: http://localhost:8080/openapi/
---
## Hook 配置
SFTPGo Hook 用於在檔案操作時觸發外部腳本,實現自動化處理。
### 配置文件位置
`/Users/accusys/momentry/etc/sftpgo/sftpgo.json`
### 通用 Actions 配置
`common.actions` 中配置:
```json
{
"common": {
"actions": {
"execute_on": ["add"], // 觸發時機: 檔案上傳完成後
"execute_sync": [], // 同步執行 ([])
"hook": "/path/to/hook.sh" // Hook 腳本路徑
}
}
}
```
### Hook 腳本範例
位置: `/Users/accusys/sftpgo_test/register_hook.sh`
```bash
#!/bin/bash
# SFTPGo Hook - 檔案上傳後自動註冊到 Momentry Core
# SFTPGo 傳遞的環境變數
# ${SFTPGO_ACTION} - 操作類型 (add, delete, etc.)
# ${SFTPGO_USERNAME} - 用戶名
# ${SFTPGO_FILEPATH} - 檔案路徑 (相對首頁目錄)
# ${SFTPGO_FILESIZE} - 檔案大小
# ${SFTPGO_TIMESTAMP} - 操作時間戳
# 絕對路徑轉換
UPLOAD_DIR="/Users/accusys/sftpgo_test/demo"
RELATIVE_PATH="${SFTPGO_FILEPATH#./}"
ABS_PATH="${UPLOAD_DIR}/${RELATIVE_PATH}"
# 日誌記錄
LOG_FILE="/Users/accusys/sftpgo_test/hook.log"
echo "$(date '+%Y-%m-%d %H:%M:%S') - User: ${SFTPGO_USERNAME}, File: ${ABS_PATH}, Size: ${SFTPGO_FILESIZE}" >> "$LOG_FILE"
# 調用 Momentry Core 註冊 API
API_URL="http://localhost:8080/api/v1/register"
RESPONSE=$(curl -s -X POST "$API_URL" \
-H "Content-Type: multipart/form-data" \
-F "file=@${ABS_PATH}" \
--connect-timeout 10 --max-time 300)
echo "$(date '+%Y-%m-%d %H:%M:%S') - API Response: ${RESPONSE}" >> "$LOG_FILE"
```
### Hook 執行權限
```bash
chmod +x /Users/accusys/sftpgo_test/register_hook.sh
```
### Hook 執行時間點
`execute_on` 支援以下值:
| 值 | 觸發時機 |
|-----|----------|
| `add` | 檔案上傳完成後 |
| `delete` | 檔案刪除後 |
| `upload_complete` | 上傳完成 (multipart) |
| `download` | 檔案下載時 |
| `rename` | 檔案重命名後 |
| `mkdir` | 建立目錄後 |
| `rmdir` | 刪除目錄後 |
### Hook 執行模式
- **同步執行** (`execute_sync`): Hook 腳本在 SFTPGo 操作返回客戶端前執行
- **非同步執行** (預設): Hook 腳本在后台執行,不阻塞客戶端
### 重新載入配置
修改 Hook 配置後,需重新載入 SFTPGo:
```bash
# 如果是 plist 啟動的服務
sudo launchctl kickstart -k system/local.sftpgo
# 或手動重啟
pkill -f sftpgo
sftpgo serve --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json &
```
### Hook 故障排除
1. **檢查 Hook 日誌**:
```bash
tail -f /Users/accusys/sftpgo_test/hook.log
```
2. **手動測試 Hook 腳本**:
```bash
export SFTPGO_USERNAME=demo
export SFTPGO_FILEPATH="./test.txt"
export SFTPGO_FILESIZE=1024
export SFTPGO_ACTION=add
/Users/accusys/sftpgo_test/register_hook.sh
```
3. **SFTPGo 錯誤日誌**:
```bash
tail -20 /Users/accusys/momentry/log/sftpgo.error.log
```
---
| 資料表 | 用途 | 登入 URL |
|--------|------|----------|
| `admins` | 管理員用戶 | `/web/admin/login` |
| `users` | 檔案傳輸用戶 | `/web/client/login` |
### Demo 用戶與組設置
#### 建立 Demo 目錄
```bash
mkdir -p /Users/accusys/sftpgo_test/demo
```
#### 創建 Demo 組
```bash
TOKEN=$(curl -s -X GET http://localhost:8080/api/v2/token -u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X POST http://localhost:8080/api/v2/groups \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "demo",
"description": "Demo group for SFTP uploads"
}'
```
#### 創建 Demo 用戶並加入組
如果 `demo` 用戶不存在:
```bash
curl -s -X POST http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "demo",
"password": "demopassword123",
"email": "demo@momentry.local",
"status": 1,
"home_dir": "/Users/accusys/sftpgo_test/demo",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}'
```
如果用戶已存在但權限格式錯誤,需修復:
```bash
psql -h 127.0.0.1 -p 5432 -U sftpgo -d sftpgo -c \
"UPDATE users SET permissions = '{\" /\":[\"*\"]}'::jsonb WHERE username='demo';"
```
將用戶加入 `demo` 組 (主要組):
```bash
curl -s -X PUT http://localhost:8080/api/v2/users/demo \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"groups": [
{
"name": "demo",
"type": 1
}
],
"permissions": {
"/": ["*"]
}
}'
```
#### 驗證設置
```bash
# 檢查用戶
curl -s -X GET "http://localhost:8080/api/v2/users/demo" \
-H "Authorization: Bearer $TOKEN" | jq .
# 檢查組
curl -s -X GET "http://localhost:8080/api/v2/groups/demo" \
-H "Authorization: Bearer $TOKEN" | jq .
```
#### SFTP 連接測試
```bash
# 使用 SFTP 連接
sshpass -p "demopassword123" sftp -P 2022 demo@localhost
# 上傳測試文件
sftp> put test.txt
```
#### 預期行為
1. 檔案上傳完成後SFTPGo 觸發 Hook
2. Hook 腳本執行並記錄到 `/Users/accusys/sftpgo_test/hook.log`
3. Hook 調用 Momentry Core API 完成註冊
4. 查看注册狀態: `tail -f /Users/accusys/sftpgo_test/hook.log`
---
## 常見問題
#### "無效的憑證" 即使密碼正確
- PostgreSQL 中的密碼哈希可能不符合 SFTPGo 預期格式
- 使用 Web 面板的 **Forgot password** 功能而非直接 SQL 更新
#### CSRF Token 錯誤
- 清除瀏覽器中 `localhost:8080` 的 cookies
- 使用無痕/私密瀏覽視窗
---
## 端到端測試流程
### 1. 準備工作
```bash
# 確認 SFTPGo 運行中
curl http://localhost:8080/healthz
# 確認 Momentry Core API 運行中
curl http://localhost:8080/api/v1/health
```
### 2. SFTP 上傳測試
```bash
# 連接到 SFTPGo
sshpass -p "demopassword123" sftp -P 2022 demo@localhost
# 上傳測試文件
sftp> put /path/to/video.mp4
```
### 3. 驗證 Hook 執行
```bash
# 查看 Hook 日誌
tail -f /Users/accusys/sftpgo_test/hook.log
# 預期看到類似輸出:
# 2026-03-22 01:30:00 - User: demo, File: /Users/accusys/sftpgo_test/demo/video.mp4, Size: 10485760
# 2026-03-22 01:30:05 - API Response: {"uuid":"abc123","status":"registered"}
```
### 4. 檢查 Momentry Core 註冊狀態
```bash
# 使用 Momentry CLI 檢查
/Users/accusys/momentry/target/debug/momentry list --api-key <your-api-key>
# 或直接查詢資料庫
psql -U accusys -d momentry -c "SELECT uuid, filename, status FROM videos WHERE filename='video.mp4';"
```
### 5. 檢查處理狀態
```bash
# 查看處理佇列
curl http://localhost:8080/api/v1/queue
# 查看 searchable 狀態
curl "http://localhost:8080/api/v1/searchable?filename=video.mp4&user=demo"
```
### 6. 故障排除檢查清單
```bash
# 1. SFTPGo 狀態
ps aux | grep sftpgo
curl http://localhost:8080/healthz
# 2. Hook 腳本權限
ls -l /Users/accusys/sftpgo_test/register_hook.sh
# 3. Hook 日誌
tail -20 /Users/accusys/sftpgo_test/hook.log
# 4. Momentry Core 日誌
tail -20 /Users/accusys/momentry/log/momentry.log
# 5. SFTPGo 日誌
tail -20 /Users/accusys/momentry/log/sftpgo.log
# 6. PostgreSQL 連接
psql -U sftpgo -d sftpgo -c "SELECT 1;"
# 7. 端口可用性
lsof -i :8080 # SFTPGo HTTP
lsof -i :2022 # SFTP
lsof -i :5678 # Momentry Core (Primary)
```
---
## 故障排除
### SFTPGo 無法啟動
@@ -327,7 +1024,11 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
| 錯誤日誌 | `/Users/accusys/momentry/log/sftpgo.error.log` | 錯誤日誌 |
| 工作目錄 | `/Users/accusys/workspace/sftpgo/` | 上傳檔案目錄 |
| plist | `/Library/LaunchDaemons/com.momentry.sftpgo.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/sftpgo_backup/sftpgo.json` | 配置備份 |
| 備份 | `/Users/accusys/momentry/backup/daily/sftpgo/` | 配置與數據庫備份 |
| 備份配置 | `/Users/accusys/momentry/var/sftpgo_backup/` | 實時配置備份 (含 sftpgo.json, sftpgo.db) |
| Hook 腳本 | `/Users/accusys/sftpgo_test/register_hook.sh` | 自動註冊腳本 |
| Hook 日誌 | `/Users/accusys/sftpgo_test/hook.log` | Hook 執行記錄 |
| Demo 目錄 | `/Users/accusys/sftpgo_test/demo` | Demo 用戶首頁目錄 |
---

332
docs/INSTALL_WORDPRESS.md Normal file
View File

@@ -0,0 +1,332 @@
# WordPress 安裝指南 (Portal)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode / big-pickle |
---
## 1. 概述
本文檔說明 Momentry Portal 的 WordPress 安裝配置,作為系統入口整合 n8n 自動化與 sftpgo 檔案服務。
---
## 2. 當前狀態
| 項目 | 狀態 |
|------|------|
| WordPress 版本 | 6.x |
| URL | https://wp.momentry.ddns.net |
| 安裝路徑 | `/Users/accusys/wordpress/web` |
| 資料庫 | wordpress (MariaDB) |
| 資料庫用戶 | wp_user |
---
## 3. 目錄結構
```
/Users/accusys/wordpress/
├── web/ # WordPress 主目錄
│ ├── wp-admin/ # WordPress 管理面板
│ ├── wp-content/ # 內容目錄
│ │ ├── ai1wm-backups/ # 備份檔案 (*.wpress)
│ │ ├── languages/ # 語言檔案
│ │ ├── plugins/ # 插件目錄
│ │ ├── themes/ # 主題目錄
│ │ ├── uploads/ # 上傳檔案
│ │ └── cache/ # 快取目錄
│ ├── wp-includes/ # WordPress 核心
│ └── wp-config.php # 配置文件
├── docker-compose.yml # Docker 配置
└── wordpress_backup.sql # 資料庫備份
```
### 空間使用
| 目錄 | 大小 | 說明 |
|------|------|------|
| `ai1wm-backups/` | ~250MB | 完整備份 (保留 2 個) |
| `plugins/` | 80MB | 插件 |
| `themes/` | 14MB | 主題 |
| `uploads/` | 12MB | 上傳檔案 |
---
## 4. 程式碼位置
自訂程式碼存放位置:
| 類型 | 路徑 |
|------|------|
| 主題 | `/Users/accusys/wordpress/web/wp-content/themes/` |
| 插件 | `/Users/accusys/wordpress/web/wp-content/plugins/` |
| 必須插件 | `/Users/accusys/wordpress/web/wp-content/mu-plugins/` |
---
## 5. 插件清單
| 插件 | 用途 | 說明 |
|------|------|------|
| elementor | 頁面建構 | 視覺化頁面編輯器 |
| all-in-one-wp-migration | 網站遷移/備份 | 完整網站備份工具 |
| akismet | 垃圾留言過濾 | 保護留言區域 |
| code-snippets | 自訂程式碼 | 無需修改主題即可添加 PHP |
### Elementor 版本
- 版本: 最新穩定版
- 用途: 頁面建構(開發階段)
### 未來計畫
- Phase 2: OpenCode 重構
- 目標: 交付無 Elementor 依賴版本
---
## 6. 主題清單
| 主題 | 說明 |
|------|------|
| twentytwentyfive | 目前使用主題 |
| twentytwentyfour | 備用 |
| twentytwentythree | 備用 |
---
## 7. 整合計畫
### 7.1 n8n 整合
n8n 作為自動化引擎WordPress 頁面透過 REST API 或 Webhook 與 n8n 通訊。
| 整合方式 | 說明 |
|----------|------|
| REST API | WordPress 呼叫 n8n API |
| Webhook | n8n 觸發 WordPress 動作 |
### 7.2 sftpgo 整合
sftpgo 作為檔案服務WordPress 頁面提供檔案上傳/下載功能。
| 整合方式 | 說明 |
|----------|------|
| WebDAV | 透過 WebDAV API 操作檔案 |
| REST API | 透過 sftpgo API 操作檔案 |
---
## 8. 管理命令
### 8.1 清理 ai1wm 備份
```bash
# 查看現有備份
ls -lt /Users/accusys/wordpress/web/wp-content/ai1wm-backups/*.wpress
# 保留最近 2 個,刪除舊的
ls -t /Users/accusys/wordpress/web/wp-content/ai1wm-backups/*.wpress | tail -n +3 | xargs rm
# 驗證結果
du -sh /Users/accusys/wordpress/web/wp-content/ai1wm-backups/
```
### 8.2 清理 WordPress 快取
```bash
# 刪除 Object Cache
wp cache flush
# 刪除 Elementor 快取
wp elementor flush_css
# 刪除全部快取
wp cache flush && wp elementor flush_css
```
### 8.3 資料庫操作
```bash
# 匯出資料庫
mysqldump -u wp_user -p wordpress > wordpress_backup.sql
# 匯入資料庫
mysql -u wp_user -p wordpress < wordpress_backup.sql
```
### 8.4 權限檢查
```bash
# 檢查目錄權限
ls -la /Users/accusys/wordpress/web/wp-content/
# 確認 wp-content 可寫入
chown -R _www:_www /Users/accusys/wordpress/web/wp-content/
chmod -R 755 /Users/accusys/wordpress/web/wp-content/
```
---
## 9. 故障排除
### 9.1 常見問題
| 問題 | 解決方案 |
|------|----------|
| 頁面載入緩慢 | 清理 Elementor/Object Cache |
| 上傳檔案失敗 | 檢查 wp-content/uploads 權限 |
| 502/504 錯誤 | 重啟 PHP-FPM |
| 資料庫連線失敗 | 檢查 wp-config.php 設定 |
### 9.2 診斷命令
```bash
# 檢查 PHP-FPM 狀態
lsof -i :9000
# 檢查 MySQL/MariaDB 狀態
lsof -i :3306
# 檢查 Apache/Nginx 狀態
lsof -i :80
# 查看 WordPress 錯誤日誌
tail -100 /Users/accusys/momentry/log/php-fpm.log
```
---
## 10. 開發協作
### 10.1 與 marcom 團隊協作
| 角色 | 負責 |
|------|------|
| marcom 團隊 | Figma 設計 / Elementor 建構 |
| OpenCode | 程式碼實作 / 重構 |
### 10.2 開發流程
```
Phase 1: marcom 建構 (現在)
└── Elementor 頁面建構
Phase 2: 交付審視 (TBD)
└── 功能確認 / 重構評估
Phase 3: OpenCode 重構 (討論後)
└── 純程式碼實作
└── 交付客戶 (無 Elementor 依賴)
```
---
## 11. PHP LSP 開發環境
### 11.1 軟體需求
| 軟體 | 版本 | 安裝方式 |
|------|------|----------|
| PHP | 8.0+ | 已安裝 (8.5.2) |
| Composer | 2.0+ | `brew install composer` |
| phpactor | Latest | PHAR 安裝 |
### 11.2 安裝步驟
#### 1. 安裝 Composer
```bash
brew install composer
```
#### 2. 安裝 phpactor
```bash
curl -sSL https://github.com/phpactor/phpactor/releases/latest/download/phpactor.phar -o ~/bin/phpactor
chmod +x ~/bin/phpactor
export PATH="$HOME/bin:$PATH"
```
#### 3. 安裝 WordPress Stubs
```bash
cd /Users/accusys/wordpress/web
composer require --dev php-stubs/wordpress-stubs
```
#### 4. 設定 phpactor
```bash
# 設定檔位置
mkdir -p ~/.config/phpactor
# 設定內容
cat > ~/.config/phpactor/phpactor.json << 'EOF'
{
"core.min_memory_limit": 1610612736,
"worse_reflection.additive_stubs": [
"/Users/accusys/wordpress/web/vendor/php-stubs/wordpress-stubs/wordpress-stubs.php"
]
}
EOF
```
#### 5. 建立索引
```bash
cd /Users/accusys/wordpress/web
~/bin/phpactor index:build --reset
```
### 11.3 OpenCode 使用方式
```bash
# 確認安裝
~/bin/phpactor --version
# 查詢類別
~/bin/phpactor class:search "WP_User"
# 查看類別資訊
~/bin/phpactor index:query WP_User
# 導航到定義
~/bin/phpactor navigate /path/to/file.php
# 查詢參照
~/bin/phpactor references /path/to/file.php
```
### 11.4 常用指令
| 指令 | 用途 |
|------|------|
| `phpactor class:search` | 搜尋類別 |
| `phpactor index:query` | 查詢索引 |
| `phpactor index:build` | 建立索引 |
| `phpactor index:clean` | 清除索引 |
| `phpactor config:dump` | 顯示設定 |
---
## 12. 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| WordPress 主目錄 | `/Users/accusys/wordpress/web` | 網站根目錄 |
| 備份目錄 | `/Users/accusys/momentry/backup/wordpress/` | 每日備份 |
| 日誌目錄 | `/Users/accusys/momentry/log/` | PHP/Apache 日誌 |
| phpactor | `~/bin/phpactor` | PHP LSP 主程式 |
| phpactor 設定 | `~/.config/phpactor/phpactor.json` | LSP 設定檔 |
| WordPress Stubs | `/Users/accusys/wordpress/web/vendor/php-stubs/` | WordPress 函數定義 |

View File

@@ -1,5 +1,21 @@
# Momentry JSON 輸出檔案規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。
---

View File

@@ -0,0 +1,782 @@
# Momentry 系統自動化安裝計劃
> **計劃階段** - 僅供討論,尚未執行
> **建立時間**: 2026-03-23
> **目標**: Thunderbolt NVMe 外開機完整安裝
---
## 系統概述
### 當前環境
| 項目 | 內容 |
|------|------|
| **主控機** | Mac mini (M4, 16GB RAM) |
| **作業系統** | macOS 26.3.1 (Tahoe) |
| **儲存** | Thunderbolt NVMe (2TB) |
| **用途** | 開機碟 + 完整 Momentry 系統 |
### 目標環境
| 項目 | 內容 |
|------|------|
| **目標主機** | 其他 Mac (Intel 或 Apple Silicon) |
| **安裝方式** | Thunderbolt NVMe 外接開機 |
| **連接方式** | Thunderbolt 3/4 |
| **控制方式** | SSH 遠端管理 |
---
## 系統架構
### 服務列表
| 服務 | 版本 | 用途 | Port |
|------|------|------|------|
| **PostgreSQL** | 18.1 | 主資料庫、n8n 資料庫 | 5432 |
| **MongoDB** | 8.0 | 文件資料庫 | 27017 |
| **MariaDB** | 11.4 | WordPress 資料庫 | 3306 |
| **Redis** | 7.x | 快取、佇列 | 6379 |
| **Qdrant** | 1.7.x | 向量資料庫 | 6333 |
| **Ollama** | 0.13.5 | 本地 LLM | 11434 |
| **Caddy** | 2.x | 反向代理 | 80/443 |
| **Gitea** | 1.21 | Git 服務 | 3000 |
| **PHP-FPM** | 8.5 | WordPress | 9000 |
| **n8n** | 2.3.5 | 工作流程自動化 | 5678 |
| **RustDesk** | hbbs/hbbr | 遠端桌面 | 21115-21119 |
| **SFTPGo** | 2.x | SFTP 服務 | 2022 |
| **Momentry Core** | 0.1.0 | 影片處理核心 | 3002 |
| **Prometheus** | 3.9.1 | 監控 | 9090 |
### 目錄結構
```
/Volumes/Momentry/
├── System/
│ └── macOS/ # macOS 系統
├── Applications/
│ └── Homebrew/ # Homebrew 應用程式
├── momentry/
│ ├── var/ # 資料目錄
│ │ ├── postgresql/ # PostgreSQL 資料
│ │ ├── mongodb/ # MongoDB 資料
│ │ ├── mariadb/ # MariaDB 資料
│ │ ├── redis/ # Redis 資料
│ │ ├── qdrant/ # Qdrant 資料
│ │ ├── n8n/ # n8n 資料
│ │ ├── ollama/ # Ollama 模型
│ │ └── ...
│ ├── etc/ # 配置檔案
│ │ ├── Caddyfile
│ │ ├── gitea/
│ │ ├── php/
│ │ └── ...
│ ├── log/ # 日誌
│ ├── scripts/ # 管理腳本
│ └── backup/ # 備份
├── momentry_core/ # Rust 原始碼
└── momentry_dashboard/ # Web Dashboard
```
---
## 階段一:前置準備
### 1.1 收集目標主機資訊
```bash
# 需要收集的資訊
- Mac 型號 (Intel/Apple Silicon)
- macOS 版本
- Thunderbolt 版本 (3/4)
- 可用記憶體
- 目標磁碟代號 (diskX)
- 網路配置 (DHCP/固定 IP)
```
### 1.2 準備 Thunderbolt NVMe
```bash
# 檢查 Thunderbolt NVMe
diskutil list external
# 預期輸出:
# /dev/diskX (external, physical):
# NAME TYPE SIZE
# Thunderbolt NVMe ...
```
### 1.3 準備主控機腳本
```bash
# 主控機需要準備的腳本
~/momentry/setup/
├── 01_prepare_disk.sh
├── 02_install_macos.sh
├── 03_install_homebrew.sh
├── 04_install_dependencies.sh
├── 05_install_services.sh
├── 06_install_momentry.sh
├── 07_configure_network.sh
├── 08_start_services.sh
└── utils/
├── common.sh
├── backup.sh
└── monitor.sh
```
---
## 階段二Thunderbolt NVMe 準備
### 2.1 分割磁碟方案 A推薦
```bash
# 磁碟分割配置
diskutil partitionDisk /dev/diskX \
GPT \
"APFS System" APFS "Momentry System" 200G \
"APFS Data" APFS "Momentry Data" 1.8T
```
### 2.2 分割磁碟方案 B最小化
```bash
# 統一 APFS 容器
diskutil partitionDisk /dev/diskX \
GPT \
APFS "Momentry" 100%
```
---
## 階段三:安裝 macOS
### 3.1 建立 macOS 安裝碟
```bash
# 下載 macOS Sonoma (或最新版本)
softwareupdate --fetch-full-installer --full-installer-version 14.0
# 建立可開機安裝碟
sudo /Applications/Install\ macOS\ Sonoma.app/Contents/Resources/createinstallinstmedi \
--volume /Volumes/Momentry \
--nointeraction
```
### 3.2 安裝 macOS 到 Thunderbolt NVMe
**兩種方法:**
#### 方法 A: 復原模式安裝
1. 連接 Thunderbolt NVMe
2. 重啟目標主機按住Option鍵
3. 選擇 Thunderbolt NVMe 開機
4. 進入 Recovery Mode (Command+R)
5. 使用 Disk Utility 格式化目標磁碟
6. 安裝 macOS
#### 方法 B: ASR 複製(建議)
```bash
# 從主控機執行
# 將現有系統複製到目標磁碟
sudo asr restore \
--source /Volumes/Macintosh\ HD \
--target /Volumes/Momentry \
--erase --noprompt
```
### 3.3 設定 macOS
```bash
# 自動化設定腳本
./setup/scripts/03_install_homebrew.sh
```
**設定項目:**
- 電腦名稱:`momentry-<serial>`
- 使用者帳號:`momentry` (管理員)
- SSH 遠端登入:啟用
- 螢幕鎖定:關閉
- 節能設定:永不休眠
---
## 階段四:安裝 Homebrew
### 4.1 安裝 Homebrew
```bash
#!/bin/bash
# 04_install_homebrew.sh
# 檢查架構
ARCH=$(uname -m)
if [ "$ARCH" = "arm64" ]; then
# Apple Silicon
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ "$ARCH" = "x86_64" ]; then
# Intel
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/usr/local/bin/brew shellenv)"
fi
# 驗證
brew --version
```
### 4.2 安裝基礎工具
```bash
# 基礎開發工具
brew install \
git \
curl \
wget \
jq \
yq \
tree \
htop \
tmux \
zsh \
zsh-completions
```
---
## 階段五:安裝服務
### 5.1 安裝資料庫服務
```bash
#!/bin/bash
# 05_install_services.sh
# PostgreSQL
brew install postgresql@18
brew services start postgresql@18
# MongoDB
brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-community
# MariaDB
brew install mariadb
brew services start mariadb
# Redis
brew install redis
brew services start redis
# Qdrant (需要 Cargo)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install qdrant
```
### 5.2 安裝應用服務
```bash
# Ollama
brew install ollama
brew services start ollama
# Caddy
brew install caddy
brew services start caddy
# Gitea
brew install gitea
brew services start gitea
# PHP
brew install php
brew services start php
# n8n
brew install n8n
brew services start n8n
```
### 5.3 Launchd 服務配置
```xml
<!-- /Library/LaunchDaemons/com.momentry.postgresql.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.postgresql</string>
<key>UserName</key>
<string>momentry</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/postgresql@18/bin/postgres</string>
<string>-D</string>
<string>/Volumes/Momentry/momentry/var/postgresql</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Volumes/Momentry/momentry/log/postgresql.log</string>
<key>StandardErrorPath</key>
<string>/Volumes/Momentry/momentry/log/postgresql.error.log</string>
</dict>
</plist>
```
---
## 階段六:安裝 Momentry Core
### 6.1 複製原始碼
```bash
#!/bin/bash
# 06_install_momentry.sh
# 建立 Momentry 目錄
mkdir -p /Volumes/Momentry/momentry/{var,etc,log,scripts,backup}
mkdir -p /Volumes/Momentry/momentry_core
# 複製原始碼
rsync -av \
--exclude 'target' \
--exclude '.git' \
--exclude 'node_modules' \
/Users/accusys/momentry_core_0.1/ \
/Volumes/Momentry/momentry_core/
# 編譯 Rust 專案
cd /Volumes/Momentry/momentry_core
cargo build --release
```
### 6.2 初始化資料庫
```bash
# 建立 PostgreSQL 資料庫
psql -U postgres <<EOF
CREATE DATABASE momentry;
CREATE DATABASE n8n;
CREATE DATABASE video_register;
CREATE USER momentry WITH PASSWORD 'momentry_password';
CREATE USER n8n WITH PASSWORD 'n8n_password';
GRANT ALL PRIVILEGES ON DATABASE momentry TO momentry;
GRANT ALL PRIVILEGES ON DATABASE n8n TO n8n;
EOF
# 執行 migration
cd /Volumes/Momentry/momentry_core
sqlx migrate run
```
### 6.3 配置環境變數
```bash
# ~/.zshrc 或 ~/.bash_profile
export DATABASE_URL="postgres://momentry:momentry_password@localhost:5432/momentry"
export REDIS_URL="redis://:momentry_password@localhost:6379"
export QDRANT_URL="http://localhost:6333"
export MONGODB_URI="mongodb://localhost:27017/momentry"
export MOMENTRY_OUTPUT_DIR="/Volumes/Momentry/momentry/var/output"
export MOMENTRY_LOG_LEVEL="info"
```
---
## 階段七:網路配置
### 7.1 設定固定 IP可選
```bash
# 網路配置腳本
#!/bin/bash
# 07_configure_network.sh
# 取得網路介面
INTERFACE=$(networksetup -listallnetworkservices | grep "Thunderbolt")
# 設定固定 IP
networksetup -setmanual "$INTERFACE" \
192.168.1.100 \
255.255.255.0 \
192.168.1.1
# 設定 DNS
networksetup -setdnsservers "$INTERFACE" \
8.8.8.8 \
8.8.4.4
```
### 7.2 配置防火牆
```bash
# 開放服務端口
# 使用 macOS Firewall 或 pfctl
```
### 7.3 設定 SSH 金鑰
```bash
# 產生 SSH 金鑰對
ssh-keygen -t ed25519 -C "momentry@$(hostname)"
# 複製公鑰到目標主機
ssh-copy-id momentry@target-host
# 主控機 SSH 配置
# ~/.ssh/config
Host momentry-target
HostName 192.168.1.100
User momentry
IdentityFile ~/.ssh/id_ed25519
```
---
## 階段八:啟動服務
### 8.1 啟動順序
```bash
#!/bin/bash
# 08_start_services.sh
# 1. 基礎服務
launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
launchctl load /Library/LaunchDaemons/com.momentry.mariadb.plist
launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
sleep 10
# 2. 向量資料庫
launchctl load /Library/LaunchDaemons/com.momentry.qdrant.plist
sleep 5
# 3. 應用服務
launchctl load /Library/LaunchDaemons/com.momentry.ollama.plist
launchctl load /Library/LaunchDaemons/com.momentry.caddy.plist
sleep 5
# 4. 其他服務
launchctl load /Library/LaunchDaemons/com.momentry.gitea.plist
launchctl load /Library/LaunchDaemons/com.momentry.php.plist
launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
# 5. Momentry Core
launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbs.plist
launchctl load /Library/LaunchDaemons/com.momentry.rustdesk.hbbr.plist
```
### 8.2 驗證服務
```bash
# 檢查所有服務狀態
function check_services() {
services=(
"postgresql"
"mongodb"
"mariadb"
"redis"
"qdrant"
"ollama"
"caddy"
"gitea"
"php"
"n8n"
"sftpgo"
)
for service in "${services[@]}"; do
if launchctl list | grep "$service" | grep -q "running"; then
echo "$service: Running"
else
echo "$service: Not running"
fi
done
}
check_services
```
---
## 階段九:備份與還原
### 9.1 備份策略
```bash
#!/bin/bash
# 備份腳本
BACKUP_DIR="/Volumes/Momentry/backup/$(date +%Y%m%d)"
# 1. PostgreSQL 備份
pg_dump -U momentry momentry > "$BACKUP_DIR/momentry.sql"
pg_dump -U n8n n8n > "$BACKUP_DIR/n8n.sql"
# 2. MongoDB 備份
mongodump --out "$BACKUP_DIR/mongodb"
# 3. Redis 備份
redis-cli BGSAVE
cp /Volumes/Momentry/var/redis/dump.rdb "$BACKUP_DIR/redis.rdb"
# 4. Qdrant 備份
curl -X POST http://localhost:6333/collections/accusysdb/snapshots
# 5. 配置檔案備份
tar -czf "$BACKUP_DIR/config.tar.gz" \
/Volumes/Momentry/momentry/etc/
```
### 9.2 自動備份 Cron
```bash
# crontab -e
0 2 * * * /Volumes/Momentry/scripts/backup.sh
0 3 * * 0 /Volumes/Momentry/scripts/backup_full.sh
```
---
## 階段十:監控與維護
### 10.1 健康檢查腳本
```bash
#!/bin/bash
# health_check.sh
# 檢查所有服務
check_postgresql() {
pg_isready -q && echo "✅ PostgreSQL" || echo "❌ PostgreSQL"
}
check_mongodb() {
mongosh --eval "db.stats()" > /dev/null 2>&1 && echo "✅ MongoDB" || echo "❌ MongoDB"
}
check_redis() {
redis-cli ping > /dev/null 2>&1 && echo "✅ Redis" || echo "❌ Redis"
}
check_qdrant() {
curl -s http://localhost:6333/health && echo "✅ Qdrant" || echo "❌ Qdrant"
}
check_n8n() {
curl -s http://localhost:5678/api/v1/workflows > /dev/null 2>&1 && echo "✅ n8n" || echo "❌ n8n"
}
check_momentry() {
curl -s http://localhost:3002/api/v1/videos > /dev/null 2>&1 && echo "✅ Momentry" || echo "❌ Momentry"
}
```
### 10.2 日誌輪替
```bash
# 新聞日誌配置
/Volumes/Momentry/momentry/log/*.log {
daily
rotate 7
compress
missingok
notifempty
create 644 momentry staff
}
```
---
## 自動化腳本架構
### 主控腳本:部署控制器
```bash
#!/bin/bash
# deploy_controller.sh
# 用於從主控機部署到目標主機
set -e
# 配置
TARGET_HOST="momentry@192.168.1.100"
TARGET_DISK="/dev/disk2"
# 顏色定義
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
function log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
function log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
function log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 階段執行
function run_stage() {
local stage=$1
local script=$2
log_info "執行階段: $stage..."
ssh "$TARGET_HOST" "bash /Volumes/Momentry/scripts/$script"
if [ $? -eq 0 ]; then
log_info "✅ 階段完成: $stage"
else
log_error "❌ 階段失敗: $stage"
exit 1
fi
}
# 主程序
log_info "開始 Momentry 系統部署..."
# 執行各階段
run_stage "磁碟準備" "01_prepare_disk.sh"
run_stage "macOS 安裝" "02_install_macos.sh"
run_stage "Homebrew 安裝" "03_install_homebrew.sh"
run_stage "依賴安裝" "04_install_dependencies.sh"
run_stage "服務安裝" "05_install_services.sh"
run_stage "Momentry 安裝" "06_install_momentry.sh"
run_stage "網路配置" "07_configure_network.sh"
run_stage "啟動服務" "08_start_services.sh"
log_info "✅ 部署完成!"
```
---
## 待確認事項
### 需要與使用者確認
1. **目標主機型號**
- Intel Mac 或 Apple Silicon
- Thunderbolt 版本 (3/4)
2. **網路配置**
- DHCP 或固定 IP
- 目標 IP 網段?
3. **磁碟配置**
- 分割方案 A (200G 系統 + 1.8T 資料)
- 分割方案 B (統一磁碟區)
4. **服務需求**
- 需要安裝全部服務?
- 還是選擇性安裝?
5. **備份策略**
- 本地備份?
- 遠端備份?
- 備份頻率?
6. **監控需求**
- Prometheus + Grafana
- 簡單腳本監控?
---
## 預估時間
| 階段 | 預估時間 | 備註 |
|------|---------|------|
| 前置準備 | 30 分鐘 | 收集資訊、準備腳本 |
| 磁碟準備 | 10 分鐘 | 分割格式化 |
| macOS 安裝 | 30-60 分鐘 | 視 USB 速度 |
| Homebrew 安裝 | 15 分鐘 | 下載速度 |
| 服務安裝 | 60-90 分鐘 | 多個服務 |
| Momentry 安裝 | 20 分鐘 | 編譯 Rust |
| 網路配置 | 10 分鐘 | 固定 IP |
| 服務啟動 | 15 分鐘 | 依序啟動 |
| 驗證測試 | 30 分鐘 | 完整測試 |
| **總計** | **3-4 小時** | 自動化後可縮短 |
---
## 風險與應對
| 風險 | 機率 | 影響 | 應對措施 |
|------|------|------|---------|
| Thunderbolt 不相容 | 低 | 高 | 準備多種驅動 |
| macOS 安裝失敗 | 低 | 高 | 準備還原方案 |
| 服務啟動失敗 | 中 | 中 | 日誌診斷腳本 |
| 網路連線問題 | 中 | 中 | 有線網路備援 |
| 儲存空間不足 | 低 | 高 | 磁碟空間檢查 |
---
## 下一步行動
1. ✅ 確認目標主機規格
2. ✅ 確認 Thunderbolt NVMe 容量
3. ✅ 確認網路配置
4. ✅ 選擇服務清單
5. ✅ 準備安裝腳本
6. ✅ 測試腳本執行
7. ✅ 正式部署
---
## 附錄
### A. 服務端口對照表
| 服務 | Port | 協議 |
|------|------|------|
| PostgreSQL | 5432 | TCP |
| MongoDB | 27017 | TCP |
| MariaDB | 3306 | TCP |
| Redis | 6379 | TCP |
| Qdrant API | 6333 | HTTP |
| Qdrant gRPC | 6334 | gRPC |
| Ollama | 11434 | HTTP |
| Caddy HTTP | 80 | HTTP |
| Caddy HTTPS | 443 | HTTPS |
| Gitea | 3000 | HTTP |
| PHP-FPM | 9000 | FastCGI |
| n8n | 5678 | HTTP |
| SFTPGo | 2022 | SFTP |
| RustDesk hbbs | 21115 | TCP |
| RustDesk hbbr | 21117 | TCP |
| Momentry | 3002 | HTTP |
| Prometheus | 9090 | HTTP |
### B. 環境變數清單
`.env` 範例檔案或 `docs/MOMENTRY_CORE_MONITORING.md`
### C. 疑難排解
`docs/PENDING_ISSUES.md`
---
**計劃狀態**: 📝 草稿 - 等待使用者確認後執行
**負責人**: OpenCode AI Assistant
**最後更新**: 2026-03-23

View File

@@ -0,0 +1,322 @@
# Momentry Core 影片 RAG 系統說明稿
## 系統架構
```
┌─────────────────────────────────────────────────────────────┐
│ 使用者 │
│ (marcom 團隊) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ WordPress 入口 │
│ (wp.momentry.ddns.net) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ n8n 自動化 │
│ (localhost:5678) │
│ │
│ [Webhook] → [HTTP Request] → [處理結果] → [回覆用戶] │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Momentry Core API │
│ (localhost:3002) │
│ │
│ POST /api/v1/search → 語意搜尋 │
│ POST /api/v1/n8n/search → n8n 專用格式 │
│ GET /api/v1/videos → 影片列表 │
└─────────────────┬───────────────────────────────────────────┘
┌─────────┴──────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ PostgreSQL │ │ Qdrant │
│ (chunks) │ │ (vectors) │
└───────────────┘ └───────────────┘
```
---
## 資料流程
```
1. 上傳影片 → SFTPGo
2. 影片註冊 → PostgreSQL
3. ASR 處理 → 產生字幕區塊
4. 儲存 chunks → PostgreSQL
5. 向量化 → Qdrant
6. 搜尋查詢 → API
7. 回傳結果 → n8n → 用戶
```
---
## 示範影片
| 項目 | 內容 |
|------|------|
| 檔案名稱 | Old_Time_Movie_Show_-_Charade_1963.HD.mov |
| UUID | a1b10138a6bbb0cd |
| 時長 | 6879 秒(約 1.9 小時) |
| 區塊數 | 3,886 個 |
| 向量數 | 3,688 個 |
---
## API 端點
### 1. 語意搜尋
```
POST http://localhost:3002/api/v1/search
```
**請求:**
```json
{
"query": "charade",
"limit": 5,
"uuid": "a1b10138a6bbb0cd"
}
```
**回應:**
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0006",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526
}
]
}
```
---
### 2. n8n 專用格式
```
POST http://localhost:3002/api/v1/n8n/search
```
**請求:**
```json
{
"query": "charade",
"limit": 5
}
```
**回應:**
```json
{
"query": "charade",
"count": 5,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## 實作範例
### n8n Workflow 設計
```
┌─────────────┐
│ Webhook │ ← 接收用戶搜尋請求
└──────┬──────┘
┌─────────────┐
│ HTTP Request│ → POST /api/v1/n8n/search
└──────┬──────┘
┌─────────────┐
│ Code │ → 處理回傳結果
└──────┬──────┘
┌─────────────┐
│ Telegram │ → 回覆給用戶
│ (或 LINE) │
└─────────────┘
```
---
## Step-by-Step n8n Workflow
### Step 1: 建立 Webhook
1. n8n 開新 Workflow
2. 新增 node: **Webhook**
3. 設定 path: `video-search`
4. 複製 Webhook URL
---
### Step 2: 設定 HTTP Request
1. 新增 node: **HTTP Request**
2. 設定:
```
Method: POST
URL: http://localhost:3002/api/v1/n8n/search
Body Content Type: JSON
```
3. Body:
```json
{
"query": "={{ $json.body }}",
"limit": 5
}
```
---
### Step 3: 處理結果 (Code)
```javascript
const hits = $input.first().json.hits;
if (!hits || hits.length === 0) {
return {
json: { message: "找不到相關結果" }
};
}
const results = hits.map((hit, index) => ({
number: index + 1,
text: hit.text,
time: `${hit.start}s - ${hit.end}s`,
score: Math.round(hit.score * 100) + "%",
url: hit.media_url + "#t=" + hit.start + "," + hit.end
}));
return { json: { results } };
```
---
### Step 4: 格式化輸出
**Telegram 格式:**
```
🎬 搜尋結果: "{{ $json.query }}"
1⃣ "fun plot twists, Woody Dialog and charming performances..."
⏱ 48.8s - 55.4s
📊 相關度: 53%
2⃣ "Don't you like me to say that a pretty girl..."
⏱ 4745.6s - 4748.6s
📊 相關度: 52%
```
---
## 測試指令
### curl 測試
```bash
# 語意搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# n8n 格式
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# 影片列表
curl http://localhost:3002/api/v1/videos
# 特定影片區塊
curl http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
```
---
## 實際搜尋範例
| 搜尋詞 | 結果摘要 |
|--------|----------|
| `charade` | "fun plot twists, Woody Dialog and charming performances..." |
| `woody` | "Well, you thick skull hair, brain half-witted..." |
| `classic movie` | "Hello and welcome to the old-time movie show..." |
| `charming` | "fun plot twists, Woody Dialog and charming performances..." |
---
## 資料庫狀態
| 資料庫 | 資料筆數 | 狀態 |
|--------|----------|------|
| PostgreSQL (videos) | 4 | ✅ |
| PostgreSQL (chunks) | 3,950 | ✅ |
| PostgreSQL (vectors) | 1,870 | ✅ |
| Qdrant (vectors) | 3,688 | ✅ |
| Redis (job cache) | 4 keys | ✅ |
---
## 下一步
1. **建立 SFTPGo 分享連結**
- 開啟 http://localhost:8080
- 登入 demo / demopassword123
- 建立影片分享連結
2. **測試 n8n Workflow**
- 匯入 Postman Collection
- 建立 Webhook
- 測試搜尋
3. **整合到 WordPress**
- 建立表單接收用戶輸入
- 呼叫 n8n Webhook
- 顯示搜尋結果
---
## 快速開始
```bash
# 1. 測試搜尋 API
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# 2. 查看影片列表
curl http://localhost:3002/api/v1/videos
# 3. 查看 n8n 是否運行
curl http://localhost:5678
```

View File

@@ -0,0 +1,127 @@
{
"info": {
"name": "Momentry Core API",
"description": "Video RAG API for Momentry Core - Video search and management",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:3002/api/v1"
}
],
"item": [
{
"name": "Search",
"item": [
{
"name": "Semantic Search",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5\n}"
},
"url": {
"raw": "{{baseUrl}}/search",
"host": ["{{baseUrl}}"],
"path": ["search"]
},
"description": "Semantic search across video chunks using vector embeddings"
}
},
{
"name": "Search with UUID Filter",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5,\n \"uuid\": \"a1b10138a6bbb0cd\"\n}"
},
"url": {
"raw": "{{baseUrl}}/search",
"host": ["{{baseUrl}}"],
"path": ["search"]
},
"description": "Search within a specific video by UUID"
}
},
{
"name": "n8n Integration Search",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5\n}"
},
"url": {
"raw": "{{baseUrl}}/n8n/search",
"host": ["{{baseUrl}}"],
"path": ["n8n", "search"]
},
"description": "Search formatted for n8n workflow integration"
}
}
]
},
{
"name": "Videos",
"item": [
{
"name": "List All Videos",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos",
"host": ["{{baseUrl}}"],
"path": ["videos"]
},
"description": "Get list of all registered videos"
}
},
{
"name": "Get Video by UUID",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos/a1b10138a6bbb0cd",
"host": ["{{baseUrl}}"],
"path": ["videos", "a1b10138a6bbb0cd"]
},
"description": "Get details for a specific video"
}
},
{
"name": "Get Video Chunks",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos/a1b10138a6bbb0cd/chunks",
"host": ["{{baseUrl}}"],
"path": ["videos", "a1b10138a6bbb0cd", "chunks"]
},
"description": "Get all chunks for a video"
}
}
]
}
]
}

106
docs/N8N_API_FIX_SUMMARY.md Normal file
View File

@@ -0,0 +1,106 @@
# n8n REST API Fix Summary
## Issue
n8n REST API was returning 404 errors for all endpoints (`/api/v1/workflows`, `/rest/workflows`, etc.)
## Root Cause
Port 5678 was occupied by the **n8n worker** process, preventing the main n8n instance from starting properly.
## Solution
### 1. Identified Port Conflict
- Worker process was listening on port 5678 (same as main instance)
- Main n8n couldn't start because port was in use
### 2. Fixed Worker Configuration
Updated `/Library/LaunchDaemons/com.momentry.n8n.worker.plist`:
- Added `N8N_PORT=5680` to worker environment variables
- Workers shouldn't need HTTP ports, but this prevents port conflict
### 3. Restarted Services
```bash
# Kill all n8n processes
sudo pkill -9 -f n8n
# Start main n8n (now successfully binds to port 5678)
sudo launchctl enable system/com.momentry.n8n.main
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.n8n.main.plist
```
## Current Status
### n8n Instance
- **URL**: http://localhost:5678
- **Version**: 2.3.5
- **Status**: Running ✅
- **API Enabled**: Yes ✅
### API Key
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNjdiY2UzOS1iY2RkLTRjMjEtYmMwYy0yODNhYmI3ZjVjMjMiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzc0MTk4NzgwfQ.zke_Qc-saILl_tcwXm2K3J4slCmaXnzCfxVbdVPPvCE
```
### MCP Configuration
File: `~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
## Verified Endpoints
### List Workflows
```bash
curl -H "X-N8N-API-KEY: <API_KEY>" http://localhost:5678/api/v1/workflows
```
**Result**: ✅ 30 workflows returned
### List Executions
```bash
curl -H "X-N8N-API-KEY: <API_KEY>" http://localhost:5678/api/v1/executions
```
**Result**: ✅ 100 executions returned
## Next Steps
1. **Start n8n Worker** (optional for MCP):
Workers handle job processing but aren't required for API access.
2. **Test MCP Integration**:
Restart OpenCode to load the MCP configuration and test n8n integration.
3. **Verify Workflow Management**:
- Create workflow via API
- Execute workflow
- Monitor execution status
## Files Modified
- `/Library/LaunchDaemons/com.momentry.n8n.worker.plist` - Added N8N_PORT=5680
## API Documentation
- Base URL: `http://localhost:5678/api/v1`
- Authentication: Header `X-N8N-API-KEY: <token>`
- Available endpoints: workflows, executions, credentials, users, etc.
See full API reference: https://docs.n8n.io/api/

220
docs/N8N_DEMO.md Normal file
View File

@@ -0,0 +1,220 @@
# n8n 整合範例
## 基本設定
### API 端點
- **Base URL:** `http://localhost:3002/api/v1`
- **Method:** `POST`
- **Content-Type:** `application/json`
---
## Workflow 1: 基礎搜尋
### Trigger: Manual / Webhook
```
[Manual Trigger]
[HTTP Request] → POST http://localhost:3002/api/v1/search
[Set] → 設定搜尋詞 "charade"
[Code] → 處理回傳結果
[Respond]
```
### HTTP Request 設定
```json
{
"url": "http://localhost:3002/api/v1/search",
"method": "POST",
"body": {
"query": "={{ $json.searchTerm }}",
"limit": 5
},
"options": {
"headers": {
"Content-Type": "application/json"
}
}
}
```
### Code (處理結果)
```javascript
const results = $input.first().json.results;
const videoUrl = "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov";
return results.map(r => ({
chunk_id: r.chunk_id,
text: r.text,
start: r.start_time,
end: r.end_time,
score: r.score,
video_url: `${videoUrl}#t=${r.start_time},${r.end_time}`
}));
```
---
## Workflow 2: n8n 專用格式
使用 `/n8n/search` 端點(已包含 media_url
### HTTP Request
```json
{
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"body": {
"query": "={{ $json.searchTerm }}",
"limit": 5
}
}
```
### 回傳格式
```json
{
"query": "charade",
"count": 5,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## Workflow 3: 訊息機器人整合
### Telegram Bot 範例
```
[Webhook: Telegram]
[Extract: /search charade]
[HTTP Request] → POST /api/v1/search
[Format Response]
[Telegram: Send Message]
```
### 回傳格式
```
🎬 搜尋結果: "charade"
1. "fun plot twists, Woody Dialog and charming performances..."
⏱ 48.8s - 55.4s
📊 分數: 0.526
2. "Don't you like me to say that a pretty girl..."
⏱ 4745.6s - 4748.6s
📊 分數: 0.525
```
---
## Workflow 4: 多影片搜尋
### 取得所有影片
```
[HTTP Request]
GET http://localhost:3002/api/v1/videos
```
### 依 UUID 篩選
```json
{
"query": "charade",
"limit": 5,
"uuid": "a1b10138a6bbb0cd"
}
```
---
## Workflow 5: 定時更新
```
[Cron: 每小時]
[HTTP Request] → GET /api/v1/videos
[Loop Over Items]
[Check: 新影片?]
[Process: 執行 vectorize]
```
---
## 實用場景
### 1. 客服機器人
用戶問「這部片在哪一段有談到 charade
→ 搜尋 API → 回傳時戳 → 直接播放該片段
### 2. 內容推薦
根據用戶輸入的關鍵字,找到相關影片片段
### 3. 自動化剪輯
搜尋多個片段 → 組合成精華影片
---
## 錯誤處理
```javascript
const response = $input.first();
if (!response.json.results || response.json.results.length === 0) {
return {
success: false,
message: "找不到相關結果"
};
}
return {
success: true,
count: response.json.results.length,
data: response.json.results
};
```
---
## 測試用 cURL
```bash
# 基本搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# n8n 格式
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# 取得影片列表
curl http://localhost:3002/api/v1/videos
# 取得特定影片的區塊
curl http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
```

View File

@@ -0,0 +1,341 @@
# n8n Video RAG Demo - API 執行記錄
> 建立時間: 2026-03-22
> 目標: 完整執行 n8n Video RAG Workflow 並記錄所有 API 呼叫
---
## Phase 1: SFTPGo 準備
### Step 1.1: 取得 Demo User Token
**API 呼叫:**
```bash
curl -X GET "http://localhost:8080/api/v2/user/token" \
-u "demo:demopassword123"
```
**Request:**
```
GET /api/v2/user/token
Authorization: Basic ZG9tbzpkZW1vcGFzc3dvcmQxMjM=
```
**Response (200 OK):**
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-03-22T07:05:57Z"
}
```
**Token 有效期限:** 20 分鐘
**Session Token (Demo User):**
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiQVBJVXNlciIsIjo6MSJdLCJleHAiOjE3NzQxNjMxNTcsImlhdCI6MTc3NDE2MTk1NywianRpIjoiZDZ2cDA5YWcyZnIwMnY3aTlybDAiLCJuYmYiOjE3NzQxNjE5NDcsInN1YiI6IjE3NzQxNjE5NTM0OTMiLCJ1c2VybmFtZSI6ImRlbW8ifQ.yw0UCv8sQXXCkOr7qmK2ejLzuoA8IDrmC9bpgFE4R_Q
```
**結果:** ✅ 成功
---
### Step 1.2: 上傳測試影片到 SFTPGo
**影片選擇:** `Old_Time_Movie_Show_-_Charade_1963.HD.mov` (2.3 GB)
- 路徑: `/Users/accusys/test_video/Old_Time_Movie_Show_-_Charade_1963.HD.mov`
- ASR Segments: 1,917 (已預處理)
**API 呼叫:**
```bash
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -X POST "http://localhost:8080/api/v2/user/files" \
-H "Authorization: Bearer $TOKEN" \
-F "path=/demo" \
-F "mkdir_parents=true" \
-F "filenames=@/Users/accusys/test_video/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
```
**Request:**
```
POST /api/v2/user/files
Authorization: Bearer <token>
Content-Type: multipart/form-data
path: /demo
mkdir_parents: true
filenames: @/path/to/Old_Time_Movie_Show_-_Charade_1963.HD.mov
```
**Response (201 Created):**
```json
{"message":"Upload completed"}
```
**上傳統計:**
- 檔案大小: 2,361,629,896 bytes (2.3 GB)
- 上傳時間: 7 秒
- 平均速度: ~337 MB/s
**結果:** ✅ 成功
---
### Step 1.3: 建立分享連結
**API 呼叫:**
```bash
curl -X POST "http://localhost:8080/api/v2/user/shares" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Charade_1963_Demo",
"paths": ["/Old_Time_Movie_Show_-_Charade_1963.HD.mov"],
"scope": 1,
"expires_at": 0
}'
```
**Request:**
```json
POST /api/v2/user/shares
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Charade_1963_Demo",
"paths": ["/Old_Time_Movie_Show_-_Charade_1963.HD.mov"],
"scope": 1,
"expires_at": 0
}
```
**Response (200 OK):**
```json
{"message":"Share created"}
```
**結果:** ✅ 成功
---
### Step 1.4: 驗證上傳結果
**API 呼叫 - 列出分享:**
```bash
curl -X GET "http://localhost:8080/api/v2/user/shares" \
-H "Authorization: Bearer $TOKEN"
```
**Response:**
```json
[
{
"id": "CjmQfrkXY5qDtC46WVZY2S",
"name": "Charade_1963_Demo",
"scope": 1,
"paths": [
"/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
],
"username": "demo",
"created_at": 1774162072853,
"updated_at": 1774162072853,
"password": ""
}
]
```
**分享連結:**
- Share ID: `CjmQfrkXY5qDtC46WVZY2S`
- Browse URL: `http://localhost:8080/web/client/pubshares/CjmQfrkXY5qDtC46WVZY2S/browse`
**本地目錄驗證:**
```
/Users/accusys/sftpgo_test/demo/
└── Old_Time_Movie_Show_-_Charade_1963.HD.mov (2,361,629,896 bytes)
```
**結果:** ✅ 成功
---
## Phase 2: Momentry 註冊
### Step 2.1: 健康檢查
**API 呼叫:**
```bash
curl -X GET "http://localhost:3002/health"
```
**Response:**
```
(待填寫)
```
---
### Step 2.2: 註冊影片
**API 呼叫:**
```bash
curl -X POST "http://localhost:3002/api/v1/register" \
-H "Content-Type: application/json" \
-d '{
"path": "/Users/accusys/sftpgo_test/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}'
```
**Request:**
```json
POST /api/v1/register
Content-Type: application/json
{
"path": "/Users/accusys/sftpgo_test/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
```
**Response:**
```
(待填寫)
{
"uuid": "...",
"video_id": ...,
"file_name": "...",
"duration": ...,
"width": ...,
"height": ...
}
```
---
## Phase 3: 處理進度追蹤
### Step 3.1: 查詢處理進度 (新版 API)
**API 呼叫:**
```bash
curl -X GET "http://localhost:3002/api/v1/progress/{uuid}"
```
**Response (新版 - 包含影片資訊與系統資源):**
```json
{
"uuid": "a1b10138a6bbb0cd",
"user": null,
"group": null,
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
"duration": 6879.33,
"overall_progress": 28,
"cpu_percent": 3.7,
"gpu_percent": null,
"memory_percent": 0.1,
"memory_mb": 19328,
"processors": [
{"name": "asr", "status": "complete", "current": 1867, "total": 0, "progress": 100, "message": "1867 segments"},
{"name": "cut", "status": "complete", "current": 1331, "total": 1331, "progress": 100, "message": "1331 scenes"},
{"name": "asrx", "status": "error", "current": 0, "total": 0, "progress": 0, "message": "0 segments"},
{"name": "yolo", "status": "progress", "current": 69400, "total": 412343, "progress": 16, "message": "frame 69400"},
{"name": "ocr", "status": "pending", "current": 0, "total": 0, "progress": 0, "message": ""},
{"name": "face", "status": "pending", "current": 0, "total": 0, "progress": 0, "message": ""},
{"name": "pose", "status": "pending", "current": 0, "total": 0, "progress": 0, "message": ""}
]
}
```
**欄位說明:**
| 欄位 | 說明 |
|------|------|
| uuid | 影片唯一識別碼 |
| user | 處理所屬用戶 (如已設定) |
| group | 處理所屬群組 (如已設定) |
| file_name | 影片檔案名稱 |
| duration | 影片時長 (秒) |
| overall_progress | 整體進度 (百分比) |
| cpu_percent | CPU 使用率 (%) |
| gpu_percent | GPU 使用率 (%),無 GPU 則為 null |
| memory_percent | 記憶體使用率 (%) |
| memory_mb | 記憶體使用量 (MB) |
| processors | 各處理器狀態陣列 |
```
---
## Phase 4: 自然語言檢索
### Step 4.1: RAG 搜尋
**API 呼叫:**
```bash
curl -X POST "http://localhost:3002/api/v1/search" \
-H "Content-Type: application/json" \
-d '{
"query": "What is the movie about?",
"limit": 10,
"uuid": "..."
}'
```
**Request:**
```json
POST /api/v1/search
Content-Type: application/json
{
"query": "What is the movie about?",
"limit": 10,
"uuid": "<uuid from registration>"
}
```
**Response:**
```
(待填寫)
```
---
### Step 4.2: n8n 搜尋 (含 media_url)
**API 呼叫:**
```bash
curl -X POST "http://localhost:3002/api/v1/n8n/search" \
-H "Content-Type: application/json" \
-d '{
"query": "What is the movie about?",
"limit": 10,
"uuid": "..."
}'
```
**Response:**
```
(待填寫)
```
---
## 憑證彙整
| 服務 | 項目 | 值 |
|------|------|------|
| SFTPGo | API Base | `http://localhost:8080/api/v2` |
| SFTPGo | Demo User | `demo` |
| SFTPGo | Demo Password | `demopassword123` (已重設) |
| SFTPGo | Demo Home | `/Users/accusys/sftpgo_test/demo` |
| SFTPGo | Token Endpoint | `/api/v2/user/token` |
| SFTPGo | Share ID | `CjmQfrkXY5qDtC46WVZY2S` |
| Momentry | Server | `http://localhost:3002` |
| Momentry | MEDIA_BASE_URL | `https://wp.momentry.ddns.net` |
---
## 版本歷史
| 日期 | 版本 | 變更 |
|------|------|------|
| 2026-03-22 | v1.0 | 初始建立文件 |
| 2026-03-22 | v1.1 | 成功取得 Demo Token |
| 2026-03-22 | v1.2 | Phase 1 完成 (上傳 Charade 2.3GB) |

677
docs/N8N_DEMO_WORKFLOW.md Normal file
View File

@@ -0,0 +1,677 @@
# n8n Video RAG Workflow - Node 設計
> 建立時間: 2026-03-22
> 目標: 讓 marcom 團隊能夠複製、貼上、修改使用的完整操作指南
---
## 完整 Workflow 架構
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ n8n Workflow: Video RAG Demo │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 1: SFTPGo 準備 (全部在 n8n Node 內執行) │ │
│ │ │ │
│ │ ① Webhook Trigger │ │
│ │ ↓ │ │
│ │ ② Set Variables (解析 file_name, query) │ │
│ │ ↓ │ │
│ │ ③ Get SFTPGo Token │ │
│ │ ↓ │ │
│ │ ④ Upload to SFTPGo │ │
│ │ ↓ │ │
│ │ ⑤ Create Share Link │ │
│ │ ↓ │ │
│ │ ⑥ Verify Upload (List Files + List Shares) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 2: Momentry 註冊 (只處理 ASR, ASRX, STORY) │ │
│ │ │ │
│ │ ⑦ Register Video (modules=asr,asrx,story) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 3: Progress Loop (n8n Logs 記錄) │ │
│ │ │ │
│ │ ⑧ Wait 10s ─────────────────────────────────────────────────┐ │ │
│ │ ↓ │ │
│ │ ⑨ Check Progress (API) │ │
│ │ ↓ │ │
│ │ ⑩ Log Progress (Code Node → n8n Logs) │ │
│ │ ↓ │ │
│ │ ⑪ Is Complete? (IF) │ │
│ │ │ │ │
│ │ ├── NO ──────────────────────────────── Loop Back ─────────┘ │ │
│ │ └── YES ────────────────────────────────────────────── Exit ──┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 4: 搜尋與回應 │ │
│ │ │ │
│ │ ⑫ Hybrid Search (Vector + BM25) │ │
│ │ ↓ │ │
│ │ ⑬ Build Response │ │
│ │ ↓ │ │
│ │ ⑭ Respond to Webhook │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 模組說明
| 模組 | 用途 | 輸出 |
|------|------|------|
| `asr` | 語音轉文字 (Whisper) | 字幕/文字稿 |
| `asrx` | 說話者分離 (WhisperX) | 誰在什麼時候說什麼 |
| `story` | 故事線生成 (Parent-Child Chunks) | 敘事結構 + 父子區塊關聯 |
**注意**: 只處理語音和故事相關模組,跳過 YOLO、OCR、Face、Pose 等視覺分析。
┌─────────────────────────────────────────────────────────────────────────────┐
│ n8n Workflow: Video RAG Demo │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 1: SFTPGo 準備 (全部在 n8n Node 內執行) │ │
│ │ │ │
│ │ ① Webhook Trigger │ │
│ │ ↓ │ │
│ │ ② Set Variables (解析 file_name, query) │ │
│ │ ↓ │ │
│ │ ③ Get SFTPGo Token │ │
│ │ ↓ │ │
│ │ ④ Upload to SFTPGo │ │
│ │ ↓ │ │
│ │ ⑤ Create Share Link │ │
│ │ ↓ │ │
│ │ ⑥ Verify Upload (List Files + List Shares) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 2: Momentry 註冊 │ │
│ │ │ │
│ │ ⑦ Register Video │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 3: Progress Loop (n8n Logs 記錄) │ │
│ │ │ │
│ │ ⑧ Wait 10s ─────────────────────────────────────────────────┐ │ │
│ │ ↓ │ │ │
│ │ ⑨ Check Progress (API) │ │ │
│ │ ↓ │ │ │
│ │ ⑩ Log Progress (Code Node → n8n Logs) │ │ │
│ │ ↓ │ │ │
│ │ ⑪ Is Complete? (IF) │ │ │
│ │ │ │ │ │
│ │ ├── NO ──────────────────────────────── Loop Back ─────────┘ │ │
│ │ └── YES ────────────────────────────────────────────── Exit ──┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 4: 搜尋與回應 │ │
│ │ │ │
│ │ ⑫ Natural Language Search │ │
│ │ ↓ │ │
│ │ ⑬ Get Media URL (含 media_url) │ │
│ │ ↓ │ │
│ │ ⑭ Build Response │ │
│ │ ↓ │ │
│ │ ⑮ Respond to Webhook │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## Node 詳細配置
### Node ①: Webhook Trigger (觸發器)
```yaml
Node Name: "Webhook Trigger"
Node Type: "Webhook"
Configuration:
HTTP Method: POST
Path: "video-rag"
Response Mode: "Response Node"
Response Node: "Respond to Webhook"
Input JSON Example:
{
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
"query": "What is the movie about?"
}
```
---
### Node ②: Set Variables (變數設定)
```yaml
Node Name: "Set Variables"
Node Type: "Set"
Configuration:
Keep Only Set: true
Variables:
- Name: "file_name"
Value: "{{ $json.body.file_name }}"
- Name: "query"
Value: "{{ $json.body.query }}"
- Name: "sftpgo_path"
Value: "/{{ $json.body.file_name }}"
- Name: "register_path"
Value: "/Users/accusys/sftpgo_test/demo/{{ $json.body.file_name }}"
```
---
### Node ③: Get SFTPGo Token (取得權杖)
```yaml
Node Name: "Get SFTPGo Token"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:8080/api/v2/user/token"
Authentication: "Basic Auth"
User: "demo"
Password: "demopassword123"
Output:
{
"access_token": "eyJhbGci...",
"expires_at": "2026-03-22T07:00:00Z"
}
```
---
### Node ④: Upload to SFTPGo (上傳檔案)
```yaml
Node Name: "Upload to SFTPGo"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:8080/api/v2/user/files"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Body Content Type: "Form-Data Multipart"
Body:
path: /demo
mkdir_parents: true
filenames: @{{ $json.file_name }}
Output:
{"message":"Upload completed"}
```
**檔案來源選項:**
1. **Webhook 接收**: 從 Webhook 的 binary data 取得
2. **固定路徑**: 指定本地檔案路徑
3. **URL 下載**: 先下載遠端檔案再上傳
---
### Node ⑤: Create Share Link (建立分享連結)
```yaml
Node Name: "Create Share Link"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:8080/api/v2/user/shares"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Body Content Type: "JSON"
Body:
{
"name": "{{ $json.file_name }}_share",
"paths": ["/{{ $json.file_name }}"],
"scope": 1,
"expires_at": 0
}
Output:
{
"id": "CjmQfrkXY5qDtC46WVZY2S",
"name": "Charade_share"
}
```
---
### Node ⑥: Verify Upload (驗證上傳)
```yaml
Node Name: "Verify Upload - List Shares"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:8080/api/v2/user/shares"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Output:
[
{
"id": "CjmQfrkXY5qDtC46WVZY2S",
"name": "Charade_share",
"paths": ["/Old_Time_Movie_Show_-_Charade_1963.HD.mov"]
}
]
```
---
### Node ⑦: Register Video (註冊影片)
**說明**: 只註冊 ASR、ASRX、STORY 模組處理
```yaml
Node Name: "Register Video"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/register"
Body Content Type: "JSON"
Body:
{
"path": "{{ $json.register_path }}",
"modules": "asr,asrx,story"
}
Output:
{
"uuid": "a1b10138a6bbb0cd",
"video_id": 7,
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
"duration": 6879.33,
"width": 1920,
"height": 1080
}
```
**可用模組**:
| 模組 | 說明 |
|------|------|
| `asr` | 語音轉文字 (Whisper) |
| `asrx` | 說話者分離 (WhisperX) |
| `story` | 故事線生成 (Parent-Child) |
| `yolo` | 物體偵測 (可選) |
| `cut` | 場景偵測 (可選) |
| `ocr` | 文字辨識 (可選) |
| `face` | 人臉偵測 (可選) |
| `pose` | 姿態估計 (可選) |
---
### Node ⑧: Wait 10 Seconds (輪詢間隔)
```yaml
Node Name: "Wait 10 Seconds"
Node Type: "Wait"
Configuration:
Amount: 10
Unit: "Seconds"
```
---
### Node ⑨: Check Progress (檢查進度)
```yaml
Node Name: "Check Progress"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:3002/api/v1/progress/{{ $('Register Video').item.json.uuid }}"
Output:
{
"uuid": "a1b10138a6bbb0cd",
"processors": [
{"name": "asr", "status": "complete", "message": "1867 segments"},
{"name": "asrx", "status": "progress", "message": "ASRX_TRANSCRIBING"},
{"name": "story", "status": "pending", "message": ""}
]
}
```
**注意**: 只有 asr、asrx、story 三個模組
---
### Node ⑩: Log Progress (記錄進度)
```yaml
Node Name: "Log Progress"
Node Type: "Code"
Configuration:
Language: "JavaScript"
Code:
```javascript
const progress = $input.first().json;
const processors = progress.processors;
const totalProcessors = processors.length;
const completedProcessors = processors.filter(p => p.status === 'complete').length;
const overallProgress = Math.round((completedProcessors / totalProcessors) * 100);
const currentProcessor = processors.find(p =>
p.status === 'progress' || p.status === 'info'
);
const progressMessage = `
═══════════════════════════════════════════════
📹 Video RAG Processing: ${overallProgress}%
UUID: ${progress.uuid}
${processors.map(p => {
const icon = p.status === 'complete' ? '✅' :
p.status === 'progress' || p.status === 'info' ? '🔄' : '⏳';
return ` ${icon} ${p.name.padEnd(6)} ${p.message || p.status}`;
}).join('\n')}
${currentProcessor ? `Current: ${currentProcessor.name}` : 'All complete!'}
═══════════════════════════════════════════════
`.trim();
console.log(progressMessage);
return {
json: {
uuid: progress.uuid,
overall_progress: overallProgress,
completed_processors: completedProcessors,
total_processors: totalProcessors,
current_processor: currentProcessor?.name || 'idle',
processors: processors,
log_message: progressMessage
}
};
```
Output:
{
"uuid": "a1b10138a6bbb0cd",
"overall_progress": 33,
"log_message": "📹 Video RAG Processing: 33%..."
}
```
---
### Node ⑪: Is Complete? (判斷分支)
```yaml
Node Name: "Is Complete?"
Node Type: "IF"
Configuration:
Condition:
$json.processors.every(p => p.status === 'complete')
Connections:
TRUE (完成): → Node ⑫ Natural Language Search
FALSE (未完成): → Node ⑧ Wait 10 Seconds (Loop)
```
---
### Node ⑫: Natural Language Search (RAG 搜尋)
```yaml
Node Name: "Natural Language Search"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/search"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}"
}
Output:
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "c_001",
"text": "Hello and welcome to the old-time movie show...",
"score": 0.92
}
]
}
```
---
### Node ⑫B: Hybrid Search (Vector + BM25)
**說明**: 使用混合搜尋,結合向量相似度和全文檢索
```yaml
Node Name: "Hybrid Search"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/search/hybrid"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}",
"vector_weight": 0.7,
"bm25_weight": 0.3
}
Output:
{
"query": "What is the movie about?",
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "c_001",
"chunk_type": "sentence",
"start_time": 0.0,
"end_time": 5.0,
"text": "Hello and welcome to the old-time movie show...",
"vector_score": 0.85,
"bm25_score": 0.75,
"combined_score": 0.80
}
]
}
```
**權重建議**:
| 查詢類型 | vector_weight | bm25_weight |
|----------|---------------|-------------|
| 主題查詢 | 0.8 | 0.2 |
| 事實查找 | 0.5 | 0.5 |
| 平衡查詢 | 0.7 | 0.3 |
---
### Node ⑬: Get Media URL (取得媒體連結)
```yaml
Node Name: "Get Media URL"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/n8n/search"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}"
}
Output:
{
"count": 10,
"hits": [
{
"id": "c_001",
"vid": "a1b10138a6bbb0cd",
"text": "Hello and welcome to the old-time movie show...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
### Node ⑭: Build Response (組合結果)
```yaml
Node Name: "Build Response"
Node Type: "Set"
Configuration:
Keep Only Set: true
Variables:
- Name: "ok"
Value: true
- Name: "uuid"
Value: "{{ $('Register Video').item.json.uuid }}"
- Name: "file_name"
Value: "{{ $('Set Variables').item.json.file_name }}"
- Name: "query"
Value: "{{ $('Set Variables').item.json.query }}"
- Name: "count"
Value: "{{ $('Get Media URL').item.json.count }}"
- Name: "results"
Value: "{{ $('Get Media URL').item.json.hits }}"
- Name: "overall_progress"
Value: "{{ $('Log Progress').item.json.overall_progress }}"
```
---
### Node ⑮: Respond to Webhook (回傳結果)
```yaml
Node Name: "Respond to Webhook"
Node Type: "Respond to Webhook"
Configuration:
Respond With: "JSON"
Response Body:
{
"ok": true,
"uuid": "{{ $json.uuid }}",
"file_name": "{{ $json.file_name }}",
"query": "{{ $json.query }}",
"count": {{ $json.count }},
"results": {{ $json.results }},
"overall_progress": {{ $json.overall_progress }},
"message": "Video RAG completed successfully"
}
```
---
## 快速複製所需資訊
### SFTPGo 設定
| 項目 | 值 |
|------|-----|
| API Base | `http://localhost:8080/api/v2` |
| Demo User | `demo` |
| Demo Password | `demopassword123` |
| Demo Home | `/Users/accusys/sftpgo_test/demo` |
| Token Endpoint | `/api/v2/user/token` |
| Upload Endpoint | `/api/v2/user/files` |
| Share Endpoint | `/api/v2/user/shares` |
### Momentry 設定
| 項目 | 值 |
|------|-----|
| API Base | `http://localhost:3002` |
| Register | `POST /api/v1/register` |
| Progress | `GET /api/v1/progress/{uuid}` |
| Search | `POST /api/v1/search` |
| n8n Search | `POST /api/v1/n8n/search` |
| Hybrid Search | `POST /api/v1/search/hybrid` |
| Media Base | `https://wp.momentry.ddns.net` |
### Demo 測試資料
**Charade (1963) Demo Video**
- UUID: `a1b10138a6bbb0cd`
- 位置: `/Users/accusys/test_video/Old_Time_Movie_Show_-_Charade_1963.HD.mov`
- 時長: 6872 秒 (~1.9 小時)
**已處理檔案**:
| 檔案 | 大小 | 內容 |
|------|------|------|
| `asr.json` | 210KB | 1867 語音區段 |
| `cut.json` | 220KB | 1331 場景 |
| `story.json` | 1.8MB | 641 父子區塊 |
| `transcript.txt` | 40KB | 可讀文字稿 |
**Output 目錄**: `/Users/accusys/momentry_core_0.1/output`
---
## 版本歷史
| 日期 | 版本 | 變更 |
|------|------|------|
| 2026-03-22 | v1.0 | 初始建立 |
| 2026-03-22 | v1.1 | 新增 Hybrid Search (Vector + BM25) 節點 |
| 2026-03-22 | v1.2 | 簡化為只處理 ASR、ASRX、STORY 模組 |

View File

@@ -0,0 +1,245 @@
# n8n HTTP Request Node 設定指南
> **API URL 說明**:
> - **本地測試**: `http://localhost:3002`
> - **n8n workflow**: `https://api.momentry.ddns.net`
>
> ⚠️ 在 n8n 中請使用 `api.momentry.ddns.net`,不要使用 `localhost:3002`
---
## 錯誤排除
### 錯誤訊息: "Your request is invalid or could not be processed by the service"
這通常表示 HTTP Request Node 的設定不正確。
---
## 正確的 Node 設定方式
### 方法 1: 使用 JSON Body (推薦)
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Authentication: None
├── Send Body: ✓ (checked)
├── Content Type: JSON
├── Body:
│ {
│ "query": "={{ $json.query }}",
│ "limit": "={{ $json.limit }}"
│ }
└── Options: (empty)
```
### 方法 2: 使用 Raw Body + Headers
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Authentication: None
├── Send Body: ✓ (checked)
├── Specify Body: Using JSON
├── JSON Body:
│ {
│ "query": "charade",
│ "limit": 3
│ }
├── Send Headers: ✓ (checked)
└── Header Parameters:
└── Content-Type: application/json
```
### 方法 3: 最簡單的 Hardcoded 測試
```
Node: HTTP Request
├── URL: https://api.momentry.ddns.net/api/v1/n8n/search
├── Method: POST
├── Send Body: ✓
├── Content Type: JSON
└── Body:
{
"query": "charade",
"limit": 3
}
```
---
## 常見錯誤與解決
### ❌ 錯誤 1: Body 格式錯誤
**錯誤設定:**
```
Body Parameters:
query = {{ $json.query }}
limit = {{ $json.limit }}
```
**正確設定:**
```
Content Type: JSON
Body:
{
"query": "={{ $json.query }}",
"limit": "={{ $json.limit }}"
}
```
### ❌ 錯誤 2: 缺少引號
**錯誤:**
```json
{
query: "charade",
limit: 3
}
```
**正確:**
```json
{
"query": "charade",
"limit": 3
}
```
### ❌ 錯誤 3: URL 錯誤
**錯誤:**
```
URL: http://localhost:3002/api/v1/n8n/search
```
**正確:**
```
URL: https://api.momentry.ddns.net/api/v1/n8n/search
```
---
## 測試步驟
### 步驟 1: 創建最簡單的測試
1. 新建工作流程
2. 添加 **Manual Trigger** Node
3. 添加 **HTTP Request** Node
4. 設定如下:
- URL: `https://api.momentry.ddns.net/api/v1/n8n/search`
- Method: POST
- Send Body: ✓
- Content Type: JSON
- Body: `{"query": "charade", "limit": 2}`
### 步驟 2: 執行測試
1. 點擊 **Execute Workflow**
2. 查看 HTTP Request Node 的輸出
3. 應該看到 JSON 回應
### 步驟 3: 確認成功
成功的回應應該包含:
```json
{
"query": "charade",
"count": 2,
"hits": [...]
}
```
---
## 直接複製使用的工作流程 JSON
```json
{
"name": "Video Search - Working Example",
"nodes": [
{
"parameters": {},
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": {
"query": "charade",
"limit": 3
},
"options": {}
},
"name": "Search Video API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [450, 300]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Search Video API",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
```
**導入方式:**
1. 在 n8n UI 中,點擊左上角的 Menu
2. 選擇 **Import from File**
3. 選擇上面的 JSON 文件
---
## 驗證 API 可用性
在終端機測試:
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":2}'
```
如果 curl 成功但 n8n 失敗,問題在於 n8n HTTP Request Node 的設定。
---
## 需要幫助?
如果仍然無法工作:
1. 開啟工作流程
2. 點擊 HTTP Request Node
3. 點擊右上角的 **Execute Node** 單獨執行
4. 查看錯誤訊息的詳細內容
5. 檢查 Network tab 中的 request/response
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 快速使用指南
- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明

View File

@@ -0,0 +1,562 @@
# Momentry n8n 整合使用手冊
| 項目 | 內容 |
|------|------|
| 版本 | V1.1 |
| 日期 | 2026-03-23 |
| 目標讀者 | n8n 使用者、DevOps |
---
## 目錄
1. [概述](#1-概述)
2. [前置作業](#2-前置作業)
3. [建立 n8n API Key](#3-建立-n8n-api-key)
4. [在 n8n 中使用 Momentry API](#4-在-n8n-中使用-momentry-api)
5. [工作流範例](#5-工作流範例)
6. [常見問題](#6-常見問題)
---
## 1. 概述
### 1.1 什麼是 n8n
n8n 是一個開源的工作流自動化工具,可以連接各種服務和 API。
### 1.2 為什麼需要整合?
| 場景 | 說明 |
|------|------|
| 自動化影片處理 | 新影片上傳時自動觸發處理流程 |
| 監控告警 | API Key 異常時發送通知 |
| 定時備份 | 定期備份 API Key 資料 |
| 跨系統同步 | 與其他系統同步 API Key 狀態 |
### 1.3 架構圖
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 觸發器 │────▶│ n8n 工作流 │────▶│ Momentry │
│ (Webhook/ │ │ (處理邏輯) │ │ API │
│ Cron) │ └─────────────┘ └─────────────┘
└─────────────┘ │ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 通知 │ │ 動作 │
│ (Slack/Email)│ │ (建立/查詢) │
└─────────────┘ └─────────────┘
```
---
### 1.4 API URL 選擇
| 環境 | URL | 說明 |
|------|-----|------|
| **本地測試** | `http://localhost:3002` | 直接訪問 API |
| **n8n workflow** | `https://api.momentry.ddns.net` | 通過反向代理 |
> ⚠️ **重要**: 在 n8n HTTP Request Node 中,請使用 `https://api.momentry.ddns.net` 而非 `localhost:3002`,因為 n8n 需要從外部訪問 API。
**本地測試時**:
```bash
curl http://localhost:3002/health
```
**n8n Workflow 中**:
```
URL: https://api.momentry.ddns.net/api/v1/n8n/search
```
---
## 2. 前置作業
### 2.1 確認服務狀態
```bash
# 檢查 Momentry API
curl http://localhost:3002/health
# 檢查 n8n
curl https://n8n.momentry.ddns.net/api/v1/workflows \
-H "X-N8N-API-KEY: your-api-key"
```
### 2.2 取得 n8n API Key
#### 方式 A: 透過 Momentry CLI
```bash
# 建立 n8n API Key
momentry n8n create \
--api-key "your-existing-n8n-api-key" \
--label "momentry-integration" \
--expires-in-days 90
# 輸出:
# ✅ n8n API Key created successfully!
# API Key: eyJhbGciOiJIUzI1NiIs...
```
#### 方式 B: 透過 n8n 介面
1. 登入 n8n: https://n8n.momentry.ddns.net
2. 前往 Settings → n8n API
3. 點擊「Create an API Key」
4. 複製 API Key
### 2.3 設定環境變數
在 n8n 中設定以下環境變數:
| 變數名稱 | 值 | 說明 |
|----------|-----|------|
| `MOMENTRY_API_URL` | `http://localhost:3002` | Momentry API URL |
| `MOMENTRY_API_KEY` | `your-api-key` | Momentry API Key |
---
## 3. 建立 n8n API Key
### 3.1 CLI 命令
```bash
# 基本建立
momentry n8n create \
--api-key <existing-n8n-key> \
--label <key-name>
# 設定過期時間
momentry n8n create \
--api-key <existing-n8n-key> \
--label "ci-pipeline" \
--expires-in-days 30
```
### 3.2 範例:建立監控用 Key
```bash
momentry n8n create \
--api-key "n8n_api_1234567890" \
--label "monitoring-key" \
--expires-in-days 180
# 輸出:
# ✅ n8n API Key created successfully!
# Key ID: abc123-def456
# Label: monitoring-key
# API Key: eyJhbGciOiJIUz...
```
### 3.3 列出已建立的 Key
```bash
momentry n8n list --api-key <existing-n8n-key>
# 輸出:
# 📋 n8n API Keys
# ┌────────────────────────────────────────────────────────────────────────────┐
# │ Label │ ID │
# ├────────────────────────────────────────────────────────────────────────────┤
# │ monitoring-key │ abc123-def456 │
# │ ci-pipeline │ xyz789-abc123 │
# └────────────────────────────────────────────────────────────────────────────┘
```
---
## 4. 在 n8n 中使用 Momentry API
> ⚠️ **注意**: API Key 管理端點 (`/api/v1/api-keys/*`) 目前仍在規劃中,尚未實作。以下為規劃中的 API 說明。
### 4.1 設定 HTTP Request 節點
```
Method: POST
URL: {{ $env.MOMENTRY_API_URL }}/api/v1/api-keys
Headers:
X-API-Key: {{ $env.MOMENTRY_API_KEY }}
Content-Type: application/json
Body:
{
"name": "auto-generated-key",
"key_type": "service",
"ttl_days": 90
}
```
### 4.2 可用的 API 端點
> ⚠️ **API Key 管理端點為規劃功能,目前尚未實作**
#### 已實作端點
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 健康檢查 |
| POST | `/api/v1/search` | 語意搜尋 |
| POST | `/api/v1/n8n/search` | n8n 格式搜尋 |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片 |
| GET | `/api/v1/progress/:uuid` | 處理進度 |
#### 規劃中端點 *(尚未實作)*
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/api/v1/api-keys` | 列出所有 API Keys |
| POST | `/api/v1/api-keys` | 建立新的 API Key |
| DELETE | `/api/v1/api-keys/{id}` | 刪除 API Key |
| POST | `/api/v1/api-keys/{id}/rotate` | 請求 Key 輪換 |
---
## 5. 工作流範例
### 範例 1定時檢查 API Key 狀態
**目的**: 每天早上 9 點檢查即將過期的 API Keys
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Schedule │────▶│ HTTP │────▶│ Filter │────▶│ Slack │
│ (每天 9AM) │ │ Request │ │ (7天內過期) │ │ 通知 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
**HTTP Request 設定**:
```json
{
"method": "GET",
"url": "{{ $env.MOMENTRY_API_URL }}/api/v1/api-keys",
"headers": {
"X-API-Key": "{{ $env.MOMENTRY_API_KEY }}"
}
}
```
**Filter 設定**:
```javascript
// 檢查是否在 7 天內過期
const expiresAt = new Date($json.expires_at);
const now = new Date();
const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
return expiresAt <= sevenDaysLater && expiresAt > now;
```
**Slack 通知格式**:
```
⚠️ API Key 即將過期提醒
以下 API Key 將在 7 天內過期:
{{ $json.map(k => `• ${k.name} (${k.key_id}) - 過期時間: ${k.expires_at}`).join('\n') }}
請及時處理!
```
---
### 範例 2新影片上傳時自動建立 API Key
**目的**: 當有新影片上傳時,自動為該影片建立專用 API Key
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Webhook │────▶│ HTTP │────▶│ Set │────▶│ Respond │
│ 觸發器 │ │ Request │ │ (組裝回應) │ │ Webhook │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
**Webhook 設定**:
```
Method: POST
Path: /new-video
```
**HTTP Request 設定**:
```json
{
"method": "POST",
"url": "{{ $env.MOMENTRY_API_URL }}/api/v1/api-keys",
"headers": {
"X-API-Key": "{{ $env.MOMENTRY_API_KEY }}"
},
"body": {
"name": "video-{{ $json.video_id }}",
"key_type": "service",
"permissions": ["read"],
"ttl_days": 30
}
}
```
**回應格式**:
```json
{
"success": true,
"video_id": "{{ $json.video_id }}",
"api_key_id": "{{ $json.key_id }}",
"message": "API Key 已建立"
}
```
---
### 範例 3API Key 異常告警
**目的**: 監控 API Key 異常使用,發送告警通知
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Webhook │────▶│ Switch │────▶│ Email │ │ Slack │
│ (異常通知) │ │ (嚴重程度) │────▶│ 通知 │ │ 通知 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
**Webhook 觸發設定** (在 Momentry 中):
```bash
export WEBHOOK_URL="https://n8n.momentry.ddns.net/webhook/anomaly-alert"
export WEBHOOK_EVENTS="anomaly_detected,rate_limited,ip_blocked"
```
**Switch 節點設定**:
```javascript
// 根據嚴重程度分流
switch ($json.data.severity) {
case 'critical':
return 0; // Email + Slack
case 'high':
return 1; // Slack only
default:
return 2; // Log only
}
```
**Email 通知格式**:
```
Subject: [{{ $json.data.severity }}] API Key 異常告警 - {{ $json.data.key_id }}
Dear Admin,
偵測到 API Key 異常使用:
Key ID: {{ $json.data.key_id }}
異常類型: {{ $json.data.anomaly_type }}
嚴重程度: {{ $json.data.severity }}
時間: {{ $json.timestamp }}
請立即檢查系統狀態。
Best regards,
Momentry Monitoring
```
---
### 範例 4定時備份 API Key 統計
**目的**: 每週一早上備份 API Key 統計報表
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Schedule │────▶│ HTTP │────▶│ Google │
│ (每週一) │ │ Request │ │ Sheets │
└─────────────┘ └─────────────┘ └─────────────┘
```
**HTTP Request 設定**:
```json
{
"method": "GET",
"url": "{{ $env.MOMENTRY_API_URL }}/api/v1/api-keys/stats",
"headers": {
"X-API-Key": "{{ $env.MOMENTRY_API_KEY }}"
}
}
```
**Google Sheets 設定**:
```
Spreadsheet: Momentry API Key Reports
Sheet: Weekly Stats
Append Row:
- Date: {{ $now }}
- Total Keys: {{ $json.total_keys }}
- Active Keys: {{ $json.active_keys }}
- Expired Keys: {{ $json.expired_keys }}
- Anomalies (24h): {{ $json.anomalies_last_24h }}
```
---
### 範例 5自動輪換即將過期的 Key
**目的**: 自動為即將過期的 Key 請求輪換
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Schedule │────▶│ HTTP │────▶│ Filter │────▶│ HTTP │
│ (每天) │ │ (取得 Keys) │ │ (即將過期) │ │ (請求輪換) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
**第一個 HTTP Request (取得 Keys)**:
```json
{
"method": "GET",
"url": "{{ $env.MOMENTRY_API_URL }}/api/v1/api-keys",
"headers": {
"X-API-Key": "{{ $env.MOMENTRY_API_KEY }}"
}
}
```
**Filter 設定**:
```javascript
// 14 天內過期且未請求輪換的 Key
const expiresAt = new Date($json.expires_at);
const now = new Date();
const fourteenDaysLater = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
return expiresAt <= fourteenDaysLater &&
!$json.rotation_required &&
$json.status === 'active';
```
**第二個 HTTP Request (請求輪換)**:
```json
{
"method": "POST",
"url": "{{ $env.MOMENTRY_API_URL }}/api/v1/api-keys/{{ $json.key_id }}/rotate",
"headers": {
"X-API-Key": "{{ $env.MOMENTRY_API_KEY }}"
},
"body": {
"reason": "auto_rotation_approaching_expiry"
}
}
```
---
## 6. 常見問題
### Q1: n8n 無法連接到 Momentry API
**檢查項目**:
```bash
# 1. 確認 API 是否運行
curl http://localhost:3002/health
# 2. 確認 API Key 是否有效
momentry api-key validate --key "your-key"
# 3. 檢查防火牆設定
nc -zv localhost 3002
```
### Q2: API Key 建立失敗
**可能原因**:
- API Key 已存在
- 權限不足
- 格式錯誤
**解決方式**:
```bash
# 檢查現有 Keys
momentry api-key list
# 使用不同的 label
momentry n8n create --api-key "..." --label "unique-label-$(date +%s)"
```
### Q3: Webhook 通知沒有收到
**檢查項目**:
```bash
# 1. 確認 Webhook URL 設定
echo $WEBHOOK_URL
# 2. 測試 Webhook
curl -X POST $WEBHOOK_URL \
-H "Content-Type: application/json" \
-d '{"test": true}'
# 3. 檢查 n8n 工作流是否啟用
# 登入 n8n → 檢查工作流狀態
```
### Q4: 如何撤銷 n8n API Key
```bash
# 列出所有 Keys
momentry n8n list --api-key "your-admin-key"
# 刪除指定 Key
momentry n8n delete \
--api-key "your-admin-key" \
--label "key-to-delete"
```
---
## 附錄
### A. n8n 工作流匯出
將上述範例工作流匯入 n8n
1. 複製工作流 JSON
2. 登入 n8n
3. 點擊「+」→「Import from File」
4. 貼上 JSON 並儲存
### B. 環境變數總覽
```bash
# Momentry
MOMENTRY_API_URL=http://localhost:3002
MOMENTRY_API_KEY=your-api-key
# n8n
N8N_URL=https://n8n.momentry.ddns.net
N8N_API_KEY=your-n8n-api-key
# Webhook
WEBHOOK_URL=https://n8n.momentry.ddns.net/webhook/alerts
WEBHOOK_SECRET=your-secret
WEBHOOK_EVENTS=anomaly_detected,key_expired,key_revoked
```
### C. 相關文件
| 文件 | 說明 |
|------|------|
| [API_INDEX.md](./API_INDEX.md) | 文件總覽(起點) |
| [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) | n8n 快速使用指南 |
| `docs/API_KEY_MANAGEMENT.md` | API Key 管理系統設計 |
| `docs/API_KEY_ARCHITECTURE.md` | 系統架構圖 |
| `docs/API_KEY_INTEGRATION_TESTS.md` | 整合測試文件 |

211
docs/N8N_MCP_SETUP.md Normal file
View File

@@ -0,0 +1,211 @@
# OpenCode n8n MCP 整合設定
> 建立時間: 2026-03-23
> 更新時間: 2026-03-23
---
## n8n MCP 工具列表 (43 個)
### Workflows (10)
| 工具 | 說明 |
|------|------|
| `n8n_list_workflows` | 列出所有 workflows |
| `n8n_get_workflow` | 取得 workflow 詳情 |
| `n8n_create_workflow` | 建立新 workflow |
| `n8n_update_workflow` | 更新 workflow |
| `n8n_delete_workflow` | 刪除 workflow |
| `n8n_activate_workflow` | 啟用 workflow |
| `n8n_deactivate_workflow` | 停用 workflow |
| `n8n_execute_workflow` | 執行 workflow |
| `n8n_get_workflow_tags` | 取得 workflow 標籤 |
| `n8n_update_workflow_tags` | 更新 workflow 標籤 |
### Executions (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_executions` | 列出執行記錄 |
| `n8n_get_execution` | 取得執行詳情 |
| `n8n_delete_execution` | 刪除執行記錄 |
### Data Tables (8)
| 工具 | 說明 |
|------|------|
| `n8n_list_datatables` | 列出資料表 |
| `n8n_create_datatable` | 建立資料表 |
| `n8n_get_datatable` | 取得資料表結構 |
| `n8n_get_datatable_rows` | 取得資料表列 |
| `n8n_insert_datatable_rows` | 插入資料列 |
| `n8n_update_datatable_rows` | 更新資料列 |
| `n8n_upsert_datatable_row` | 插入或更新資料列 |
| `n8n_delete_datatable_rows` | 刪除資料列 |
### Tags (5)
| 工具 | 說明 |
|------|------|
| `n8n_list_tags` | 列出所有標籤 |
| `n8n_get_tag` | 取得標籤 |
| `n8n_create_tag` | 建立標籤 |
| `n8n_update_tag` | 更新標籤 |
| `n8n_delete_tag` | 刪除標籤 |
### Credentials (4)
| 工具 | 說明 |
|------|------|
| `n8n_list_credentials` | 列出憑證 |
| `n8n_create_credential` | 建立憑證 |
| `n8n_delete_credential` | 刪除憑證 |
| `n8n_get_credential_schema` | 取得憑證 schema |
### Users (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_users` | 列出使用者 |
| `n8n_get_user` | 取得使用者 |
| `n8n_delete_user` | 刪除使用者 |
### Variables (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_variables` | 列出變數 |
| `n8n_create_variable` | 建立變數 |
| `n8n_delete_variable` | 刪除變數 |
### 其他 (7)
| 工具 | 說明 |
|------|------|
| `n8n_list_projects` | 列出專案 |
| `n8n_create_project` | 建立專案 |
| `n8n_update_project` | 更新專案 |
| `n8n_delete_project` | 刪除專案 |
| `n8n_generate_audit` | 產生安全審計報告 |
| `n8n_health_check` | 健康檢查 |
| `n8n_trigger_webhook` | 觸發 webhook |
---
## 安裝步驟
### 1. 安裝 n8n MCP Server
```bash
npm install -g @nextoolsolutions/mcp-n8n
```
驗證:
```bash
which mcp-n8n
# /opt/homebrew/bin/mcp-n8n
```
### 2. 取得 n8n API Key
1. 開啟 n8n UI: `http://localhost:5678`
2. 登入後點擊右上角 **Settings****API**
3. 點擊 **Create New API Key**
4. 複製產生的 key
### 3. 設定 OpenCode MCP 設定檔
建立或編輯 `~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
### 4. 驗證 MCP 運作
重啟 OpenCode確認 n8n MCP tools 可用。
---
## n8n API 端點
n8n v2 REST API 路徑為 `/rest/`(不是 `/api/v1/`
| 端點 | 方法 | 說明 |
|------|------|------|
| `/rest/workflows` | GET | 列出 workflows |
| `/rest/workflows/:id` | GET | 取得 workflow |
| `/rest/workflows` | POST | 建立 workflow |
| `/rest/workflows/:id` | PUT | 更新 workflow |
| `/rest/workflows/:id` | DELETE | 刪除 workflow |
| `/rest/workflows/:id/activate` | POST | 啟用 workflow |
| `/rest/workflows/:id/deactivate` | POST | 停用 workflow |
| `/rest/workflows/:id/execute` | POST | 執行 workflow |
**認證方式:**
```bash
curl -H "X-N8N-API-KEY: YOUR_API_KEY" \
http://localhost:5678/rest/workflows
```
---
## 疑難排解
### API 404 問題
如果 API 傳回 404檢查
1. **n8n 是否運行中**
```bash
curl http://localhost:5678
```
2. **n8n 初始設定(重要!)**
- 第一次使用必須在瀏覽器完成初始化
- 開啟 `http://localhost:5678`
- 按照畫面指示建立管理員帳號
- 完成後才能使用 API
3. **API Key 是否正確**
```bash
curl -H "X-N8N-API-KEY: YOUR_KEY" \
http://localhost:5678/rest/workflows
```
### n8n 初始設定(第一次使用)
1. 開啟瀏覽器: `http://localhost:5678`
2. 輸入 email 和密碼建立管理員帳號
3. 完成後進入 Settings → API
4. 建立 API Key 並複製
### CLI Import vs PostgreSQL
n8n 使用 PostgreSQL 儲存資料:
- CLI `n8n import:workflow` 可能寫入 SQLite
- 手動在 UI import 會寫入 PostgreSQL
建議直接使用 UI 或 MCP import。
---
## 相關文件
- [OPENCODE_GUIDE.md](./OPENCODE_GUIDE.md) - OpenCode 使用規範
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南
- [N8N_DEMO_WORKFLOW.md](./N8N_DEMO_WORKFLOW.md) - n8n Workflow 範例

178
docs/N8N_MCP_TEST_REPORT.md Normal file
View File

@@ -0,0 +1,178 @@
# n8n MCP 整合測試報告
## 測試日期
2026-03-23
## 測試環境
- **n8n Version**: 2.3.5
- **n8n URL**: http://localhost:5678
- **MCP Server**: @nextoolsolutions/mcp-n8n v2.0.0
- **OpenCode Config**: ~/.config/opencode/opencode.json
## 測試結果
### ✅ 所有測試通過
| 測試項目 | 狀態 | 詳細說明 |
|---------|------|---------|
| MCP 伺服器初始化 | ✅ 通過 | Protocol version 2024-11-05 |
| 工具列表載入 | ✅ 通過 | 43 個工具可用 |
| 工具呼叫 (list_workflows) | ✅ 通過 | 成功返回 5 個 workflows |
| API 連線 | ✅ 通過 | http://localhost:5678 |
## 可用工具 (43 個)
### Workflows (10)
- `n8n_list_workflows` - 列出所有工作流程
- `n8n_get_workflow` - 取得工作流程詳情
- `n8n_create_workflow` - 建立新工作流程
- `n8n_update_workflow` - 更新工作流程
- `n8n_delete_workflow` - 刪除工作流程
- `n8n_activate_workflow` - 啟動工作流程
- `n8n_deactivate_workflow` - 停止工作流程
- `n8n_execute_workflow` - 執行工作流程
- `n8n_get_workflow_tags` - 取得工作流程標籤
- `n8n_update_workflow_tags` - 更新工作流程標籤
### Executions (3)
- `n8n_list_executions` - 列出執行記錄
- `n8n_get_execution` - 取得執行詳情
- `n8n_delete_execution` - 刪除執行記錄
### Data Tables (6)
- `n8n_list_datatables` - 列出資料表
- `n8n_create_datatable` - 建立資料表
- `n8n_get_datatable` - 取得資料表
- `n8n_get_datatable_rows` - 查詢資料表資料
- `n8n_insert_datatable_rows` - 插入資料
- `n8n_update_datatable_rows` - 更新資料
- `n8n_upsert_datatable_row` - 更新或插入
- `n8n_delete_datatable_rows` - 刪除資料
### Tags (5)
- `n8n_list_tags` - 列出標籤
- `n8n_get_tag` - 取得標籤
- `n8n_create_tag` - 建立標籤
- `n8n_update_tag` - 更新標籤
- `n8n_delete_tag` - 刪除標籤
### Credentials (4)
- `n8n_list_credentials` - 列出認證
- `n8n_create_credential` - 建立認證
- `n8n_delete_credential` - 刪除認證
- `n8n_get_credential_schema` - 取得認證結構
### Users (3)
- `n8n_list_users` - 列出使用者
- `n8n_get_user` - 取得使用者
- `n8n_delete_user` - 刪除使用者
### Variables (3)
- `n8n_list_variables` - 列出變數
- `n8n_create_variable` - 建立變數
- `n8n_delete_variable` - 刪除變數
### Projects (4)
- `n8n_list_projects` - 列出專案
- `n8n_create_project` - 建立專案
- `n8n_update_project` - 更新專案
- `n8n_delete_project` - 刪除專案
### System (3)
- `n8n_generate_audit` - 產生安全稽核報告
- `n8n_health_check` - 健康檢查
- `n8n_trigger_webhook` - 觸發 Webhook
## 配置檔案
### ~/.config/opencode/opencode.json
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
## 測試範例
### 列出工作流程
```bash
# 使用 curl
curl -H "X-N8N-API-KEY: <API_KEY>" http://localhost:5678/api/v1/workflows
# 使用 MCP
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"n8n_list_workflows","arguments":{"limit":5}}}' | mcp-n8n
```
### 建立工作流程
```bash
# 使用 curl
curl -X POST \
-H "Content-Type: application/json" \
-H "X-N8N-API-KEY: <API_KEY>" \
-d '{"name":"My Workflow","nodes":[],"connections":{}}' \
http://localhost:5678/api/v1/workflows
# 使用 MCP
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"n8n_create_workflow","arguments":{"workflow":{"name":"My Workflow","nodes":[],"connections":{}}}}' | mcp-n8n
```
## 現有工作流程 (範例)
測試中成功讀取的工作流程:
1. **Diagnostic: Environment Test** (ID: 4vaf6dKznkTccuyC)
- 狀態: Active
- 用途: 環境測試與時間同步驗證
2. **Simple Test Webhook v2** (ID: 38bbM14sGo0eVCuW)
- 狀態: Active
- 用途: Webhook 測試
3. **HL Chat Searching - RAG Only** (ID: 6Y9c7mGtye4DjuENR5Kbg)
- 狀態: Inactive
- 用途: RAG 聊天搜尋整合
4. **HL Embedding with AccusysDB** (ID: 61nRs3BeNGlBtuYJFLSFn)
- 狀態: Inactive
- 用途: Qdrant 向量資料庫嵌入
5. **HL Embedding with AccusysDB (local)** (ID: 017oYPE7cDpvybAn)
- 狀態: Archived
- 用途: 本地測試版本
## 結論
**n8n MCP 整合測試全部通過!**
MCP 伺服器已成功配置並運作,可以透過 OpenCode 使用所有 43 個 n8n 管理工具。
### 建議用途
1. **自動化工作流程管理** - 使用 AI 協助建立、修改、監控工作流程
2. **批次執行** - 透過 MCP 批量管理工作流程
3. **監控與稽核** - 自動化執行記錄檢視與安全稽核
4. **整合測試** - 與 Momentry Core Video RAG 整合測試
### 下一步
- 使用 OpenCode 建立 Video RAG 整合工作流程
- 設定自動化監控與告警
- 建立工作流程模板庫

152
docs/N8N_SETUP_COMPLETE.md Normal file
View File

@@ -0,0 +1,152 @@
# Momentry Video RAG - n8n 工作流程設定完成
## ✅ 最終成功版本
| 項目 | 內容 |
|------|------|
| **工作流程名稱** | Video Search - Working v3 |
| **ID** | 4vQo8I4SXEaR5E1A |
| **狀態** | ✅ SUCCESS |
| **執行 ID** | 1620 |
---
## 成功關鍵
### HTTP Request Node 正確設定
```json
{
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {}
}
```
**重點**:
-`specifyBody`: "json" (不是 "body")
-`jsonBody`: 字串格式 (不是物件)
- ✅ 使用 `"{\"query\":\"..."}` 轉義引號
---
## 所有可用工作流程
| 工作流程 | ID | 狀態 | 說明 |
|---------|-----|------|------|
| Video Search - Working v3 | 4vQo8I4SXEaR5E1A | ✅ 成功 | **推薦使用** |
| Video Search - HTTP Only | tZbljQCFZDOJ4C0s | ❌ 失敗 | body 格式錯誤 |
| Video Search - Debug Simple | e2CMjonwILMUYjp0 | ⚠️ 待測 | Code Node 版本 |
| Video Search - Instant | zC5K3TbFzWGAh0la | ❌ 失敗 | `$httpRequest` 不可用 |
---
## 如何使用
### 方法 1: 直接執行
```bash
# 開啟工作流程
open https://n8n.momentry.ddns.net/workflow/4vQo8I4SXEaR5E1A
```
然後:
1. 點擊 **"Execute Workflow"** ▶️
2. 點擊 **"Show Result"** 節點
3. 查看 JSON 結果
### 方法 2: 修改搜尋關鍵字
1. 點擊 **"Search API"** 節點
2. 修改 `jsonBody`:
```json
"{\"query\":\"您的關鍵字\",\"limit\":5}"
```
3. 儲存並重新執行
---
## API 端點
### Momentry Core API
```
POST https://api.momentry.ddns.net/api/v1/n8n/search
Content-Type: application/json
Body:
{
"query": "charade",
"limit": 3,
"uuid": "可選的影片UUID"
}
```
### 直接測試
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
```
---
## 已建立的文件
| 文件 | 路徑 | 內容 |
|------|------|------|
| API URL 範例 | `docs/API_URL_EXAMPLES.md` | 完整 URL 和 curl 指令 |
| HTTP Request 指南 | `docs/N8N_HTTP_REQUEST_GUIDE.md` | Node 設定說明 |
| 輸出查看指南 | `docs/N8N_VIEW_OUTPUT_GUIDE.md` | 如何查看結果 |
| MCP 測試報告 | `docs/N8N_MCP_TEST_REPORT.md` | 43 個 MCP 工具 |
| API 修復總結 | `docs/N8N_API_FIX_SUMMARY.md` | 問題修復過程 |
| 工作流程 JSON | `docs/n8n_workflow_video_rag_mcp.json` | 原始工作流程 |
| 測試腳本 | `docs/test_all.sh` | 自動測試腳本 |
---
## 服務狀態
✅ **Momentry Core**: https://api.momentry.ddns.net (Port 3002)
✅ **n8n**: https://n8n.momentry.ddns.net (Port 5678)
✅ **MCP 整合**: 43 個工具可用
---
## 下一步建議
### 1. 建立帶有參數的工作流程
修改現有工作流程,讓 query 和 limit 可以動態輸入:
- 添加 Webhook Node 接收外部請求
- 或使用 Set Node 設定變數
### 2. 建立完整的 RAG 流程
結合 OpenAI
- 搜尋影片片段
- 使用 GPT 生成回答
- 回傳格式化的 RAG 結果
### 3. 自動化監控
- 建立定時執行的工作流程
- 監控 API 健康狀態
- 發送 Telegram/Email 通知
---
## 問題排除
如果再次遇到 "Your request is invalid"
1. 檢查 `specifyBody` 必須設為 `"json"`
2. `jsonBody` 必須是字串格式,不是物件
3. 確保使用正確的 JSON 轉義: `{\"key\":\"value\"}`
---
## 完成!🎉
所有設定已完成:
- ✅ n8n REST API 修復並運作正常
- ✅ MCP 整合完成 (43 個工具)
- ✅ Momentry Core API 可外部存取
- ✅ 成功的工作流程已創建並測試
您可以開始使用 n8n 自動化管理 Momentry Core 了!

View File

@@ -0,0 +1,279 @@
# n8n Video Search 工作流程 - 成功設定指南
> 建立時間: 2026-03-22
> 適用版本: n8n 2.3.5
---
## ✅ 成功案例
| 項目 | 內容 |
|------|------|
| **工作流程名稱** | Video Search - Working v3 |
| **ID** | 4vQo8I4SXEaR5E1A |
| **狀態** | ✅ 成功運作 |
| **測試結果** | 成功搜尋 "charade",返回 3 個結果 |
---
## 正確的 HTTP Request Node 設定
### 成功的設定方式
```json
{
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {}
}
```
### 關鍵設定說明
| 設定項目 | 正確值 | 錯誤值 | 說明 |
|---------|--------|--------|------|
| **specifyBody** | `"json"` | `"body"` | 必須選擇 `"json"` |
| **jsonBody** | 字串 `"{...}"` | 物件 `{}` | 必須是 JSON 字串格式 |
| **轉義** | `\"query\"` | `"query"` | 引號需要轉義 |
---
## 工作流程架構
```
┌─────────────────────────┐
│ Manual Trigger │ ← 點擊 "Execute Workflow"
└───────────┬─────────────┘
┌─────────────────────────┐
│ HTTP Request Node │ ← 呼叫 Momentry API
│ - specifyBody: "json" │
│ - jsonBody: "{...}" │
└───────────┬─────────────┘
┌─────────────────────────┐
│ Code Node │ ← 格式化輸出
│ - console.log() │
│ - return json │
└─────────────────────────┘
```
---
## 完整的 Workflow JSON
```json
{
"name": "Video Search - Working v3",
"nodes": [
{
"parameters": {},
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {}
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [450, 300]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nconsole.log('Response:', JSON.stringify(data, null, 2));\nreturn [{ json: data }];"
},
"name": "Show Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [650, 300]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [[{"node": "Search API", "type": "main", "index": 0}]]
},
"Search API": {
"main": [[{"node": "Show Result", "type": "main", "index": 0}]]
}
},
"settings": {"executionOrder": "v1"},
"staticData": null
}
```
---
## 使用步驟
### 步驟 1: 導入工作流程
1. 開啟 n8n UI: `https://n8n.momentry.ddns.net`
2. 點擊 **Add Workflow** (+)
3. 點擊 **Import from File**
4. 選擇上面的 JSON 檔案
### 步驟 2: 執行測試
1. 點擊 **"Execute Workflow"** 按鈕 (▶️)
2. 等待執行完成
3. 點擊 **"Show Result"** 節點
4. 查看右側 **JSON** 面板
### 步驟 3: 修改搜尋關鍵字
1. 點擊 **"Search API"** 節點
2. 修改 `jsonBody`:
```json
"{\"query\":\"您的關鍵字\",\"limit\":5}"
```
3. 點擊 **Save** (Ctrl+S)
4. 重新執行
---
## 成功的回應範例
```json
{
"query": "charade",
"count": 3,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## 常見錯誤與解決
### ❌ 錯誤 1: "Your request is invalid"
**原因**: `specifyBody` 設為 `"body"` 而不是 `"json"`
**解決**:
```json
✅ "specifyBody": "json"
❌ "specifyBody": "body"
```
### ❌ 錯誤 2: "$httpRequest is not defined"
**原因**: Code Node 中使用 `$httpRequest`,但您的 n8n 版本不支援
**解決**: 使用 **HTTP Request Node** 代替 Code Node
### ❌ 錯誤 3: Body 格式錯誤
**原因**: `body` 使用物件格式 `{query: "..."}`
**解決**: 使用 `jsonBody` 字串格式 `{"query":"..."}`
### ❌ 錯誤 4: JSON 引號未轉義
**原因**: `"{query: "charade"}"` - 引號衝突
**解決**: `\"` 轉義 `"{\"query\":\"charade\"}"`
---
## 測試指令
### 直接測試 API
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}'
```
### 驗證服務狀態
```bash
# 檢查 Momentry Core
curl https://api.momentry.ddns.net/api/v1/videos
# 檢查 n8n
curl http://localhost:5678/api/v1/workflows \
-H "X-N8N-API-KEY: <your_api_key>"
```
---
## 服務資訊
| 服務 | URL | 說明 |
|------|-----|------|
| **n8n UI** | https://n8n.momentry.ddns.net | 工作流程管理 |
| **Momentry API** | https://api.momentry.ddns.net | 影片搜尋 API |
| **工作流程** | https://n8n.momentry.ddns.net/workflow/4vQo8I4SXEaR5E1A | 成功案例 |
---
## 進階使用
### 添加 Webhook 觸發器
如果你想從外部呼叫這個工作流程:
1. 在第一個節點前添加 **Webhook** Node
2. 設定:
```
Method: POST
Path: video-search
Response Mode: Last Node
```
3. 將 Webhook 連接到 Search API
4. 儲存並執行
5. 使用生成的 Webhook URL 呼叫:
```bash
curl -X POST <webhook_url> \
-d '{"query":"charade","limit":3}'
```
### 使用動態變數
修改 jsonBody 使用表達式:
```json
"{\"query\":\"={{ $json.query }}\",\"limit\":{{ $json.limit }}}"
```
然後在前面添加 Set Node 設定變數。
---
## 相關文件
- `docs/N8N_SETUP_COMPLETE.md` - 完整設定總結
- `docs/N8N_HTTP_REQUEST_GUIDE.md` - HTTP Request 詳細指南
- `docs/API_URL_EXAMPLES.md` - API URL 範例
---
## 完成!🎉
您現在擁有一個可以成功搜尋影片的 n8n 工作流程!
**關鍵成功要素**:
1. ✅ 使用 `specifyBody: "json"`
2. ✅ 使用 `jsonBody` 字串格式
3. ✅ 正確轉義 JSON 引號
4. ✅ 使用外部 API URL (`https://api.momentry.ddns.net`)

View File

@@ -0,0 +1,141 @@
# n8n 工作流程輸出查看指南
## 問題:"Node executed successfully but no output data"
這是正常的!在 n8n 中,你需要**點擊節點**才能看到輸出資料。
---
## 如何查看輸出資料
### 方法 1: 點擊節點查看
1. 執行工作流程後(點擊 Execute Workflow
2. **點擊任何一個節點**Node
3. 在右側面板會顯示該節點的輸出
4. 查看 **JSON** 分頁看到完整資料
### 方法 2: 查看執行記錄
1. 執行工作流程
2. 點擊左側的 **"Executions"** 選單
3. 找到最近的執行記錄
4. 點擊展開查看每個節點的輸出
### 方法 3: 使用 Respond to Webhook
如果你想直接看到結果,添加一個 Respond to Webhook 節點:
```javascript
// 在最後一個節點之後添加:
Node: Respond to Webhook
Response Mode: Last Node
Response Body: {{ JSON.stringify($json) }}
```
---
## 新增的工作流程
### ✅ Video Search - Debug Simple
- **ID**: e2CMjonwILMUYjp0
- **URL**: https://n8n.momentry.ddns.net/workflow/e2CMjonwILMUYjp0
這個版本保證有輸出!
### 執行步驟
1. 開啟工作流程
2. 點擊 **"Execute Workflow"** ▶️
3. 等待執行完成(約 3-5 秒)
4. **點擊最後一個節點** "Step 3 - Show Results"
5. 查看右側的 **JSON** 分頁
### 預期看到的輸出
```json
{
"status": "SUCCESS",
"query": "charade",
"totalResults": 2,
"firstHit": {
"text": "fun plot twists, Woody Dialog and charming perform...",
"time": "48.8s - 55.44s",
"score": "53%"
}
}
```
或如果失敗:
```json
{
"status": "FAILED",
"error": "error message here"
}
```
---
## 截圖說明
### 執行後的畫面
```
┌─────────────────────────────────────┐
│ When clicking "Execute Workflow" │ ✅ (綠色勾)
├─────────────────────────────────────┤
│ Step 1 - Set Query │ ✅ (綠色勾)
├─────────────────────────────────────┤
│ Step 2 - Call API │ ✅ (綠色勾) ← 點擊這裡
├─────────────────────────────────────┤
│ Step 3 - Show Results │ ✅ (綠色勾) ← 或這個
└─────────────────────────────────────┘
[右側面板 - 點擊節點後顯示]
┌─────────────────────────────────────┐
│ Node: Step 2 - Call API │
│ │
│ [JSON] [Table] [Schema] │ ← 點擊 JSON
│ │
│ { │
│ "success": true, │
│ "query": "charade", │
│ ... │
│ } │
└─────────────────────────────────────┘
```
---
## 快速測試
如果不想用瀏覽器,直接在這裡執行:
```bash
# 開啟簡化版工作流程
open https://n8n.momentry.ddns.net/workflow/e2CMjonwILMUYjp0
```
然後:
1. 點擊 **Execute Workflow**
2. 點擊 **"Step 3 - Show Results"** 節點
3. 看右側 JSON 面板
---
## 如果仍然看不到資料
檢查:
1. ✅ 工作流程已儲存
2. ✅ 點擊了正確的節點(有綠色勾的)
3. ✅ 右側面板已展開(點擊 JSON 分頁)
4. ✅ 沒有紅色錯誤訊息
---
## 聯絡支援
如果還是有問題,請告訴我:
1. 你點擊的是哪個節點?
2. 右側面板顯示什麼?
3. 有沒有紅色錯誤訊息?

View File

@@ -0,0 +1,169 @@
# Momentry Video RAG MCP Workflow
## 工作流程資訊
- **名稱**: Momentry Video RAG MCP
- **ID**: WlVvpX2OeKK83QOK
- **Webhook Path**: `video-rag-mcp`
- **狀態**: ✅ Active (已啟動)
- **建立時間**: 2026-03-22
## 工作流程架構
```
┌─────────────────┐ ┌──────────────────────┐ ┌───────────────────┐ ┌─────────────────┐
│ Webhook │────▶│ Search Momentry │────▶│ Process RAG │────▶│ Respond to │
│ Trigger │ │ Core │ │ Results │ │ Webhook │
└─────────────────┘ └──────────────────────┘ └───────────────────┘ └─────────────────┘
│ POST http://localhost:5678/webhook/video-rag-mcp
{
"query": "搜尋關鍵字",
"limit": 5,
"uuid": "可選的影片UUID"
}
```
## Node 說明
### 1. Webhook Trigger
- **類型**: Webhook
- **Method**: POST
- **Path**: `video-rag-mcp`
- **Response Mode**: Last Node (等待最後一個節點完成後回應)
### 2. Search Momentry Core
- **類型**: HTTP Request
- **URL**: `http://localhost:3002/api/v1/n8n/search`
- **Method**: POST
- **Body**:
```json
{
"query": "搜尋關鍵字",
"limit": 5,
"uuid": "可選的影片UUID"
}
```
- **Timeout**: 30秒
### 3. Process RAG Results
- **類型**: Code (JavaScript)
- **功能**:
- 處理 Momentry Core 搜尋結果
- 格式化 hits 為結構化資料
- 建立 RAG context用於 LLM 問答)
- 計算相關度百分比
**輸出格式**:
```json
{
"success": true,
"query": "搜尋關鍵字",
"totalFound": 5,
"context": "[1] 文本內容... (Video: 影片標題, Time: 10s-20s)\n\n[2] ...",
"results": [
{
"index": 1,
"id": "chunk_id",
"title": "影片標題",
"text": "文本內容",
"startTime": 10,
"endTime": 20,
"relevance": "85%",
"videoUuid": "uuid",
"mediaUrl": "影片URL",
"deepLink": "影片URL#t=10,20"
}
]
}
```
### 4. Respond to Webhook
- **類型**: Respond to Webhook
- **Response**: JSON 格式結果
- **Status Code**: 200
## 使用方式
### 直接呼叫 Webhook
```bash
curl -X POST http://localhost:5678/webhook/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{
"query": "charade",
"limit": 5
}'
```
### 指定特定影片搜尋
```bash
curl -X POST http://localhost:5678/webhook/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{
"query": "audrey hepburn",
"limit": 3,
"uuid": "a1b10138a6bbb0cd"
}'
```
### 在 n8n 工作流程中使用
可以將此 Webhook 作為子工作流程觸發器,或使用 HTTP Request Node 呼叫:
```json
{
"name": "Call Video RAG",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "http://localhost:5678/webhook/video-rag-mcp",
"method": "POST",
"body": {
"query": "={{ $json.searchTerm }}",
"limit": 5
}
}
}
```
## RAG Context 用途
工作流程產生的 `context` 欄位可直接用於 LLM 提示:
```javascript
// Example: 使用 context 進行問答
const prompt = `
基於以下影片片段資訊回答問題:
${context}
問題:${userQuestion}
請根據上述內容提供準確的答案。
`;
```
## 相關文件
- [Momentry Core API 文件](./API_ACCESS.md)
- [n8n MCP 測試報告](./N8N_MCP_TEST_REPORT.md)
- [N8N_DEMO_WORKFLOW.md](./N8N_DEMO_WORKFLOW.md) - 完整工作流程設計
## MCP 建立指令
此工作流程是透過 MCP 工具建立的:
```bash
# 使用 MCP 建立工作流程
node create_workflow.js | mcp-n8n
# 使用 MCP 啟動工作流程
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"n8n_activate_workflow","arguments":{"workflowId":"WlVvpX2OeKK83QOK"}}}' | mcp-n8n
```
## 工作流程檔案
- 原始檔案: `docs/n8n_workflow_video_rag_mcp.json`

View File

@@ -1,5 +1,21 @@
# Node.js 開發指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。
@@ -175,7 +191,8 @@ psql -U accusys -h localhost -d momentry -c "SELECT * FROM node_version_baseline
| 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 |
|------|-------------|----------|------|------|------|
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5690 | ✅ 執行中 | 工作流自動化平台 |
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5679 | ✅ 執行中 | 工作流自動化平台 |
| markdownlint-cli | 25.x | /opt/homebrew/bin/npm | - | ✅ 已安裝 | Markdown lint 工具 |
| - | - | - | - | - | 新增應用請填入此表 |
---

430
docs/OPENCODE_GUIDE.md Normal file
View File

@@ -0,0 +1,430 @@
# OpenCode 使用規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-21 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-21 | 創建文件 | Warren |
| V1.1 | 2026-03-21 | 新增 MCP 設定章節 | OpenCode |
---
## 概述
本文檔定義使用 OpenCode 進行專案開發的最佳實踐,確保開發效率和程式碼品質。
---
## 任務管理
### 任務批次策略
**原則**: 一次處理 1-2 個功能,完成後驗證,再繼續下一個。
| 批次大小 | 適用場景 | 說明 |
|----------|----------|------|
| 1 個 | 緊急修復、簡單任務 | 快速完成 |
| 2-3 個 | 一般功能開發 | 平衡效率與品質 |
| 3+ 個 | 大型重構 | 需要更詳細的追蹤 |
### 驗證流程
每個任務完成後必須執行:
```bash
# 1. 編譯檢查
cargo check
# 2. Lint 檢查
cargo clippy --lib
# 3. 單元測試
cargo test --lib
# 4. 格式化檢查
cargo fmt -- --check
```
---
## 溝通模式
### 有效的任務描述
**建議格式**:
```
執行 [功能名稱]
- 優先級: 高/中/低
- 驗收標準: [明確的標準]
- 約束: [限制條件]
```
**範例**:
```
實作 monitor_jobs 表
- 優先級: 高
- 驗收標準: CRUD 操作可用,單元測試通過
- 約束: 使用現有架構
```
### 明確的暫停點
在每個階段完成後主動詢問:
```
[任務] 完成。要繼續:
1. 實作功能 A
2. 添加測試
3. 更新文檔
```
### 不建議的溝通
| 模式 | 問題 | 建議 |
|------|------|------|
| 一次指定太多項目 | 增加複雜度,難以追蹤 | 分批處理 |
| 模糊的任務描述 | 難以評估進度 | 使用明確的驗收標準 |
| 從不驗證 | 累積問題 | 每步驟完成後驗證 |
---
## 決策點
### 常見決策點
| 階段 | 問題 | 選項 |
|------|------|------|
| 開始 | 如何開始? | 先了解現況 / 直接實作 |
| 實作 | 實作方式? | 保持現有架構 / 重構 |
| 驗證 | 通過了嗎? | 繼續 / 修復問題 |
| 完成 | 還有什麼? | 下一個任務 / 結束 |
### 決策準則
1. **安全優先**: 破壞性變更需要明確確認
2. **驗證後繼續**: 每步驟完成後驗證
3. **文檔同步**: 變更後更新文檔
---
## 文檔使用
### 必讀文檔
| 文檔 | 用途 | 查閱時機 |
|------|------|----------|
| `AGENTS.md` | 專案規範 | 每次對話開始 |
| `docs/*.md` | 技術規格 | 功能實作前 |
### 文檔更新時機
| 變更類型 | 需要更新 |
|----------|----------|
| 新功能 | `AGENTS.md` + 相關技術文檔 |
| 架構變更 | `ARCHITECTURE_EVALUATION.md` |
| 問題修復 | `PENDING_ISSUES.md` |
| 環境變更 | `INSTALL_*.md` |
---
## 審查清單
### 實作完成後檢查
- [ ] `cargo clippy --lib` 通過
- [ ] `cargo test --lib` 通過
- [ ] `cargo fmt -- --check` 通過
- [ ] 文檔已更新
- [ ] 新功能有單元測試
- [ ] Pre-commit hook 通過
### 對話結束前
- [ ] 所有變更已驗證
- [ ] 文檔已同步
- [ ] 下一步計劃明確
---
## 範例流程
### 範例 1: 實作新功能
```
用戶: 實作使用者認證功能
OpenCode:
1. 分析現有架構
2. 創建任務清單
3. 實作核心功能
4. 添加單元測試
5. 更新文檔
6. 驗證通過
用戶: 完成,繼續下一個
```
### 範例 2: 修復問題
```
用戶: 修復登入超時問題
OpenCode:
1. 重現問題
2. 分析根因
3. 實作修復
4. 驗證修復
5. 添加測試防止回歸
用戶: 確認修復完成
```
---
## 常見問題
### Q: 如何避免一次處理太多?
**A**: 使用 `/todo` 追蹤任務,分批處理。
### Q: 如何確保文檔同步?
**A**: 每個任務完成後檢查是否需要更新文檔。
### Q: 何時應該結束對話?
**A**: 當主要任務完成,且沒有緊急的後續步驟時。
---
## MCP 設定
### 設定檔案
OpenCode MCP 設定位於 `~/.config/opencode/opencode.json`
### 已啟用的 MCP Servers
| Server | 用途 | 命令 |
|--------|------|------|
| gitea | Gitea API 操作 | `/opt/homebrew/bin/gitea-mcp-server` |
| n8n | n8n Workflow 操作 | `/opt/homebrew/bin/mcp-n8n` |
| postgres | PostgreSQL 資料庫查詢 | `/opt/homebrew/bin/mcp-server-postgres` |
| redis | Redis 快取操作 | `/opt/homebrew/bin/mcp-server-redis` |
| qdrant | Qdrant 向量搜尋 | `/opt/homebrew/bin/mcp-server-qdrant` |
| filesystem | 檔案系統操作 | `/opt/homebrew/bin/mcp-server-filesystem` |
### MCP 設定格式
**Schema 參考**: `https://opencode.ai/config.json`
**必要欄位**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `type` | string | `"local"``"remote"` |
| `command` | array | 命令和參數local 必要) |
| `url` | string | 遠端 URLremote 必要) |
**可選欄位**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `environment` | object | 環境變數local only |
| `enabled` | boolean | 是否啟用 |
| `timeout` | number | 請求超時(毫秒) |
| `headers` | object | 請求頭remote only |
**Local MCP 範例**:
```json
{
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
},
"postgres": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-server-postgres"],
"environment": {
"DATABASE_URL": "postgresql://accusys:accusys@localhost:5432/momentry"
}
},
"redis": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-server-redis"],
"environment": {
"REDIS_URL": "redis://:accusys@localhost:6379"
}
},
"qdrant": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-server-qdrant"],
"environment": {
"QDRANT_URL": "http://localhost:6333",
"QDRANT_API_KEY": ""
}
},
"filesystem": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-server-filesystem"],
"args": ["/Users/accusys/momentry"]
}
}
}
```
**Remote MCP 範例**:
```json
{
"mcp": {
"jira": {
"type": "remote",
"url": "https://jira.example.com/mcp",
"enabled": true,
"headers": {
"Authorization": "Bearer your-token"
}
}
}
}
```
### n8n MCP 工具 (43 個)
#### Workflows (10)
| 工具 | 說明 |
|------|------|
| `n8n_list_workflows` | 列出所有 workflows |
| `n8n_get_workflow` | 取得 workflow 詳情 |
| `n8n_create_workflow` | 建立新 workflow |
| `n8n_update_workflow` | 更新 workflow |
| `n8n_delete_workflow` | 刪除 workflow |
| `n8n_activate_workflow` | 啟用 workflow |
| `n8n_deactivate_workflow` | 停用 workflow |
| `n8n_execute_workflow` | 執行 workflow |
| `n8n_get_workflow_tags` | 取得 workflow 標籤 |
| `n8n_update_workflow_tags` | 更新 workflow 標籤 |
#### Executions (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_executions` | 列出執行記錄 |
| `n8n_get_execution` | 取得執行詳情 |
| `n8n_delete_execution` | 刪除執行記錄 |
#### Data Tables (8)
| 工具 | 說明 |
|------|------|
| `n8n_list_datatables` | 列出資料表 |
| `n8n_create_datatable` | 建立資料表 |
| `n8n_get_datatable` | 取得資料表結構 |
| `n8n_get_datatable_rows` | 取得資料表列 |
| `n8n_insert_datatable_rows` | 插入資料列 |
| `n8n_update_datatable_rows` | 更新資料列 |
| `n8n_upsert_datatable_row` | 插入或更新資料列 |
| `n8n_delete_datatable_rows` | 刪除資料列 |
#### Tags (5)
| 工具 | 說明 |
|------|------|
| `n8n_list_tags` | 列出所有標籤 |
| `n8n_get_tag` | 取得標籤 |
| `n8n_create_tag` | 建立標籤 |
| `n8n_update_tag` | 更新標籤 |
| `n8n_delete_tag` | 刪除標籤 |
#### Credentials (4)
| 工具 | 說明 |
|------|------|
| `n8n_list_credentials` | 列出憑證 |
| `n8n_create_credential` | 建立憑證 |
| `n8n_delete_credential` | 刪除憑證 |
| `n8n_get_credential_schema` | 取得憑證 schema |
#### Users (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_users` | 列出使用者 |
| `n8n_get_user` | 取得使用者 |
| `n8n_delete_user` | 刪除使用者 |
#### Variables (3)
| 工具 | 說明 |
|------|------|
| `n8n_list_variables` | 列出變數 |
| `n8n_create_variable` | 建立變數 |
| `n8n_delete_variable` | 刪除變數 |
#### 其他 (7)
| 工具 | 說明 |
|------|------|
| `n8n_list_projects` | 列出專案 |
| `n8n_create_project` | 建立專案 |
| `n8n_update_project` | 更新專案 |
| `n8n_delete_project` | 刪除專案 |
| `n8n_generate_audit` | 產生安全審計報告 |
| `n8n_health_check` | 健康檢查 |
| `n8n_trigger_webhook` | 觸發 webhook |
### 安裝 n8n MCP
```bash
npm install -g @nextoolsolutions/mcp-n8n
```
### n8n API Key 設定
1. 開啟 n8n UI (http://localhost:5678)
2. 前往 Settings → API
3. 建立 API Key
4. 將 API Key 加入 `opencode.json``N8N_API_KEY`
### 驗證 MCP 運作
```bash
# 測試 MCP server
N8N_BASE_URL=https://n8n.momentry.ddns.net \
N8N_API_KEY="your-key" \
mcp-n8n
# 測試工具列表
echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}}
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}' | \
N8N_BASE_URL=https://n8n.momentry.ddns.net \
N8N_API_KEY="your-key" \
mcp-n8n
```
---
## 相關文件
- [AGENTS.md](../AGENTS.md) - 專案開發規範
- [ARCHITECTURE_EVALUATION.md](./ARCHITECTURE_EVALUATION.md) - 架構優化評估
- [PENDING_ISSUES.md](./PENDING_ISSUES.md) - 待解決問題追蹤
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南

View File

@@ -0,0 +1,535 @@
# OpenCode MCP Servers 安裝指南
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-03-24 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-24 | 創建文件 | OpenCode | OpenCode / big-pickle |
| V1.1 | 2026-03-24 | 新增 sentry, context7, playwright MCP | OpenCode | OpenCode / big-pickle |
---
## 概述
本文檔說明如何在 macOS 上為 OpenCode 安裝和配置 MCP (Model Context Protocol) Servers透過標準化的協定讓 AI 助手能夠存取各種外部服務和工具。
MCP Servers 提供:
- 標準化的工具調用介面
- 安全的資源存取控制
- 統一的配置和管理方式
---
## 已安裝的 MCP Servers
| Server | 安裝方式 | 用途 | 狀態 |
|--------|----------|------|------|
| gitea | Homebrew | Gitea API 操作 | ✅ |
| n8n | NPM (@nextoolsolutions/mcp-n8n) | n8n Workflow 管理 | ✅ |
| postgres | NPM (@modelcontextprotocol/server-postgres) | PostgreSQL 資料庫查詢 | ✅ |
| redis | NPM (@modelcontextprotocol/server-redis) | Redis 快取操作 | ✅ |
| mongodb | NPM (mongodb-mcp-server) | MongoDB 文件資料庫 | ✅ |
| qdrant | Python (qdrant/mcp-server-qdrant) | Qdrant 向量搜尋 | ✅ |
| filesystem | NPM (@modelcontextprotocol/server-filesystem) | 檔案系統操作 | ✅ |
| sentry | NPM (@sentry/mcp-server) | 錯誤追蹤和監控 | ⏳ |
| context7 | NPM (@upstash/context7-mcp) | 技術文檔搜尋 | ✅ |
| playwright | NPM (@playwright/mcp) | 瀏覽器自動化 | ✅ |
---
## 前置條件
- OpenCode 已安裝
- Node.js (用於 NPM 套件)
- Python 3.11+ (用於 qdrant-mcp)
- 相關服務已運行 (PostgreSQL, Redis, Qdrant, Gitea, n8n, MongoDB)
---
## 安裝步驟
### Step 1: 安裝 NPM MCP Servers
```bash
npm install -g @modelcontextprotocol/server-postgres
npm install -g @modelcontextprotocol/server-redis
npm install -g @modelcontextprotocol/server-filesystem
npm install -g @modelcontextprotocol/server-everything
npm install -g @nextoolsolutions/mcp-n8n
npm install -g mongodb-mcp-server
npm install -g @sentry/mcp-server
npm install -g @upstash/context7-mcp
npm install -g @playwright/mcp
```
**驗證**:
```bash
which mcp-server-postgres
which mcp-server-redis
which mcp-server-filesystem
which mcp-n8n
which mongodb-mcp-server
which sentry-mcp
which context7-mcp
which playwright-mcp
```
---
### Step 2: 安裝 gitea-mcp-server
```bash
brew install gitea-mcp-server
```
**驗證**:
```bash
which gitea-mcp-server
```
---
### Step 3: 安裝 MongoDB MCP Server (已包含在 Step 1)
MongoDB MCP 已透過 NPM 安裝:
```bash
npm install -g mongodb-mcp-server
```
---
### Step 4: 安裝 Qdrant MCP Server (Python)
```bash
cd /tmp
git clone https://github.com/qdrant/mcp-server-qdrant.git
cd mcp-server-qdrant
/opt/homebrew/bin/python3.11 -m pip install -e .
```
**驗證**:
```bash
which mcp-server-qdrant
```
---
### Step 5: 配置 OpenCode MCP 設定
編輯 `~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token",
"<GITEA_TOKEN>",
"-host",
"http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
},
"postgres": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/mcp-server-postgres",
"postgresql://accusys:accusys@localhost:5432/momentry"
]
},
"redis": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/mcp-server-redis",
"redis://:accusys@localhost:6379"
]
},
"mongodb": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mongodb-mcp-server"],
"environment": {
"MONGODB_URI": "mongodb://localhost:27017"
}
},
"qdrant": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-server-qdrant"],
"environment": {
"QDRANT_URL": "http://localhost:6333",
"QDRANT_API_KEY": ""
}
},
"filesystem": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/mcp-server-filesystem",
"/Users/accusys/momentry"
]
},
"sentry": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/sentry-mcp",
"--access-token",
"<SENTRY_ACCESS_TOKEN>"
]
},
"context7": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/context7-mcp"]
},
"playwright": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/playwright-mcp"]
}
}
}
```
---
### Step 6: 驗證 MCP Servers
```bash
# 列出所有 MCP servers
opencode mcp ls
# 或在 OpenCode 中執行
/mcps
```
---
## MCP Server 工具說明
### gitea
| 工具 | 說明 |
|------|------|
| list_repos | 列出倉庫 |
| get_repo | 取得倉庫詳情 |
| list_issues | 列出 Issues |
| create_issue | 建立 Issue |
| list_pulls | 列出 Pull Requests |
---
### n8n (43 個工具)
#### Workflows (10)
| 工具 | 說明 |
|------|------|
| n8n_list_workflows | 列出所有 workflows |
| n8n_get_workflow | 取得 workflow 詳情 |
| n8n_create_workflow | 建立新 workflow |
| n8n_update_workflow | 更新 workflow |
| n8n_delete_workflow | 刪除 workflow |
| n8n_activate_workflow | 啟用 workflow |
| n8n_deactivate_workflow | 停用 workflow |
| n8n_execute_workflow | 執行 workflow |
| n8n_get_workflow_tags | 取得 workflow 標籤 |
| n8n_update_workflow_tags | 更新 workflow 標籤 |
#### Executions (3)
| 工具 | 說明 |
|------|------|
| n8n_list_executions | 列出執行記錄 |
| n8n_get_execution | 取得執行詳情 |
| n8n_delete_execution | 刪除執行記錄 |
#### Data Tables (8)
| 工具 | 說明 |
|------|------|
| n8n_list_datatables | 列出資料表 |
| n8n_create_datatable | 建立資料表 |
| n8n_get_datatable | 取得資料表結構 |
| n8n_get_datatable_rows | 取得資料表列 |
| n8n_insert_datatable_rows | 插入資料列 |
| n8n_update_datatable_rows | 更新資料列 |
| n8n_upsert_datatable_row | 插入或更新資料列 |
| n8n_delete_datatable_rows | 刪除資料列 |
#### Tags (5)
| 工具 | 說明 |
|------|------|
| n8n_list_tags | 列出所有標籤 |
| n8n_get_tag | 取得標籤 |
| n8n_create_tag | 建立標籤 |
| n8n_update_tag | 更新標籤 |
| n8n_delete_tag | 刪除標籤 |
#### 其他 (17)
| 工具 | 說明 |
|------|------|
| n8n_list_credentials | 列出憑證 |
| n8n_create_credential | 建立憑證 |
| n8n_delete_credential | 刪除憑證 |
| n8n_get_credential_schema | 取得憑證 schema |
| n8n_list_users | 列出使用者 |
| n8n_get_user | 取得使用者 |
| n8n_delete_user | 刪除使用者 |
| n8n_list_variables | 列出變數 |
| n8n_create_variable | 建立變數 |
| n8n_delete_variable | 刪除變數 |
| n8n_list_projects | 列出專案 |
| n8n_create_project | 建立專案 |
| n8n_update_project | 更新專案 |
| n8n_delete_project | 刪除專案 |
| n8n_generate_audit | 產生安全審計報告 |
| n8n_health_check | 健康檢查 |
| n8n_trigger_webhook | 觸發 webhook |
---
### postgres
| 工具 | 說明 |
|------|------|
| query | 執行唯讀 SQL 查詢 |
**範例**:
```json
{
"sql": "SELECT * FROM users LIMIT 10;"
}
```
---
### redis
| 工具 | 說明 |
|------|------|
| set | 設定 key-value 配對 |
| get | 取得 key 的值 |
| delete | 刪除 key(s) |
| list | 列出符合模式的 keys |
---
### mongodb
| 工具 | 說明 |
|------|------|
| mongodb_list_databases | 列出所有資料庫 |
| mongodb_list_collections | 列出資料庫中的集合 |
| mongodb_find | 查詢文件 |
| mongodb_insert_one | 插入單個文件 |
| mongodb_insert_many | 插入多個文件 |
| mongodb_update_one | 更新單個文件 |
| mongodb_update_many | 更新多個文件 |
| mongodb_delete_one | 刪除單個文件 |
| mongodb_delete_many | 刪除多個文件 |
| mongodb_aggregate | 聚合查詢 |
**範例**:
```json
{
"database": "momentry",
"collection": "videos",
"filter": {"uuid": "abc123"}
}
```
---
### qdrant
| 工具 | 說明 |
|------|------|
| 查詢工具 | 向量搜尋和集合管理 |
---
### filesystem
| 工具 | 說明 |
|------|------|
| read_file | 讀取檔案 |
| write_file | 寫入檔案 |
| list_directory | 列出目錄 |
| create_directory | 建立目錄 |
---
### sentry
| 工具 | 說明 |
|------|------|
| sentry_list_issues | 列出 issues |
| sentry_get_issue | 取得 issue 詳情 |
| sentry_create_issue | 建立 issue |
| sentry_update_issue | 更新 issue |
| sentry_list_projects | 列出專案 |
| sentry_search_docs | 搜尋 Sentry 文件 |
**注意**: 需要設定 `SENTRY_ACCESS_TOKEN`
**取得 Token**:
1. 登入 https://sentry.io
2. Settings → Developer Tools → API Keys
3. 建立新 Token (需要 `event:read`, `project:read` 權限)
---
### context7
| 工具 | 說明 |
|------|------|
| context7_resolve-library-id | 解析函式庫 ID |
| context7_query-docs | 查詢函式庫文件 |
**用途**: 即時技術文件和函式庫查詢,獲取最新的 API 語法和範例。
---
### playwright
| 工具 | 說明 |
|------|------|
| playwright_navigate | 導航到 URL |
| playwright_screenshot | 截圖 |
| playwright_click | 點擊元素 |
| playwright_type | 輸入文字 |
| playwright_evaluate | 執行 JavaScript |
**用途**: 瀏覽器自動化測試、網頁截圖、網頁內容抓取。
---
## 管理指令
### 新增 MCP Server
```bash
opencode mcp add
```
### 列出 MCP Servers
```bash
opencode mcp ls
```
### 認證 OAuth Server
```bash
opencode mcp auth <server-name>
```
---
## 故障排除
### MCP Server 連線失敗
1. 檢查服務是否運行:
```bash
# PostgreSQL
pg_isready -h localhost -p 5432
# Redis
redis-cli -a accusys ping
# Qdrant
curl -s http://localhost:6333/
```
2. 檢查 MCP Server 是否安裝:
```bash
which mcp-server-postgres
which mcp-server-redis
```
3. 驗證配置格式正確
---
### Token 或 API Key 無效
1. PostgreSQL: 確認使用者名稱和密碼
2. Redis: 確認密碼
3. n8n: 確認 API Key 未過期
4. Gitea: 確認 Token 有效
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| OpenCode 配置 | ~/.config/opencode/opencode.json | MCP 設定檔 |
| Gitea MCP | /opt/homebrew/bin/gitea-mcp-server | 安裝路徑 |
| n8n MCP | /opt/homebrew/bin/mcp-n8n | 安裝路徑 |
| Postgres MCP | /opt/homebrew/bin/mcp-server-postgres | 安裝路徑 |
| Redis MCP | /opt/homebrew/bin/mcp-server-redis | 安裝路徑 |
| Qdrant MCP | /opt/homebrew/bin/mcp-server-qdrant | 安裝路徑 |
| Filesystem MCP | /opt/homebrew/bin/mcp-server-filesystem | 安裝路徑 |
---
## 常用指令
```bash
# 列出所有 MCP servers
opencode mcp ls
# 測試 PostgreSQL MCP
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"query","arguments":{"sql":"SELECT 1;"}}}' | \
/opt/homebrew/bin/mcp-server-postgres postgresql://accusys:accusys@localhost:5432/momentry
# 測試 Redis MCP
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ping","arguments":{}}}' | \
/opt/homebrew/bin/mcp-server-redis redis://:accusys@localhost:6379
```
---
## 版本資訊
- 版本: 1.0
- 安裝日期: 2026-03-24
- 文件更新: 2026-03-24
---
## 相關文件
- [OpenCode Guide](./OPENCODE_GUIDE.md) - OpenCode 使用指南
- [Gitea MCP 安裝](./INSTALL_GITEA_MCP.md) - Gitea MCP 詳細設定
- [n8n MCP 設定](./N8N_MCP_SETUP.md) - n8n MCP 詳細設定
- [服務總覽](./SERVICES.md) - 所有服務狀態總覽

813
docs/PENDING_ISSUES.md Normal file
View File

@@ -0,0 +1,813 @@
# 待解決問題追蹤
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 更新問題狀態 | OpenCode | - |
| V1.2 | 2026-03-21 | 添加備份機制優化待辦 | OpenCode | - |
| V1.3 | 2026-03-21 | 完成清理硬編碼密碼 | OpenCode | - |
| V1.4 | 2026-03-21 | 完成 OpenCode n8n MCP 整合 | OpenCode | - |
| V1.5 | 2026-03-21 | 完成 API Key Management 核心模組 | OpenCode | - |
| V1.6 | 2026-03-23 | 添加 Momentry Core API launchd 待辦 | OpenCode | - |
| V1.7 | 2026-03-23 | 完成 Momentry Core API launchd 設定 | OpenCode | - |
| V1.8 | 2026-03-24 | 完成服務統一遷移,所有服務使用自定義 plist | OpenCode | big-pickle |
| V1.9 | 2026-03-24 | 建立統一會員系統實作計畫 | OpenCode | big-pickle |
| V2.0 | 2026-03-24 | 建立 Job Worker 實作計畫 | OpenCode | big-pickle |
---
## 問題 #1: sqlx async INSERT 不會實際寫入數據庫
### 問題描述
使用 sqlx async 執行 INSERT 時報告成功rows_affected=1但數據沒有實際寫入數據庫。
### 嘗試過的解決方案
| # | 嘗試方法 | 結果 |
|---|---------|------|
| 1 | 使用 `execute()` | 報告成功但未寫入 |
| 2 | 使用 `fetch()` | 同樣問題 |
| 3 | 使用交易 | 同樣問題 |
| 4 | 使用連接池 `acquire()` | 同樣問題 |
| 5 | 每個操作創建新連接池 | 同樣問題 |
| 6 | 使用 `std::process::Command` 同步調用 psql | 同樣問題 |
| 7 | 使用 `tokio::task::spawn_blocking` | 同樣問題 |
### 觀察到的現象
- 直接用 psql 命令行可以成功寫入
- 用另一個 PostgreSQL client 可以成功寫入
- sqlx 查詢 COUNT(*) 可以正確讀取數據
- 但 sqlx INSERT 報告成功卻不寫入
### 懷疑方向
- sqlx 連接池與 PostgreSQL 的某種交互問題
- Rust async runtime 與 PostgreSQL client 的問題
- postgresql.conf 配置問題
### 臨時解決方案
- Qdrant 向量存儲正常工作(不受影響)
- 存儲狀態追蹤功能正常運作
### 負責人
-
### 建立日期
2026-03-17
### 備註
影響 `store_vector` 函數PVector 存儲無法正常工作,但 QVector 正常運作
### 2026-03-21 調查結果
#### 測試結果
- 直接 psql INSERT: ✅ 成功
- 資料寫入驗證: ✅ 成功
#### 發現的問題
`store_vector` 函數 (`postgres_db.rs:819-860`) 存在以下問題:
```rust
// 問題 1: 錯誤被靜默忽略
match join_result {
Ok((cid, Ok(output))) => {
if !output.status.success() {
let err = String::from_utf8_lossy(&output.stderr);
tracing::error!("psql error for {}: {}", cid, err);
// 沒有返回錯誤!只是記錄日誌
}
}
// ...
}
Ok(()) // 即使失敗也返回 Ok
```
#### 建議修復
1.`store_vector` 返回實際結果
2. 在失敗時返回 `Err`
3. 添加單元測試驗證
#### 下一步
- [x] 修復 `store_vector` 錯誤處理
- [x] 添加單元測試
- [ ] 重現並確認問題根因
### 2026-03-21 修復完成
已修復 `store_vector` 函數的錯誤處理:
```rust
// 修復前:錯誤被靜默忽略
match join_result {
Ok((cid, Ok(output))) => {
if !output.status.success() {
tracing::error!("..."); // 只記錄,不返回錯誤
}
}
}
Ok(()) // 即使失敗也返回 Ok
// 修復後:正確傳播錯誤
let (cid, output) = result;
let output = output.map_err(|e| anyhow::anyhow!(...))?;
if !output.status.success() {
anyhow::bail!("psql INSERT failed...");
}
```
---
## 問題 #2: TUI 與 stdout 輸出混合
### 問題描述
Python processors 的 progress 輸出蓋過 TUI導致使用者無法清楚看到處理進度。
### 解決方案
~~使用 TUI 渲染到 stderr~~ → 使用 Redis Pub/Sub 作為消息總線
### 當前狀態
| 子項目 | 狀態 |
|--------|------|
| RedisPublisher Python 端 | ✅ 已實作 |
| Rust Redis 客戶端 | ✅ 已實作 (`redis_client.rs`) |
| Rust 訂閱更新 TUI | ✅ 已實作 (main.rs) |
| 中斷後繼續/重做 | 🔜 待實作 |
### 負責人
-
### 建立日期
2026-03-17
### 更新日期
2026-03-21
### 參考文檔
- `docs/MOMENTRY_CORE_REDIS_KEYS.md`
- `scripts/redis_publisher.py`
- `src/core/db/redis_client.rs`
---
## 問題 #3: Redis Message Bus 尚未實作
### 問題描述
根據設計規範,需要使用 Redis 作為監控和狀態管理系統。
### 當前狀態
| 實作項目 | 狀態 | 說明 |
|---------|------|------|
| Redis 客戶端 (Hash) | ✅ | `redis_client.rs` |
| Redis 客戶端 (Pub/Sub) | ✅ | `redis_client.rs::subscribe_progress()` |
| Python RedisPublisher | ✅ | `scripts/redis_publisher.py` |
| Rust 訂閱頻道 | ✅ | `main.rs` 中的 redis 訂閱邏輯 |
| 監控腳本 | ✅ | `monitor/service/health_check.sh` |
### 負責人
-
### 建立日期
2026-03-17
### 更新日期
2026-03-21
### 優先級
~~高~~ → 中 - 核心功能已完成
### 參考文檔
- `docs/MOMENTRY_CORE_REDIS_KEYS.md`
- `docs/MOMENTRY_CORE_MONITORING.md`
---
## 架構優化待評估
詳細內容請參考 [ARCHITECTURE_EVALUATION.md](./ARCHITECTURE_EVALUATION.md)
| 項目 | 複雜度 | 優先級 |
|------|--------|--------|
| PostgreSQL → Redis 故障轉移 | 中 | 待評估 |
| 連接池監控 | 低 | 待評估 |
| Processor 重試機制 | 中 | 待評估 |
| PyO3 整合 | 高 | 低 |
| HTTP 健康端點 | 低 | ✅ 已完成 |
| Commit Message Lint | 低 | ✅ 已完成 |
---
## 備份機制優化 (2026-03-21)
### 問題分析
| 問題 | 嚴重性 | 說明 |
|------|--------|------|
| 溫冷分層未啟用 | 中 | weekly/monthly/archive 目錄為空 |
| 清理策略未執行 | 高 | 每日備份保留過多,空間浪費 |
| 無異地備份 | 高 | 無遠程備份(雲端/另一設備) |
| 備份驗證未自動化 | 中 | 只檢查文件存在,沒驗證可恢復性 |
| Gitea 大文件 | 中 | 每次備份 1GB頻率過高 |
| 密碼硬編碼 | 高 | 腳本中有多處明文密碼 |
### 待辦事項
| # | 任務 | 優先級 | 狀態 |
|---|------|--------|------|
| 1 | 啟用溫冷分層 (`backup_monitor.sh tier`) | 高 | 待辦 |
| 2 | 啟用清理策略 (`backup_monitor.sh cleanup`) | 高 | 待辦 |
| 3 | 添加備份驗證腳本 | 高 | 待辦 |
| 4 | 優化 Gitea 備份頻率 (改為週備份) | 中 | 待辦 |
| 5 | 創建外部備份腳本 (rsync/雲端) | 高 | 待辦 |
| 6 | 清理腳本中的硬編碼密碼 | 高 | ✅ 已完成 |
### 推薦備份策略
| 層級 | 保留時間 | 頻率 | 目標 |
|------|----------|------|------|
| Hot | 7 天 | 每日 | 本地 SSD |
| Warm | 30 天 | 每週 | 本地 HDD |
| Cold | 90 天 | 每月 | 外部存儲 |
| Archive | 1 年 | 每季 | 離線/雲端 |
### 建議的 Crontab
```bash
# 每日備份 (排除 Gitea)
0 3 * * 1-6 /Users/accusys/momentry/scripts/backup_all.sh all_except_gitea
# 每週完整備份 (含 Gitea)
0 3 * * 0 /Users/accusys/momentry/scripts/backup_all.sh all
# 每週溫冷分層
0 4 * * 0 /Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh tier
# 每週清理
0 5 * * 0 /Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh cleanup
# 每月驗證
0 6 1 * * /Users/accusys/momentry/scripts/verify_backup.sh
```
### 負責人
-
### 參考文件
- `/Users/accusys/momentry/scripts/backup_all.sh`
- `/Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh`
- `/Users/accusys/momentry/backup/`
---
## OpenCode n8n MCP 整合 (2026-03-21)
### 完成狀態
| 項目 | 狀態 | 說明 |
|------|------|------|
| n8n REST API 啟用 | ✅ | `N8N_PUBLIC_API_ENABLED=true` |
| API Key 生成 | ✅ | Settings → API |
| MCP Server 安裝 | ✅ | `@nextoolsolutions/mcp-n8n` |
| OpenCode 設定 | ✅ | `~/.config/opencode/opencode.json` |
| 43 工具可用 | ✅ | workflows, executions, datatables, tags 等 |
### 設定檔案
`~/.config/opencode/opencode.json`:
```json
{
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
### 重要提醒
1. **API Key 安全**: 避免提交到 Git
2. **n8n 需通過反向代理**: localhost:5678 無法直接訪問 API需通過 Caddy
3. **重啟生效**: 修改 `opencode.json` 後需重啟 OpenCode
### 參考文件
- `docs/OPENCODE_GUIDE.md` - MCP 設定章節
- `docs/INSTALL_N8N.md` - n8n 安裝指南
---
## n8n API 備份腳本 (2026-03-21)
### 腳本位置
| 腳本 | 說明 | 依賴 |
|------|------|------|
| `monitor/workflow/backup_n8n_api.py` | REST API 備份 | requests |
| `monitor/workflow/backup_n8n_mcp.py` | MCP 備份(開發中) | mcp SDK |
### 使用方式
```bash
# 備份所有 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py
# 只顯示變更(不備份)
N8N_API_KEY="..." python3.11 backup_n8n_api.py --diff
# 差異備份(只備份變更的 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py --incremental
# 列出可用備份
python3.11 backup_n8n_api.py --list
# 驗證最新備份
python3.11 backup_n8n_api.py --verify
# 顯示備份統計
python3.11 backup_n8n_api.py --stats
# 只備份啟用的 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py --active-only
# 備份失敗的執行記錄
N8N_API_KEY="..." python3.11 backup_n8n_api.py --failed-only
```
### 功能
- [x] REST API 備份
- [x] 變更偵測
- [x] SHA256 校驗
- [x] 備份版本化
- [x] 差異備份(`--incremental`
- [x] 備份驗證(`--verify`
- [x] 備份統計(`--stats`
- [x] 失敗執行記錄備份(`--failed-only`
- [ ] 選擇性備份(按 Tags
### 備份位置
```
/Users/accusys/momentry/backup/n8n_workflows/api/
├── 20260321_122059/
│ ├── workflows.json # 21 workflows
│ ├── workflows.json.sha256 # SHA256 校驗
│ ├── tags.json # Tags若有
│ └── manifest.json # 元數據
└── ...
```
### Crontab 建議
```bash
# 每日備份(下午 3 點)
0 15 * * * N8N_API_KEY="..." /opt/homebrew/bin/python3.11 /Users/accusys/momentry_core_0.1/monitor/workflow/backup_n8n_api.py >> /Users/accusys/momentry/log/monitor/workflow_backup_api.log 2>&1
```
---
## API Key Management System (2026-03-21)
### 設計目標
為 Momentry Core 實現完整的 API Key 管理系統,包括:
- API Key 生成(安全隨機)
- Key 哈希SHA256
- 異常檢測
- 強制輪換機制
- 審計日誌
### 模組架構
```
src/core/api_key/
├── mod.rs # 模組導出
├── models.rs # 數據模型和類型
├── service.rs # 核心服務邏輯
├── anomaly.rs # 異常檢測
└── rotation.rs # 輪換管理
```
### Key 類型
| 類型 | 前綴 | 默認 TTL | 寬限期 |
|------|------|----------|--------|
| System | `msys_` | 365 天 | 72 小時 |
| User | `muser_` | 90 天 | 24 小時 |
| Service | `msvc_` | 180 天 | 48 小時 |
| Integration | `mint_` | 30 天 | 24 小時 |
| Emergency | `memg_` | 1 天 | 0 小時 |
### Key 格式
```
{prefix}{uuid}_{timestamp}_{random}
例如: muser_a1b2c3d4e5f6_1710998400_abc12345
```
### 異常檢測閾值
| 指標 | 閾值 |
|------|------|
| 每分鐘請求數 | 1000 |
| 每小時請求數 | 10000 |
| 錯誤率 | 50% |
| 每小時唯一 IP 數 | 5 |
| 鎖定閾值 | 3 次觸發 |
### 實現狀態
| 組件 | 狀態 | 說明 |
|------|------|------|
| 數據模型 | ✅ 完成 | `models.rs` |
| Key 生成/哈希 | ✅ 完成 | `service.rs` |
| 異常檢測 | ✅ 完成 | `anomaly.rs` |
| 輪換機制 | ✅ 完成 | `rotation.rs` |
| CLI 命令 | ✅ 完成 | `main.rs` |
| 數據庫集成 | ✅ 完成 | `postgres_db.rs` |
| Redis 告警 | ✅ 完成 | `redis_client.rs` |
| 數據庫遷移 | ✅ 完成 | `migrations/001_api_key_management.sql` |
| 單元測試 | ✅ 完成 | 55 個測試通過 |
### CLI 命令
```bash
# 創建 API Key
momentry api-key create <name> --key-type service --ttl 90
# 列出所有 Keys
momentry api-key list
# 驗證 Key
momentry api-key validate --key <key>
# 撤銷 Key
momentry api-key revoke --key <key>
# 請求輪換
momentry api-key rotate --key <key>
# 顯示統計
momentry api-key stats
```
### 參考文件
- `src/core/api_key/` - API Key 模組
- `docs/API_KEY_MANAGEMENT.md` - 設計文檔
- `migrations/001_api_key_management.sql` - 數據庫遷移
---
## 問題 #5: Redis 用戶名問題 (2026-03-21)
### 問題描述
Redis 僅有 `default` 用戶,無 `accusys` 用戶。`.env` 檔案使用 `redis://accusys:accusys@localhost:6379` 格式會導致認證失敗。
### 測試結果
| URL 格式 | 結果 |
|----------|------|
| `redis://accusys:accusys@localhost:6379` | ❌ AUTH failed |
| `redis://:accusys@localhost:6379` | ✅ PONG |
### Redis ACL 狀態
```
user default on sanitize-payload #1bd51c... ~* &* +@all
requirepass: accusys
```
### 根本原因
1. Redis 啟動時僅設定 `--requirepass accusys`
2. 未建立自訂用戶 `accusys`
3. ACL 變更不會持久化(無 config file
### 已執行修復
| 項目 | 修改 |
|------|------|
| `.env` | `redis://accusys:accusys@localhost:6379``redis://:accusys@localhost:6379` |
### 待解決問題
1. **ACL 持久化**Redis 啟動後手動建立的用戶不會保留(重啟後消失)
2. **需配置 ACL 文件**:建議建立 `users.acl` 並在 plist 中指定
### 建議解決方案
#### 方案 A使用默認用戶現行
```bash
# .env
REDIS_URL=redis://:accusys@localhost:6379
```
**優點**:簡單,無需修改 Redis 配置
**缺點**:所有應用共享默認用戶
#### 方案 B建立 ACL 配置文件
```bash
# 1. 創建 ACL 文件
cat > /Users/accusys/momentry/etc/redis/users.acl << 'EOF'
user default on sanitize-payload ~* &* +@all >accusys
user accusys on sanitize-payload ~* &* +@all >accusys
EOF
# 2. 修改 plist 添加 --aclfile 參數
--aclfile /Users/accusys/momentry/etc/redis/users.acl
# 3. 重啟 Redis
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
**優點**支持多用戶ACL 持久化
**缺點**:需修改 plist 並重啟
### 影響範圍
- `src/core/config.rs` - REDIS_URL 讀取
- `src/core/db/redis_client.rs` - Redis 連線
- `momentry api-key` 命令 - 異常告警
### 狀態
- [x] 已確認問題存在
- [x] 已修改 `.env` 使用默認用戶
- [ ] 待決定是否實施 ACL 方案
---
## 問題 #6: Momentry Core API 未開機自動啟動 (2026-03-23)
### 問題描述
Momentry Core API 服務 (`momentry server --port 3002`) 未設定 launchd導致
1. 系統重啟後 API 服務不會自動啟動
2. `api.momentry.ddns.net` 返回 502 Bad Gateway
3. n8n workflow 呼叫 API 時失敗
### 發現過程
1. n8n workflow 呼叫 `https://api.momentry.ddns.net/api/v1/n8n/search` 返回 502
2. 檢查發現 port 3002 無服務運行
3. Caddy 配置正向確,但後端服務未啟動
4. 手動啟動服務後 API 正常運作
### 配置需求
| 項目 | 值 |
|------|-----|
| 服務名稱 | `com.momentry.api` |
| 二進位檔 | `/Users/accusys/momentry_core_0.1/target/release/momentry` |
| 命令 | `server --port 3002` |
| Port | 3002 |
| 環境變數 | `DATABASE_URL`, `REDIS_URL` 等 |
### 待辦事項
- [x] 建立 `docs/INSTALL_MOMENTRY_API.md` 安裝文件
- [x] 建立 `/Library/LaunchDaemons/com.momentry.api.plist`
- [x] 設定環境變數 (`DATABASE_URL`, `REDIS_URL` 等)
- [x] 測試 launchctl load/unload
- [x] 驗證開機自動啟動 (launchd 載入成功)
### 完成日期
2026-03-23
### 參考文件
- `/Library/LaunchDaemons/com.momentry.n8n.main.plist` - n8n plist 範例
- `docs/INSTALL_N8N.md` - plist 配置說明
---
## 服務統一遷移 (2026-03-24)
### 問題描述
Reboot 後發現 n8n workflow 數量從 42 變成 41確認是 PostgreSQL 資料庫問題。經過調查發現:
1. **兩組不同的 PostgreSQL 資料目錄**
- Homebrew plist: `/opt/homebrew/var/postgresql@18` (有最新資料)
- Custom plist: `/Users/accusys/momentry/var/postgresql` (可能是舊資料)
2. **Reboot 時 custom plist 搶先啟動**,使用了錯誤的資料目錄
### 解決方案
1. **統一使用 custom plist**
- 刪除 homebrew plist (`~/Library/LaunchAgents/homebrew.mxcl.postgresql@18.plist`)
- Custom plist 使用 `/Users/accusys/momentry/var/postgresql` 作為資料目錄
- 將所有 14 個服務的 plist 註冊到 launchd
2. **所有已遷移的服務**
| 服務 | Plist | 資料目錄 |
|------|-------|----------|
| PostgreSQL | ✅ | `/Users/accusys/momentry/var/postgresql` |
| MariaDB | ✅ | `/Users/accusys/momentry/var/mariadb` |
| MongoDB | ✅ | `/opt/homebrew/var/mongodb` |
| Redis | ✅ | - |
| Ollama | ✅ | - |
| Qdrant | ✅ | - |
| n8n Main | ✅ | - |
| n8n Worker | ✅ | - |
| Caddy | ✅ | - |
| SFTPGo | ✅ | - |
| Gitea | ✅ | - |
| Gitea MCP | ✅ | - |
| PHP | ✅ | - |
| Momentry API | ✅ | - |
| RustDesk HBBR | ✅ | - |
| RustDesk HBBS | ✅ | - |
### 還發現的 Homebrew 服務 (未遷移)
| 服務 | 建議 |
|------|------|
| homebrew.mxcl.grafana | ⚠️ 考慮遷移 |
| homebrew.mxcl.prometheus | ⚠️ 考慮遷移 |
| homebrew.mxcl.openwebui | ⚠️ 考慮遷移 |
| homebrew.mxcl.kafka | ⚠️ 考慮遷移 |
| homebrew.mxcl.seaweedfs | ⚠️ 考慮遷移 |
| homebrew.mxcl.netdata | ⚠️ 考慮遷移 |
| homebrew.mxcl.ddclient | ⚠️ 動態 DNS |
| homebrew.mxcl.shadowsocks-rust | ⚠️ VPN |
### 預防措施
1. **確保統一資料目錄**:所有服務只使用一個資料目錄
2. **Reboot 測試**:遷移完成後需進行 Reboot 測試
3. **文件同步**plist 檔案同步到 repo
### 完成日期
2026-03-24
### 參考文件
- `docs/SERVICES.md` - 服務管理文檔
- `docs/SERVICE_ADDITION_GUIDE.md` - 服務添加規範
- `momentry_runtime/plist/` - plist 檔案存放位置
---
## Job Worker 實作 (2026-03-24)
### 目標
實作輪詢式 Job Worker實現檔案註冊後自動觸發處理
1. **輪詢機制**Worker 定期輪詢 jobs 佇列
2. **並行處理**:最多 2 個 processor 同時執行
3. **失敗容忍**:任一模組獨立,失敗可接續
### 設計決策
| 項目 | 決策 | 理由 |
|------|------|------|
| 觸發方式 | 輪詢Job Worker | 暫無可靠 API 觸發 |
| 並行處理 | 最多 2 個 | 可根據 CPU/GPU 調整 |
| 失敗處理 | 獨立模組,部分完成可接續 | 任何模組失敗都產出狀態 |
| Worker 啟動 | 獨立進程 | 隔離、易管理 |
| 並行上限 | 環境變數 + 預設值 | 靈活調整 |
| 狀態同步 | PostgreSQL + Redis | 可靠 + 即時 |
### 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `MOMENTRY_MAX_CONCURRENT` | 2 | 最大並行 processor 數 |
| `MOMENTRY_POLL_INTERVAL` | 5 | 輪詢間隔(秒) |
| `MOMENTRY_WORKER_ENABLED` | true | 是否啟用 worker |
### 實作計畫
詳細內容請參考 [JOB_WORKER_IMPLEMENTATION_PLAN.md](./JOB_WORKER_IMPLEMENTATION_PLAN.md)
### Phase 規劃
| Phase | 任務 | 預估工時 |
|-------|------|----------|
| 1 | 資料庫遷移 | 2h |
| 2 | Worker 框架 | 4h |
| 3 | Register API 整合 | 2h |
| 4 | Processor 執行 | 4h |
| 5 | 進度追蹤 | 2h |
| 6 | API 端點 | 3h |
| 7 | CLI 命令 | 2h |
| 8 | 測試 | 4h |
**總預估**: ~23h
### 實作結構
```
src/
├── worker/
│ ├── mod.rs # Worker 模組導出
│ ├── config.rs # Worker 配置
│ ├── worker.rs # Worker 主邏輯
│ ├── processor.rs # Processor 執行器
│ ├── queue.rs # Job 佇列管理
│ └── progress.rs # 進度追蹤
├── api/
│ └── server.rs # 更新 Register API
└── main.rs # 新增 worker 命令
```
### 狀態
- [x] 系統分析完成
- [x] 實作計畫文件建立
- [ ] Phase 1: 資料庫遷移
- [ ] Phase 2: Worker 框架
- [ ] Phase 3: Register API 整合
- [ ] Phase 4: Processor 執行
- [ ] Phase 5-8: 依序實作
### 參考文件
- `docs/JOB_WORKER_IMPLEMENTATION_PLAN.md` - 完整實作計畫
- `docs/PROCESSING_PIPELINE.md` - 處理流程
- `docs/MOMENTRY_CORE_REDIS_KEYS.md` - Redis Key 設計
---
## 統一會員系統 + 影片歸屬追蹤 (2026-03-24)
### 目標
建立統一的會員系統:
1. WordPress 作為唯一登入入口
2. 每個影片關聯到 user_id追蹤歸屬
3. Per-user 配額管理
4. API 端點啟用認證
### 實作計畫
詳細內容請參考 [USER_MANAGEMENT_PLAN.md](./USER_MANAGEMENT_PLAN.md)
### Phase 規劃
| Phase | 任務 | 複雜度 | 優先級 | 預估工時 |
|-------|------|--------|--------|----------|
| 1 | WordPress Application Passwords 測試 | 低 | P0 | 1.5h |
| 2 | 資料庫遷移 (users 表) | 中 | P0 | 3h |
| 3 | API auth middleware | 中 | P0 | 4h |
| 4 | Register API 更新 | 低 | P0 | 2h |
| 5 | Admin users API | 中 | P1 | 4h |
| 6 | n8n workflow | 中 | P1 | 6h |
| 7 | 配額管理 | 中 | P2 | 4h |
| 8 | 測試驗證 | 中 | P2 | 4h |
**總預估**: ~28.5h
### 待確認事項
- [ ] WordPress 用戶建立方式(手動/Elementor表單
- [ ] API Key 格式確認
- [ ] SFTPGo 整合方式
- [ ] 配額管理策略
- [ ] 用戶刪除同步流程
### 狀態
- [x] 系統分析完成
- [x] 實作計畫文件建立
- [ ] Phase 1: WordPress 認證測試
- [ ] Phase 2: 資料庫遷移
- [ ] Phase 3-8: 依序實作
### 參考文件
- `docs/USER_MANAGEMENT_PLAN.md` - 完整實作計畫
- `docs/API_KEY_MANAGEMENT.md` - API Key 管理
- `docs/SFTPGO_DEMO_USER.md` - SFTPGo 用戶設定

271
docs/PROCESSING_PIPELINE.md Normal file
View File

@@ -0,0 +1,271 @@
# Video Processing Pipeline - 處理流程
> 建立時間: 2026-03-22
---
## 處理流程架構
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Video Processing Pipeline │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 1: JSON 生成 (Process) │ │
│ │ │ │
│ │ video.mp4 ──→ [ASR] ──→ asr.json (語音辨識) │ │
│ │ ──→ [CUT] ──→ cut.json (場景偵測) │ │
│ │ ──→ [ASRX] ──→ asrx.json (說話者分離) │ │
│ │ ──→ [YOLO] ──→ yolo.json (物體偵測) │ │
│ │ ──→ [OCR] ──→ ocr.json (文字辨識) │ │
│ │ ──→ [Face] ──→ face.json (人臉偵測) │ │
│ │ ──→ [Pose] ──→ pose.json (姿態估計) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 2: 入庫 (Import) │ │
│ │ │ │
│ │ .json files ──→ PostgreSQL (fs_json = true) │ │
│ │ ↓ │ │
│ │ pre_chunks 表 (from ASR, CUT) │ │
│ │ frames 表 (from YOLO, OCR, Face, Pose) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 3: Chunk 生成 (Chunk) │ │
│ │ │ │
│ │ pre_chunks ──→ [Chunk Rule] ──→ chunks 表 │ │
│ │ ↓ │ │
│ │ 清洗 → 純文字 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 4: 向量化 (Vectorize) │ │
│ │ │ │
│ │ chunks ──→ [Embedding Model] ──→ vectors │ │
│ │ ↓ │ │
│ │ Qdrant (主要向量庫) │ │
│ │ PGVector (備份向量庫) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Stage 5: 搜尋 (Search) │ │
│ │ │ │
│ │ Natural Language Query ──→ [Embedding] ──→ [Qdrant Search] │ │
│ │ ↓ │ │
│ │ 返回結果含 media_url │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## CLI 命令
### Stage 1: JSON 生成 (Process)
```bash
# 基本用法
cargo run --bin momentry -- process <uuid_or_path>
# 只處理特定模組
cargo run --bin momentry -- process <uuid> --modules asr,cut
# 強制重新處理(忽略完整性檢查)
cargo run --bin momentry -- process <uuid> --force
# 從中斷點續傳
cargo run --bin momentry -- process <uuid> --resume
# 模組使用雲端處理
cargo run --bin momentry -- process <uuid> --modules yolo,face --cloud yolo
# 完整範例
cargo run --bin momentry -- process /path/to/video.mp4 \
--modules asr,cut,yolo,ocr \
--cloud yolo
```
### Stage 2: 入庫 (Import)
```bash
# 目前入庫在 process 完成後自動執行
# 計劃新增獨立的 import 命令
# cargo run --bin momentry -- import <uuid>
```
### Stage 3: Chunk 生成
```bash
# 生成 chunks
cargo run --bin momentry -- chunk <uuid>
```
### Stage 4: 向量化
```bash
# 向量化 chunks
cargo run --bin momentry -- vectorize <uuid>
# 指定模型
cargo run --bin momentry -- vectorize <uuid> --model sentence-transformers/all-MiniLM-L6-v2
```
---
## 處理模式選項
### --force (強制重新處理)
- 刪除現有的 JSON 檔案
- 從頭開始處理
- 適用於:處理失敗、模型更新、需要重新處理
```bash
# 強制重新處理 YOLO
cargo run --bin momentry -- process <uuid> --modules yolo --force
```
### --resume (續傳)
- 檢查現有 JSON 的進度
- 從中斷點繼續處理
- 適用於:處理中斷、系統崩潰後恢復
```bash
# 從上次中斷點繼續
cargo run --bin momentry -- process <uuid> --resume
```
### 預設行為 (Smart Mode)
- 如果 JSON 完全:跳過
- 如果 JSON 不完整:警告 + 跳過(需要 --resume 或 --force
- 如果 JSON 不存在:處理
```
Output:
ASR: ✓ Already complete, skipping
⚠️ Found incomplete JSON file: /path/to/yolo.json
Progress: 73800/412343 (17.9%)
Use --resume to continue from checkpoint
Use --force to reprocess from scratch
YOLO: ✓ Already complete, skipping
```
---
## 可用模組
| 模組 | 功能 | 輸出 | 用途 |
|------|------|------|------|
| asr | 自動語音辨識 | asr.json | 語音轉文字 |
| cut | 場景偵測 | cut.json | 影片分段 |
| asrx | 說話者分離 | asrx.json | 多人對話分析 |
| yolo | 物體偵測 | yolo.json | 物體辨識 |
| ocr | 文字辨識 | ocr.json | 畫面文字 |
| face | 人臉偵測 | face.json | 人臉辨識 |
| pose | 姿態估計 | pose.json | 人體姿態 |
---
## 向量化模型選擇
```bash
# 預設模型
--model sentence-transformers/all-MiniLM-L6-v2
# 高精度模型
--model sentence-transformers/all-mpnet-base-v2
# 多語言模型
--model sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
# 中文模型
--model sentence-transformers/paraphrase-multilingual-mpnet-base-v2
```
---
## 資料庫儲存
### PostgreSQL (主要關聯式資料庫)
- 影片資訊
- Chunks 資料
- Pre-chunks 資料
- Frames 資料
- 使用者資料
### Qdrant (主要向量資料庫)
- Chunk 向量
- 相似度搜尋
### PGVector (備份向量資料庫)
- Chunk 向量副本
- 備援機制
---
## Pipeline 狀態追蹤
### PostgreSQL 狀態欄位
```sql
-- 影片處理狀態
videos.status: 'pending' | 'processing' | 'completed' | 'failed'
-- 檔案處理狀態
videos.fs_json: true/false
videos.fs_chunks: true/false
videos.fs_vectors: true/false
-- pre_chunks 狀態
pre_chunks.imported: true/false
-- frames 狀態
frames.imported: true/false
-- chunks 狀態
chunks.cleaned: true/false
chunks.vectorized: true/false
```
### 進度查詢 API
```bash
# 查詢處理進度
curl http://localhost:3002/api/v1/progress/{uuid}
# 回應範例
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"overall_progress": 65,
"cpu_percent": 45.2,
"gpu_percent": 98.5,
"memory_mb": 8500,
"processors": [
{"name": "asr", "status": "complete", "progress": 100},
{"name": "cut", "status": "complete", "progress": 100},
{"name": "yolo", "status": "progress", "progress": 45},
{"name": "ocr", "status": "pending", "progress": 0}
]
}
```
---
## 下一步
1. **API 端點** - 支援 --modules 和 --cloud 參數
2. **獨立 Import 命令** - 分離入庫流程
3. **獨立 Chunk 命令** - 分離 chunk 生成
4. **獨立 Vectorize 命令** - 分離向量化流程
5. **模型管理** - 新增、選擇、預覽模型

View File

@@ -1,5 +1,22 @@
# Python 開發規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 新增 RedisPublisher API 文檔 | OpenCode | - |
---
## 概述
本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。
@@ -229,6 +246,63 @@ Pillow>=10.0.0
---
## RedisPublisher 進度發布
### 概述
`redis_publisher.py` 提供統一的進度發布介面,用於 Python 處理器向 Rust 端的 TUI 即時回報進度。
### 基本用法
```python
#!/opt/homebrew/bin/python3.11
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
def process_video(video_path: str, uuid: str):
pub = RedisPublisher(uuid)
pub.info("asr", "Starting ASR processing")
pub.progress("asr", current=50, total=100, message="Processing segment")
pub.complete("asr", "Transcription complete")
```
### API 參考
| 方法 | 說明 | 範例 |
|------|------|------|
| `info(proc, msg)` | 發布資訊訊息 | `pub.info("asr", "Model loaded")` |
| `progress(proc, cur, tot, msg)` | 發布進度 | `pub.progress("asr", 50, 100, "...")` |
| `complete(proc, msg)` | 發布完成 | `pub.complete("asr", "Done")` |
| `error(proc, msg)` | 發布錯誤 | `pub.error("asr", "Failed")` |
| `warning(proc, msg)` | 發布警告 | `pub.warning("asr", "Retry...")` |
| `percentage(proc, pct, msg)` | 發布百分比 | `pub.percentage("asr", 50.5, "50%")` |
### 結構化訊息格式
```python
from redis_publisher import MessageType, ProgressContext
# 使用 Context Manager
with ProgressContext(pub, "asr"):
# 自動發布開始/完成/錯誤
run_asr()
# 帶 extra 資料
pub.progress("asr", current=50, total=100, message="...",
extra={"fps": 30.5, "model": "tiny"})
```
### 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `REDIS_URL` | `redis://:accusys@localhost:6379` | Redis 連線 URL |
| `REDIS_PASSWORD` | `accusys` | Redis 密碼 |
---
## 程式碼規範
### Import 排序

View File

@@ -1,5 +1,22 @@
# Rust 開發規範 - Momentry Core
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 新增 PythonExecutor 模組說明 | OpenCode | - |
---
本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。
## 1. 專案結構
@@ -27,6 +44,7 @@ src/
│ │ └── qdrant_db.rs
│ ├── processor/ # 影片處理器
│ │ ├── mod.rs
│ │ ├── executor.rs # Python 腳本統一執行器 (含超時控制)
│ │ ├── asr.rs # 語音識別
│ │ ├── asrx.rs # 說話者分離
│ │ ├── ocr.rs # 文字辨識
@@ -273,6 +291,47 @@ for line in stderr.lines() {
}
```
### 5.3 PythonExecutor 統一執行器
使用 `PythonExecutor` 封裝 Python 腳本執行邏輯:
```rust
use momentry_core::core::processor::{PythonExecutor, validate_python_env};
// 驗證 Python 環境
fn init() -> Result<()> {
validate_python_env()?;
Ok(())
}
// 使用 Executor 執行腳本
async fn run_script() -> Result<()> {
let executor = PythonExecutor::new()?;
executor.run(
"asr_processor.py",
&["/path/to/video.mp4", "/path/to/output.json"],
Some("job-uuid"),
"ASR",
Some(Duration::from_secs(3600)), // 1小時超時
).await?;
Ok(())
}
```
#### Processor 超時設定
| Processor | 超時 | 說明 |
|----------|------|------|
| ASR | 1 小時 | 語音識別 |
| ASRx | 2 小時 | 說話者分離 |
| YOLO | 2 小時 | 物件偵測 |
| OCR | 2 小時 | 文字辨識 |
| Face | 2 小時 | 人臉偵測 |
| Pose | 2 小時 | 姿態估計 |
| Cut | 1 小時 | 場景偵測 |
---
## 6. Python 與 Node.js 混用規範
@@ -379,7 +438,7 @@ let output = Command::new(venv_python)
| **獨立路徑** | Python 用 venv 路徑Node.js 用 node@22 路徑 |
| **獨立環境** | n8n 服務使用 launchd plist不與 Rust 共享環境 |
| **明確版本** | 所有腳本明確指定直譯器路徑 |
| **PORT 分配** | n8n: 5678/5690, API: 另行分配 |
| **PORT 分配** | n8n: 5678/5679, API: 另行分配 |
#### 6.4.2 環境變數隔離

121
docs/SEARCH_PROMPTS.md Normal file
View File

@@ -0,0 +1,121 @@
# 搜尋範例 Prompt
## 基本搜尋測試
### 1. 簡單關鍵字搜尋
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 5}'
```
### 2. 電影相關詞
```
charade
woody allen
audrey hepburn
classic movie
old time movie
romantic comedy
```
### 3. 場景描述
```
widowed woman
secret agent
chase scene
paris
```
---
## 進階搜尋測試
### 4. 短語搜尋
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "fun plot twists", "limit": 3}'
```
### 5. 情感/描述詞
```
charming performances
hilarious
suspenseful
dramatic
```
### 6. 動作場景
```
running
chase
fighting
dancing
```
---
## 整合範例
### n8n Workflow
```
搜尋詞: "charade"
→ 取得 chunk 的 start_time, end_time
→ 組裝成影片 URL
→ 回傳給用戶
```
### PHP 範例
```php
$searchTerms = ['charade', 'woody', 'audrey', 'classic'];
// 搜尋每個詞
foreach ($searchTerms as $term) {
$ch = curl_init('http://localhost:3002/api/v1/search');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => $term,
'limit' => 5
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
$data = json_decode($response, true);
// 處理結果
foreach ($data['results'] as $result) {
echo "{$result['text']} (score: {$result['score']})\n";
}
}
```
---
## 預期回傳格式
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0006",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526
}
],
"query": "charade"
}
```
---
## 測試檢查清單
- [ ] 基本關鍵字搜尋
- [ ] n8n 整合格式
- [ ] 影片時戳取得
- [ ] 多筆結果排序
- [ ] 不同 chunk_type 搜尋

View File

@@ -1,4 +1,22 @@
# Momentry 服務添加規範 v2.0
# Momentry 服務添加規範 v2.1
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 更新時間 | 2026-03-24 |
| 文件版本 | V2.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V2.1 | 2026-03-24 | 更新 launchctl 命令,使用 bootstrap | OpenCode | OpenCode / big-pickle |
---
## 一、概述
@@ -265,24 +283,44 @@ EOF
### 8.1 基本操作
```bash
# 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 啟動服務 (使用 launchctl bootstrap)
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist
# 停止服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
# 停止服務 (使用 launchctl bootout)
sudo launchctl bootout system/com.momentry.{service}.plist
# 重服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 重新載入服務
sudo launchctl bootout system/com.momentry.{service}.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist
# 查看服務狀態
launchctl list | grep momentry
launchctl list | grep com.momentry
# 查看特定服務狀態
launchctl list | grep com.momentry.{service}
# 查看服務日誌
tail -f /Users/accusys/momentry/log/{service}.log
tail -f /Users/accusys/momentry/log/{service}.error.log
```
### 8.2 批量管理
```bash
# 啟動所有 Momentry 服務
for plist in /Library/LaunchDaemons/com.momentry.*.plist; do
sudo launchctl bootstrap system "$plist"
done
# 停止所有 Momentry 服務
for svc in $(launchctl list | grep com.momentry | awk '{print $3}'); do
sudo launchctl bootout system/$svc 2>/dev/null
done
# 查看所有 Momentry 服務狀態
launchctl list | grep com.momentry
```
### 8.2 故障排除
```bash
@@ -657,3 +695,4 @@ EOF
| 1.0 | 2026-03-15 | 初始版本 |
| 2.0 | 2026-03-15 | 統一 Plist 位置、移除 root/用戶區分、加入運行方式分類 |
| 2.1 | 2026-03-15 | 新增服務備份作業、服務完整刪除作業 |
| 2.1 | 2026-03-24 | 更新 launchctl 命令,使用 `bootstrap`/`bootout` 替代 `load`/`unload` | |

504
docs/SFTPGO_DEMO_USER.md Normal file
View File

@@ -0,0 +1,504 @@
# SFTPGo Demo 用戶指南
## Web 管理介面
**URL**: https://sftpgo.momentry.ddns.net
### 登入方式
| 角色 | 用戶名 | 密碼 |
|------|--------|------|
| **Demo 用戶** | `demo` | `demopassword123` |
### 可用功能
- 瀏覽個人目錄結構
- 上傳、下載檔案
- 查看上傳記錄
---
## 快速連線資訊
| 項目 | 值 |
|------|-----|
| **主機** | `momentry.ddns.net` |
| **SFTP 連接埠** | `2022` |
| **用戶名** | `demo` |
| **密碼** | `demopassword123` |
| **主目錄** | `/demo` |
---
## 連線方式
### 1. 命令列 SFTP
```bash
# 使用密碼連線
sshpass -p "demopassword123" sftp -P 2022 demo@momentry.ddns.net
# 使用金鑰連線 (需先設定)
sftp -P 2022 -i ~/.ssh/id_rsa demo@momentry.ddns.net
```
### 2. FileZilla
1. **主機**: `sftp://momentry.ddns.net`
2. **連接埠**: `2022`
3. **協定**: `SFTP`
4. **登入類型**: `一般`
5. **用戶名**: `demo`
6. **密碼**: `demopassword123`
### 3. Cyberduck (macOS)
1. 選擇 **連線 > 新連線**
2. 協定選擇 **SFTP (SSH File Transfer Protocol)**
3. 伺服器: `momentry.ddns.net`
4. 連接埠: `2022`
5. 使用者名稱: `demo`
6. 密碼: `demopassword123`
### 4. curl 上傳
```bash
curl -u demo:demopassword123 \
-T /path/to/video.mp4 \
sftp://momentry.ddns.net:2022/demo/
```
---
## SFTP 基本操作
### 連線後常用指令
```bash
# 進入互動式模式
sftp demo@momentry.ddns.net -P 2022
# 常用指令
sftp> pwd # 顯示目前目錄
sftp> ls # 列出檔案
sftp> ls -la # 詳細列表
sftp> cd uploads # 切換目錄
sftp> mkdir videos # 建立目錄
sftp> put local.mp4 # 上傳檔案
sftp> get remote.mp4 # 下載檔案
sftp> rm old.mp4 # 刪除檔案
sftp> exit # 斷線
```
### 批次上傳
```bash
# 上傳多個檔案
sshpass -p "demopassword123" sftp -P 2022 demo@momentry.ddns.net <<EOF
cd uploads
put video1.mp4
put video2.mp4
put video3.mp4
bye
EOF
# 使用 glob 上傳
sshpass -p "demopassword123" sftp -P 2022 demo@momentry.ddns.net <<EOF
mput /path/to/videos/*.mp4
bye
EOF
```
---
## 自動上傳腳本
### Bash 腳本
```bash
#!/bin/bash
# upload.sh - 上傳視頻到 Momentry
HOST="momentry.ddns.net"
PORT="2022"
USER="demo"
PASS="demopassword123"
REMOTE_DIR="/demo/uploads"
# 要上傳的檔案
FILE="$1"
if [ -z "$FILE" ]; then
echo "用法: $0 <檔案路徑>"
exit 1
fi
sshpass -p "$PASS" sftp -P $PORT $USER@$HOST <<EOF
mkdir $REMOTE_DIR
cd $REMOTE_DIR
put "$FILE"
bye
EOF
echo "上傳完成: $FILE"
```
使用方式:
```bash
chmod +x upload.sh
./upload.sh /path/to/video.mp4
```
### Python 腳本
```python
#!/usr/bin/env python3
"""上傳檔案到 Momentry SFTP"""
import paramiko
import sys
import os
def upload_file(local_path, remote_dir="/demo/uploads"):
host = "momentry.ddns.net"
port = 2022
username = "demo"
password = "demopassword123"
transport = paramiko.Transport((host, port))
transport.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(transport)
filename = os.path.basename(local_path)
remote_path = f"{remote_dir}/{filename}"
sftp.put(local_path, remote_path)
print(f"已上傳: {filename} -> {remote_path}")
sftp.close()
transport.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python upload_sftp.py <檔案路徑>")
sys.exit(1)
upload_file(sys.argv[1])
```
安裝依賴:
```bash
pip install paramiko
```
---
## WebDAV 替代方案
如果 SFTP 連線有問題,可使用 WebDAV:
| 項目 | 值 |
|------|-----|
| **URL** | `https://momentry.ddns.net/webdav/` |
| **用戶名** | `demo` |
| **密碼** | `demopassword123` |
### curl 使用 WebDAV
```bash
# 上傳
curl -u demo:demopassword123 \
-T video.mp4 \
"https://momentry.ddns.net/webdav/demo/uploads/"
# 下載
curl -u demo:demopassword123 \
-o video.mp4 \
"https://momentry.ddns.net/webdav/demo/uploads/video.mp4"
# 列出目錄
curl -u demo:demopassword123 \
-X PROPFIND \
"https://momentry.ddns.net/webdav/demo/" \
-H "Depth: 1"
```
---
## 故障排除
### 連線被拒絕
```bash
# 檢查 SFTPGo 是否運行
curl -s http://localhost:8080/api/v2/status | jq .status
# 檢查連接埠
nc -zv momentry.ddns.net 2022
```
### 認證失敗
確認密碼是否正確:
```bash
# 測試認證
curl -u demo:demopassword123 \
"https://momentry.ddns.net/webdav/" -I
```
### 權限不足
上傳目錄可能需要先建立:
```bash
sshpass -p "demopassword123" sftp -P 2022 demo@momentry.ddns.net <<EOF
mkdir uploads
mkdir videos
bye
EOF
```
---
## 檔案上傳後自動化
上傳後SFTPGo 會自動:
1. 觸發 Hook 腳本
2. 記錄上傳事件到 `/Users/accusys/sftpgo_test/hook.log`
3. 呼叫 Momentry Core API 註冊視頻
查看上傳日誌:
```bash
tail -f /Users/accusys/sftpgo_test/hook.log
```
---
## 管理手冊
### 管理員帳戶
| 角色 | 用戶名 | 密碼 | 說明 |
|------|--------|------|------|
| **WebAdmin** | `admin` | `Test3200Test3200` | SFTPGo 管理介面 |
**WebAdmin URL**: https://sftpgo.momentry.ddns.net
### Admin 創建方式
根據官方文檔SFTPGo 有兩種方式創建管理員:
#### 方式 1: Web UI (首次設定)
1. 訪問 `http://localhost:8080/web/admin`
2. 如果沒有管理員,會顯示設定畫面
3. 輸入用戶名和密碼創建第一個管理員
#### 方式 2: 自動創建 (推薦)
需要同時滿足以下條件:
1. 配置文件中設定 `"create_default_admin": true`
2. 設定環境變數 `SFTPGO_DEFAULT_ADMIN_USERNAME``SFTPGO_DEFAULT_ADMIN_PASSWORD`
### 設定步驟
#### Step 1: 確保配置文件正確
確認 `/Users/accusys/momentry/etc/sftpgo/sftpgo.json` 中有:
```json
{
"data_provider": {
"create_default_admin": true
},
"httpd": {
"setup": {
"installation_code": "Test3200Test3200"
}
}
}
```
#### Step 2: 更新 plist 加入環境變數
編輯 `/Library/LaunchDaemons/com.momentry.sftpgo.plist`,加入:
```xml
<key>EnvironmentVariables</key>
<dict>
<key>SFTPGO_DEFAULT_ADMIN_USERNAME</key>
<string>admin</string>
<key>SFTPGO_DEFAULT_ADMIN_PASSWORD</key>
<string>Test3200Test3200</string>
</dict>
```
#### Step 3: 重啟 SFTPGo
```bash
launchctl unload homebrew.mxcl.sftpgo
launchctl load homebrew.mxcl.sftpgo
```
#### Step 4: 驗證管理員
```bash
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200"
```
成功回應:
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 1200
}
```
### REST API 認證流程
#### 1. 獲取 Token
```bash
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200"
```
#### 2. 使用 Token 訪問 API
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
# 查看所有用戶
curl -s http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN"
# 查看系統狀態
curl -s http://localhost:8080/api/v2/status \
-H "Authorization: Bearer $TOKEN"
```
### 常用管理操作
#### 查看所有用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" | jq .
```
#### 建立新用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X POST http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"password": "userpassword123",
"email": "user@example.com",
"status": 1,
"home_dir": "/Users/accusys/momentry/var/sftpgo/data/newuser",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}'
```
#### 建立用戶組
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X POST http://localhost:8080/api/v2/groups \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "editors",
"description": "Editor group with upload permissions"
}'
```
#### 刪除用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X DELETE http://localhost:8080/api/v2/users/username \
-H "Authorization: Bearer $TOKEN"
```
#### 修改用戶密碼
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X PUT http://localhost:8080/api/v2/users/demo \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"password": "newpassword456"
}'
```
### 重要設定
| 設定項目 | 值 | 說明 |
|----------|-----|------|
| **SFTP 連接埠** | `2022` | SSH 檔案傳輸協定 |
| **HTTP/WebDAV 連接埠** | `8080` | 內部 HTTP 服務 |
| **WebAdmin 連接埠** | `8080` | 管理介面 (`/web/admin`) |
| **WebClient 連接埠** | `8080` | 用戶介面 (`/web/client`) |
| **資料庫** | PostgreSQL | 用戶和設定儲存 |
| **Hook 腳本** | `/Users/accusys/sftpgo_test/register_hook.sh` | 上傳後自動化處理 |
| **安裝碼** | `Test3200Test3200` | 首次設定管理員所需 |
| **create_default_admin** | `true` | 自動創建管理員 |
| **Token 有效期** | 1200 秒 (20分鐘) | JWT 過期時間 |
### 用戶目錄結構
所有 SFTPGo 用戶資料統一存放在 `/Users/accusys/momentry/var/sftpgo/data/` 目錄下:
| 用戶 | 資料夾路徑 | 密碼 | 說明 |
|------|------------|------|------|
| **demo** | `/Users/accusys/momentry/var/sftpgo/data/demo` | `demopassword123` | Demo 用戶上傳目錄 |
| **momentry** | `/Users/accusys/momentry/var/sftpgo/data/momentry` | `momentry123` | Momentry 系統用戶 |
| **warren** | `/Users/accusys/momentry/var/sftpgo/data/warren` | `warren123` | 其他用戶 |
### API Token 獲取方式
```bash
# 注意:使用 GET 而非 POST
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq .access_token
```
### 故障排除
| 問題 | 解決方案 |
|------|----------|
| 無法獲取 Token | 確認環境變數已正確設定並重啟 SFTPGo |
| SFTP 連線被拒絕 | 檢查 SFTPGo 服務: `launchctl list \| grep sftpgo` |
| 無法登入 WebAdmin | 確認 admin 用戶存在: 檢查 plist 中環境變數是否正確 |
| 上傳失敗 | 檢查 Hook 腳本: `tail -f /Users/accusys/momentry/log/sftpgo.error.log` |
| 權限不足 | 檢查用戶權限或更新 `permissions` 設定 |
| API 返回 401 | Token 過期,需重新獲取: `curl -X POST .../token -u "admin:pass"` | |
---
## 安全注意事項
- **密碼保護**: `demopassword123` 為 demo 帳戶密碼
- **限制存取**: Demo 用戶只能訪問 `/demo` 目錄
- **監控**: 所有上傳都有日誌記錄
- **生產環境**: 正式環境應使用更強的密碼和金鑰認證

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
# 統一會員系統 + 影片歸屬追蹤實作計畫
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-24 |
| 文件版本 | V1.0 |
| 狀態 | 待確認 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-24 | 創建實作計畫 | OpenCode |
---
## 1. 背景與目標
### 1.1 現有問題
目前 Momentry 生態系統中,各服務有獨立的用戶管理:
| 服務 | 用戶系統 | 問題 |
|------|----------|------|
| WordPress | wp_users (2 admin) | 無會員系統,無 API 認證 |
| SFTPGo | users 表 (3 users) | 獨立管理 |
| n8n | users 表 | 獨立管理 |
| Gitea | `user` 表 | 獨立管理 |
| Momentry Core | api_keys (未啟用) | 無 user 關聯 |
**問題**
1. 無法追蹤影片歸屬(誰上傳的影片)
2. 無法實作 per-user 配額管理
3. API 端點全部公開,無認證
4. 用戶創建需要多處操作
### 1.2 目標
建立統一的會員系統,讓 WordPress 成為唯一登入入口:
```
┌─────────────────────────────────────────────────────────────────┐
│ 目標架構 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WordPress (會員系統) │
│ │ │
│ ├─► SFTPGo (檔案上傳) │
│ ├─► Momentry Core (影片處理) │
│ └─► n8n (自動化流程) │
│ │
│ 統一的 user_id 追蹤 │
│ │ │
│ └─► videos 表關聯 user_id │
│ └─► monitor_jobs 表關聯 user_id │
│ └─► per-user 配額管理 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 2. 現有系統分析
### 2.1 WordPress
| 項目 | 狀態 |
|------|------|
| 安裝插件 | Elementor, Akismet, Code Snippets, All-in-One WP Migration |
| 用戶表 | wp_users (2 users: wp_user, sc_demo) |
| 會員插件 | 無 |
| REST API | 標準端點 (`/wp-json/wp/v2/users`) |
| 認證方式 | Cookie / Application Passwords |
| JWT | 無 |
### 2.2 SFTPGo
| 項目 | 值 |
|------|-----|
| 用戶數 | 3 (demo, warren, momentry) |
| API | REST API v2 (`/api/v2/users`) |
| Admin | admin:Test3200Test3200 |
| Hook | `/Users/accusys/sftpgo_test/register_hook.sh` |
### 2.3 Momentry Core
| 項目 | 狀態 |
|------|------|
| api_keys 表 | 已存在 |
| users 表 | 不存在 |
| videos.user_id | 不存在 |
| API 認證 | 未啟用(所有端點公開) |
---
## 3. 實作計畫
### Phase 1: WordPress 認證機制啟用
#### 1.1 啟用 Application Passwords
**WordPress 5.6+ 內建功能**,無需額外插件。
```php
// wp-config.php (如需自訂設定)
define('WP APPLICATION_PASSWORDS_ENABLED', true);
```
**使用方式**
```bash
# Basic Auth 格式
curl -X POST "https://wp.momentry.ddns.net/wp-json/wp/v2/users" \
-u "username:application_password" \
-H "Content-Type: application/json" \
-d '{"username": "newuser", "email": "user@example.com", "password": "password"}'
```
#### 1.2 測試 WordPress REST API
```bash
# 取得用戶列表(需要 admin 權限)
curl -s -u "wp_user:xxxx xxxx xxxx xxxx xxxx xxxx" \
"https://wp.momentry.ddns.net/wp-json/wp/v2/users"
# 創建新用戶
curl -X POST "https://wp.momentry.ddns.net/wp-json/wp/v2/users" \
-u "wp_user:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{
"username": "testuser",
"email": "test@example.com",
"password": "TestPass123!",
"roles": ["subscriber"]
}'
```
---
### Phase 2: 資料庫結構調整
#### 2.1 新增 users 表Momentry Core
```sql
-- migrations/002_user_management.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
wordpress_id BIGINT UNIQUE NOT NULL,
username VARCHAR(60) NOT NULL,
email VARCHAR(100) NOT NULL,
api_key_hash VARCHAR(64),
quota_size BIGINT DEFAULT 10737418240, -- 10GB
quota_used BIGINT DEFAULT 0,
sftpgo_username VARCHAR(60),
status VARCHAR(20) DEFAULT 'active', -- active, suspended, deleted
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_users_wordpress_id ON users(wordpress_id);
CREATE INDEX idx_users_username ON users(username);
-- videos 表新增 user_id
ALTER TABLE videos ADD COLUMN user_id BIGINT REFERENCES users(id);
CREATE INDEX idx_videos_user_id ON videos(user_id);
-- monitor_jobs 表新增 user_id
ALTER TABLE monitor_jobs ADD COLUMN user_id BIGINT REFERENCES users(id);
CREATE INDEX idx_monitor_jobs_user_id ON monitor_jobs(user_id);
-- api_keys 表新增 user_id
ALTER TABLE api_keys ADD COLUMN user_id BIGINT REFERENCES users(id);
```
#### 2.2 更新 api_keys 表結構
```sql
-- 新增欄位
ALTER TABLE api_keys ADD COLUMN user_id BIGINT REFERENCES users(id);
ALTER TABLE api_keys ADD COLUMN wordpress_id BIGINT;
ALTER TABLE api_keys ADD COLUMN sftpgo_username VARCHAR(60);
```
---
### Phase 3: API 認證中介層
#### 3.1 中介層設計
```rust
// src/api/middleware/auth.rs
#[derive(Clone)]
pub struct AuthState {
pub db: Arc<PostgresDb>,
pub cache: Arc<RedisCache>,
}
pub async fn auth_middleware(
req: Request,
next: Next,
state: AuthState,
) -> Result<Response, StatusCode> {
// 1. 從 Header 提取 API Key
// Header: X-API-Key: muser_xxx
// 或: Authorization: Bearer muser_xxx
// 2. 驗證並取得 user_id
let user_id = validate_api_key(&req, &state).await?;
// 3. 附加到 request extensions
req.extensions_mut().insert(UserContext { user_id });
// 4. 執行 handler
next.call(req).await
}
#[derive(Clone)]
pub struct UserContext {
pub user_id: i64,
}
```
#### 3.2 API Key 格式更新
```
新格式: muser_{uuid}_{timestamp}_{random}_{user_id_hash}
```
| 欄位 | 說明 |
|------|------|
| 前綴 | `muser_` = User 類型 |
| uuid | 唯一識別碼 |
| timestamp | 創建時間戳 |
| random | 隨機字串 |
| user_id_hash | 壓縮的 user_id |
---
### Phase 4: 更新 Register API
#### 4.1 修改 register 端點
```rust
// POST /api/v1/register
pub async fn register(
State(state): State<ApiState>,
Json(req): Json<RegisterRequest>,
Extension(ctx): Extension<UserContext>, // 新增
) -> Result<Json<RegisterResponse>, StatusCode> {
// ... 現有邏輯 ...
// 驗證用戶配額
let user = state.db.get_user(ctx.user_id).await?;
if user.quota_used + file_size > user.quota_size {
return Err(StatusCode::FORBIDDEN);
}
// 關聯 user_id 到影片
let video_uuid = state.db.create_video(req, Some(ctx.user_id)).await?;
// 建立 processing job帶 user_id
state.db.create_monitor_job(
job_type: "auto_ingestion",
video_uuid,
user_id: Some(ctx.user_id),
processors: vec!["asr", "cut", "yolo", "ocr", "face", "pose"],
).await?;
Ok(Json(RegisterResponse { uuid: video_uuid }))
}
```
---
### Phase 5: n8n 自動化流程
#### 5.1 用戶註冊 Workflow
```
┌─────────────────────────────────────────────────────────────────┐
│ WordPress 用戶註冊自動化流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Trigger: Webhook (或 WordPress Plugin) │
│ │
│ Step 1: 驗證管理員權限 │
│ └─► 檢查 WordPress REST API 憑證 │
│ │
│ Step 2: 在 Momentry Core 建立用戶記錄 │
│ └─► POST /api/v1/admin/users │
│ └─► 產生 API Key │
│ │
│ Step 3: 在 SFTPGo 建立用戶 │
│ └─► POST /api/v2/users (SFTPGo API) │
│ └─► 設定 home_dir: /data/{username} │
│ │
│ Step 4: 更新用戶記錄 │
│ └─► 關聯 sftpgo_username │
│ │
│ Step 5: 發送歡迎 email │
│ └─► 包含 SFTP 登入資訊 │
│ └─► 包含 API Key │
│ │
└─────────────────────────────────────────────────────────────────┘
```
#### 5.2 SFTPGo Hook 更新
```bash
# /Users/accusys/sftpgo_test/register_hook.sh
# 修改為傳遞 user_id
curl -X POST "http://localhost:3002/api/v1/register" \
-H "X-API-Key: ${SFTPGO_USER_API_KEY}" \
-H "X-SFTPGo-User: ${SFTPGO_USERNAME}" \
-d "{\"path\": \"${SFTPGO_FILE_PATH}\"}"
```
---
## 4. 實作優先順序
| Phase | 任務 | 複雜度 | 優先級 | 預估工時 |
|-------|------|--------|--------|----------|
| 1.1 | 測試 WordPress Application Passwords | 低 | P0 | 1h |
| 1.2 | 為 WordPress 產生 Application Password | 低 | P0 | 0.5h |
| 2.1 | 建立 users 表 migration | 中 | P0 | 2h |
| 2.2 | 更新 videos, monitor_jobs 表 | 低 | P0 | 1h |
| 3.1 | 實作 API auth middleware | 中 | P0 | 4h |
| 3.2 | 更新 register API 接受 user_id | 低 | P0 | 2h |
| 4 | 建立 admin users API | 中 | P1 | 4h |
| 5.1 | 建立 n8n 用戶註冊 workflow | 中 | P1 | 6h |
| 5.2 | 更新 SFTPGo hook | 低 | P1 | 2h |
| 6 | 實作配額管理 | 中 | P2 | 4h |
| 7 | 測試與驗證 | 中 | P2 | 4h |
**總預估工時**: ~30.5h
---
## 5. 待確認事項
### 5.1 WordPress 用戶建立方式
- [ ] 手動在 wp-admin 建立?還是透過 Elementor 表單?
- [ ] 是否需要 email 驗證?
- [ ] 初始角色設定subscriber / contributor
### 5.2 API Key 格式
- [ ] 維持現有 `muser_` 前綴格式?
- [ ] 還是建立新的用戶專用 key 格式?
- [ ] 是否需要 JWT token
### 5.3 SFTPGo 整合
- [ ] 每個 WordPress 用戶對應一個 SFTPGo 用戶?
- [ ] home_dir 命名規則?(如 `data/{wordpress_username}`
- [ ] SFTPGo 配額是否同步?
### 5.4 配額管理
- [ ] 每人預設 10GB 空間?
- [ ] 超出配額如何處理?(阻止上傳 / 警告)
- [ ] 配額用完後是否暫停 SFTPGo 用戶?
### 5.5 資料同步
- [ ] WordPress 用戶刪除時是否同步刪除其他系統?
- [ ] 用戶停權時的處理流程?
---
## 6. 參考文件
### 內部文件
| 文件 | 用途 |
|------|------|
| `docs/PENDING_ISSUES.md` | 待解決問題追蹤 |
| `docs/API_KEY_MANAGEMENT.md` | API Key 管理系統 |
| `docs/API_REFERENCE.md` | API 端點參考 |
| `docs/SFTPGO_DEMO_USER.md` | SFTPGo 用戶設定 |
| `docs/N8N_INTEGRATION_GUIDE.md` | n8n 整合指南 |
| `docs/INSTALL_WORDPRESS.md` | WordPress 安裝指南 |
### 外部資源
| 資源 | URL |
|------|-----|
| WordPress REST API | https://developer.wordpress.org/rest-api/ |
| WordPress Application Passwords | https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/#authentication-plugins |
| SFTPGo REST API | https://docs.sftpgo.com/latest/rest-api/ |
---
## 7. 附錄
### A. 現有使用者資料
#### WordPress (wp_users)
| ID | user_login | user_email | display_name |
|----|------------|------------|--------------|
| 1 | wp_user | marketing@accusys.com.tw | wp_user |
| 2 | sc_demo | susan.cheng@accusys.com.tw | Susan Cheng |
#### SFTPGo (users)
| username | email | home_dir | status |
|----------|-------|----------|--------|
| demo | demo@momentry.local | /Users/accusys/momentry/var/sftpgo/data/demo | Active |
| warren | warren@momentry.local | /Users/accusys/momentry/var/sftpgo/data/warren | Active |
| momentry | system@momentry.local | /Users/accusys/momentry/var/sftpgo/data/momentry | Active |
### B. 服務端口
| 服務 | Port | URL |
|------|------|-----|
| WordPress | 9000 (PHP-FPM) | https://wp.momentry.ddns.net |
| SFTPGo | 8080 | http://localhost:8080 |
| Momentry API | 3002 | http://localhost:3002 |
| n8n | 5678 | http://localhost:5678 |

469
docs/USER_MANUAL.md Normal file
View File

@@ -0,0 +1,469 @@
# Momentry 使用手冊
| 項目 | 內容 |
|------|------|
| 版本 | V1.0 |
| 日期 | 2026-03-21 |
| 目標讀者 | 系統管理員、開發者 |
---
## 目錄
1. [快速開始](#1-快速開始)
2. [安裝與設定](#2-安裝與設定)
3. [CLI 命令參考](#3-cli-命令參考)
4. [影片管理](#4-影片管理)
5. [API Key 管理](#5-api-key-管理)
6. [第三方整合](#6-第三方整合)
7. [監控與維護](#7-監控與維護)
8. [疑難排解](#8-疑難排解)
---
## 1. 快速開始
### 1.1 最小啟動流程
```bash
# 1. 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
# 2. 設定環境變數
source .env
# 3. 啟動 API 伺服器
momentry server --host 127.0.0.1 --port 3000
# 4. 建立 API Key
momentry api-key create my-first-key --key-type user --ttl 90
```
### 1.2 驗證安裝
```bash
# 檢查系統狀態
curl http://localhost:3002/health
# 檢查版本
momentry --help
```
---
## 2. 安裝與設定
### 2.1 環境需求
| 項目 | 需求 |
|------|------|
| 作業系統 | macOS (Apple Silicon) |
| Rust | 1.70+ |
| PostgreSQL | 15+ |
| Redis | 7+ |
| Python | 3.11+ (用於 AI 處理) |
### 2.2 安裝步驟
```bash
# 1. 複製專案
git clone <repository-url>
cd momentry_core_0.1
# 2. 編譯
cargo build --release
# 3. 安裝到系統
cp target/release/momentry /usr/local/bin/
```
### 2.3 環境變數設定
建立 `.env` 檔案:
```bash
# Database
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# Redis
REDIS_URL=redis://:accusys@localhost:6379
# Gitea (選用)
GITEA_URL=http://localhost:3000
# n8n (選用)
N8N_URL=https://n8n.momentry.ddns.net
# API Server
API_HOST=127.0.0.1
API_PORT=3000
# 監控目錄
WATCH_DIRECTORIES=~/Videos
```
---
## 3. CLI 命令參考
### 3.1 一般命令
| 命令 | 說明 |
|------|------|
| `momentry --help` | 顯示幫助 |
| `momentry server` | 啟動 API 伺服器 |
| `momentry register <path>` | 註冊影片 |
| `momentry process <uuid>` | 處理影片 |
| `momentry query <text>` | RAG 查詢 |
### 3.2 影片管理
```bash
# 註冊影片
momentry register /path/to/video.mp4
# 處理影片
momentry process <uuid>
# 生成縮圖
momentry thumbnails <uuid> --count 6
# 查看狀態
momentry status <uuid>
```
### 3.3 API Key 管理
```bash
# 建立 Key
momentry api-key create <name> --key-type <type> --ttl <days>
# 列出 Keys
momentry api-key list
# 驗證 Key
momentry api-key validate --key <key>
# 撤銷 Key
momentry api-key revoke --key <key>
# 請求輪換
momentry api-key rotate --key <key>
# 統計資訊
momentry api-key stats
```
### 3.4 Gitea 整合
```bash
# 建立 Token
momentry gitea create \
--username <user> \
--password <pwd> \
--token-name <name> \
--scopes "read:repository,write:repository"
# 列出 Tokens
momentry gitea list --username <user> --password <pwd>
# 刪除 Token
momentry gitea delete \
--username <user> \
--password <pwd> \
--token-name <name>
```
### 3.5 n8n 整合
```bash
# 建立 API Key
momentry n8n create \
--api-key <existing-key> \
--label <name> \
--expires-in-days 90
# 列出 Keys
momentry n8n list --api-key <key>
# 刪除 Key
momentry n8n delete --api-key <key> --label <name>
```
### 3.6 備份管理
```bash
# 列出備份
momentry backup list
# 清理舊備份
momentry backup cleanup --days 30
```
---
## 4. 影片管理
### 4.1 影片生命週期
```
上傳 → 註冊 → 處理 → 儲存 → 查詢
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
檔案 資料庫 AI分析 向量庫 RAG
```
### 4.2 註冊影片
```bash
# 自動偵測格式
momentry register ~/Videos/my-video.mp4
# 輸出:
# UUID: a1b2c3d4e5f6g7h8
# Duration: 120.5s
# Resolution: 1920x1080
```
### 4.3 處理流程
處理包含以下階段:
| 階段 | 說明 | 時間 (約) |
|------|------|-----------|
| Probe | 影片資訊分析 | 5s |
| ASR | 語音辨識 | 視長度 |
| OCR | 文字辨識 | 視長度 |
| YOLO | 物件偵測 | 視長度 |
| Cut | 場景切割 | 30s |
| Chunk | 內容分段 | 10s |
| Vector | 向量化 | 20s |
### 4.4 查詢影片
```bash
# RAG 查詢
momentry query "影片中有什麼內容?"
# 取得特定影片
momentry resolve <uuid>
```
---
## 5. API Key 管理
### 5.1 Key 類型
| 類型 | 前綴 | 用途 | 預設 TTL |
|------|------|------|----------|
| System | `msys_` | 系統內部 | 365 天 |
| User | `muser_` | 個人用戶 | 90 天 |
| Service | `msvc_` | 服務間通訊 | 180 天 |
| Integration | `mint_` | 第三方整合 | 30 天 |
| Emergency | `memg_` | 緊急存取 | 1 天 |
### 5.2 建立 Key
```bash
# 一般 Key
momentry api-key create my-service --key-type service --ttl 90
# 緊急 Key (24小時有效)
momentry api-key create emergency-access --key-type emergency
# 輸出:
# ✅ API Key created successfully!
# Key ID: msvc_xxxxxxxx
# API Key: msvc_xxxxxxxx_xxxxx_xxxxx
# Expires: 2026-06-19
```
### 5.3 Key 生命週期
```
建立 → 使用 → 過期/撤銷 → 清理
│ │ │ │
▼ ▼ ▼ ▼
資料庫 驗證 停用 定期刪除
```
### 5.4 安全建議
| 建議 | 說明 |
|------|------|
| 定期輪換 | 每 90 天更新 Key |
| 最小權限 | 只授予必要權限 |
| 監控使用 | 定期檢查使用統計 |
| 及時撤銷 | 異常時立即撤銷 |
---
## 6. 第三方整合
### 6.1 Gitea
```bash
# 建立 CI/CD 用 Token
momentry gitea create \
--username admin \
--password "your-password" \
--token-name "ci-pipeline" \
--scopes "read:repository,write:repository"
# 在 CI 中使用
export GITEA_TOKEN="token-sha1-value"
curl -H "Authorization: token $GITEA_TOKEN" \
http://localhost:3000/api/v1/user
```
### 6.2 n8n
```bash
# 建立工作流用 Key
momentry n8n create \
--api-key "existing-n8n-key" \
--label "workflow-key" \
--expires-in-days 90
# 在 n8n 中使用
# HTTP Request Header: X-N8N-API-Key: <key>
```
### 6.3 Webhook 通知
```bash
# 設定 Webhook
export WEBHOOK_URL="https://n8n.example.com/webhook/alerts"
export WEBHOOK_EVENTS="anomaly_detected,key_expired"
```
---
## 7. 監控與維護
### 7.1 系統監控
```bash
# 檢查服務狀態
ps aux | grep momentry
ps aux | grep postgres
redis-cli -a accusys ping
# 檢查日誌
tail -f /Users/accusys/momentry/log/momentry.log
tail -f /Users/accusys/momentry/log/redis.log
```
### 7.2 資料庫維護
```bash
# 檢查資料庫大小
psql -U accusys -d momentry -c "SELECT pg_size_pretty(pg_database_size('momentry'));"
# 清理過期記錄
momentry api-key stats # 檢查統計
# 定期清理由系統自動執行
```
### 7.3 備份
```bash
# 手動備份 PostgreSQL
pg_dump -U accusys momentry > backup_$(date +%Y%m%d).sql
# 恢復備份
psql -U accusys momentry < backup_20260321.sql
```
---
## 8. 疑難排解
### 8.1 常見問題
#### Q: 無法連接資料庫
```bash
# 檢查 PostgreSQL 狀態
pg_isready -h localhost -p 5432
# 檢查連線
psql -U accusys -d momentry -c "SELECT 1;"
```
#### Q: Redis 連線失敗
```bash
# 檢查 Redis 狀態
redis-cli -a accusys ping
# 檢查認證
redis-cli -a accusys INFO server | grep redis_version
```
#### Q: API Key 驗證失敗
```bash
# 檢查 Key 狀態
momentry api-key validate --key "your-key"
# 檢查是否過期
momentry api-key list
```
### 8.2 錯誤碼對照
| 錯誤碼 | 說明 | 解決方式 |
|--------|------|----------|
| `E001` | 資料庫連線失敗 | 檢查 PostgreSQL |
| `E002` | Redis 連線失敗 | 檢查 Redis |
| `E003` | API Key 無效 | 重新建立 Key |
| `E004` | 影片不存在 | 檢查 UUID |
| `E005` | 處理失敗 | 檢查日誌 |
### 8.3 日誌位置
| 日誌 | 路徑 |
|------|------|
| Momentry | `/Users/accusys/momentry/log/momentry.log` |
| PostgreSQL | `/Users/accusys/momentry/log/postgresql.log` |
| Redis | `/Users/accusys/momentry/log/redis.log` |
| Gitea | `/Users/accusys/momentry/log/gitea.log` |
---
## 附錄
### A. 完整命令列表
```bash
momentry --help
momentry register --help
momentry process --help
momentry api-key --help
momentry gitea --help
momentry n8n --help
momentry backup --help
```
### B. 環境變數總覽
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `DATABASE_URL` | `postgres://accusys@localhost:5432/momentry` | PostgreSQL |
| `REDIS_URL` | `redis://:accusys@localhost:6379` | Redis |
| `GITEA_URL` | `http://localhost:3000` | Gitea |
| `N8N_URL` | `https://n8n.momentry.ddns.net` | n8n |
| `API_HOST` | `127.0.0.1` | API 主機 |
| `API_PORT` | `3000` | API 埠號 |
### C. 相關文件
| 文件 | 說明 |
|------|------|
| `docs/API_CURL_EXAMPLES.md` | API curl 範例 |
| `docs/N8N_INTEGRATION_GUIDE.md` | n8n 整合指南 |
| `docs/API_KEY_MANAGEMENT.md` | API Key 設計 |
| `CHANGELOG.md` | 版本記錄 |

245
docs/VERSION_MANAGEMENT.md Normal file
View File

@@ -0,0 +1,245 @@
# Momentry Core 版本管理規範
## 1. 版本與通訊埠對照表
| 版本 | Binary | Port | Redis Prefix | 用途 |
|------|--------|------|--------------|------|
| **Production** | `momentry` | **3002** | `momentry:` | 正式環境 |
| **Development** | `momentry_playground` | **3003** | `momentry_dev:` | 開發測試 |
### 通訊埠嚴禁事項
- ❌ 開發版嚴禁使用 3002
- ❌ 任何 `cargo run` 直接啟動的 server 嚴禁綁定 3002
- ❌ Debug build 嚴禁部署到 3002
---
## 2. 開發環境隔離原則
### 2.1 開發流程
```bash
# 永遠在 3003 開發測試
cd /Users/accusys/momentry_core_0.1
# 開發版啟動 (3003)
cargo run --bin momentry_playground -- server
# 或
cargo run --bin momentry -- server --port 3003
```
### 2.2 測試完成後
1. 確認所有功能在 3003 正常運作
2. 進行 `cargo clippy --lib` 檢查
3. 進行 `cargo test --lib` 測試
4. 確認後才能進行 release
### 2.3 環境變數隔離
```bash
# Development
export MOMENTRY_SERVER_PORT=3003
export MOMENTRY_REDIS_PREFIX=momentry_dev:
# Production (launchd 管理,勿手動設定)
# MOMENTRY_SERVER_PORT=3002
# MOMENTRY_REDIS_PREFIX=momentry:
```
---
## 3. Release 版本管理
### 3.1 Release 前檢查清單
```
□ 開發版 (3003) 功能測試完成
□ cargo clippy --lib 通過
□ cargo test --lib 通過
□ cargo fmt -- --check 通過
□ 所有修改已 commit 到 Gitea
```
### 3.2 Release 流程
```bash
# 1. 確保目前是乾淨的工作目錄
git status
# 2. 備份當前 production binary
BACKUP_DIR="/Users/accusys/momentry/backup/bin"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp /Users/accusys/momentry/bin/momentry "${BACKUP_DIR}/momentry_${TIMESTAMP}"
# 3. 停止 production server
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 或
pkill -f "target/release/momentry server"
# 4. 編譯 release 版本
cargo build --release --bin momentry
# 5. 部署到正式位置
cp target/release/momentry /Users/accusys/momentry/bin/momentry
# 6. 啟動 production server
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 7. 驗證
curl http://localhost:3002/health
```
### 3.3 Backup 存放位置
```
/Users/accusys/momentry/backup/bin/
├── momentry_20260325_143000 (backup)
├── momentry_20260324_100000
├── momentry_20260323_090000
└── ...
```
---
## 4. Gitea 版本控制
### 4.1 Commit 規範
```
feat: 新功能
fix: 錯誤修復
refactor: 重構
docs: 文件更新
chore: 杂项
test: 测试
```
### 4.2 Release Tag 規範
```bash
# 建立 release tag
git tag -a v0.1.1 -m "Release v0.1.1 - API Key Authentication"
git push origin v0.1.1
```
### 4.3 版本號命名
```
v{major}.{minor}.{patch}
│ │ └── Patch version (bug fix)
│ └───────── Minor version (新功能)
└──────────────── Major version (破壞性變更)
```
### 4.4 Gitea Release 建立
1. 在 Gitea Repo > Releases > New Release
2. 選擇對應的 Tag
3. 填寫 Release Notes
4. 上傳 compiled binary如需要
---
## 5. 服務管理
### 5.1 Production Service (launchd)
```bash
# Plist 位置
/Library/LaunchDaemons/com.momentry.api.plist
# 管理指令
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist # 啟動
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist # 停止
sudo launchctl list | grep momentry # 狀態
```
### 5.2 緊急回滾
```bash
# 1. 停止當前服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 2. 恢復上一個 backup
BACKUP_FILE=$(ls -t /Users/accusys/momentry/backup/bin/ | head -1)
cp "/Users/accusys/momentry/backup/bin/${BACKUP_FILE}" /Users/accusys/momentry/bin/momentry
# 3. 重啟服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 4. 驗證
curl http://localhost:3002/health
```
---
## 6. 快速參考卡片
### Development
```bash
# 啟動開發版
cd /Users/accusys/momentry_core_0.1
cargo run --bin momentry_playground -- server
# 或手動指定 port
cargo run --bin momentry -- server --port 3003
# 測試端點
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3003/api/v1/jobs
```
### Production
```bash
# 查看狀態
sudo launchctl list | grep momentry
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 查看日誌
tail -f /Users/accusys/momentry/log/momentry_release.log
```
### 常用指令
```bash
# 檢查 port 使用
lsof -i :3002 # Production
lsof -i :3003 # Development
# 檢查 process
ps aux | grep momentry | grep server
# 停止所有 momentry server
pkill -9 -f "momentry.*server"
```
---
## 7. 禁止事項
| 項目 | 說明 |
|------|------|
| ❌ 禁止在 3002 測試 | 3002 是 Production嚴禁用於測試 |
| ❌ 禁止覆蓋 production binary | 使用 backup + deploy 流程 |
| ❌ 禁止跳過測試直接 release | 必須完成檢查清單 |
| ❌ 禁止在未備份的情況下部署 | 每次部署前必須備份 |
---
## 8. 疑難排解
### Q: 3002 無法綁定怎麼辦?
```bash
# 檢查誰在使用
lsof -i :3002
# 停止舊的 server
pkill -9 -f "momentry.*server"
```
### Q: 如何確認使用的是哪個版本?
```bash
# 檢查 binary 位置和版本
file $(which momentry)
./target/release/momentry --version 2>/dev/null || echo "No version flag"
```
### Q: 如何確認有沒有 API Key 驗證?
```bash
# 沒有 API Key 應該返回 401
curl -s http://localhost:3002/api/v1/jobs
# HTTP/1.1 401 Unauthorized
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
# YOLO Resume 功能整合規劃
## 現有資源
### 1. video_yolo_player 專案
**位置**: `/Users/accusys/video_yolo_player/video_yolo_object_prescan.py`
**功能**:
- ✅ Ctrl+C 暫停並保存進度
- ✅ 自動從上一次繼續 (自動偵測 last_processed_frame)
- ✅ 可配置自動保存間隔 (預設 30 秒)
- ✅ 完整 metadata 追蹤 (處理時間、檢測數量等)
- ✅ 互動式詢問是否繼續
### 2. momentry_core_0.1 專案
**位置**: `/Users/accusys/momentry_core_0.1/scripts/yolo_processor.py`
## 整合狀態
| 項目 | 狀態 | 完成日期 |
|------|------|----------|
| Python script 整合 | ✅ 已完成 | 2026-03-22 |
| Rust JSON 格式支援 | ✅ 已完成 | 2026-03-22 |
| Auto-save 功能 (時間) | ✅ 已完成 | 2026-03-22 |
| Auto-save 功能 (frame) | ✅ 已完成 | 2026-03-22 |
| Ctrl+C 信號處理 | ✅ 已完成 | 2026-03-22 |
| --force 參數 | ✅ 已完成 | 2026-03-22 |
| --auto-save-frames 參數 | ✅ 已完成 | 2026-03-22 |
## 已實作功能
### momentry_core_0.1/scripts/yolo_processor.py
```python
# 新增功能:
def load_existing_data(output_file) # 載入現有資料
def save_detection_data(output_file) # 保存進度
def signal_handler(signum, frame) # Ctrl+C 處理
# 新增參數:
--auto-save 30 # 自動保存間隔 (秒)
--auto-save-frames 300 # 每 N frames 保存一次 (先到為準)
--force # 強制從頭開始
```
### 輸出格式
```json
{
"metadata": {
"video_path": "...",
"fps": 24.0,
"total_frames": 1000,
"status": "in_progress" | "completed" | "interrupted",
"total_detections": 5000,
"processing_time": 120.5,
"auto_save_count": 4,
"auto_save_interval": 30,
"auto_save_frames": 300,
"last_saved_frame": 12500,
"last_saved_at": "ISO timestamp"
},
"frames": {
"1": { "frame_number": 1, "time_seconds": 0.0, "detections": [...] },
"2": { "frame_number": 2, "time_seconds": 0.042, "detections": [...] }
}
}
```
### Auto-save 觸發條件
**滿足以下任一條件就會寫入磁碟:**
1. 距離上次儲存已超過 `--auto-save` 秒數
2. 距離上次儲存已處理超過 `--auto-save-frames` 個 frames
### 使用方式
```bash
# 第一次執行 (預設: 30秒 或 300 frames)
python yolo_processor.py video.mp4 output.json --uuid "abc123"
# 中斷後繼續 (自動偵測)
python yolo_processor.py video.mp4 output.json --uuid "abc123"
# 強制從頭開始
python yolo_processor.py video.mp4 output.json --force
# 自訂自動保存間隔
python yolo_processor.py video.mp4 output.json --auto-save 10 --auto-save-frames 100
```
### Rust 端支援
`check_json_completeness()` 已更新以支援新格式:
- 讀取 `frames` dict 的最後一個 key 作為 `last_processed_frame`
- 檢查 `metadata.status` 判斷是否完成
## 待測試項目
- [ ] 實際執行中斷後繼續
- [ ] 驗證 large video (如 Old_Time_Movie_Show) 繼續處理
- [ ] 驗證 Rust --resume 參數正確傳遞

View File

@@ -0,0 +1,110 @@
{
"name": "Momentry Video Search - Simple",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search-simple",
"responseMode": "lastNode",
"options": {}
},
"id": "webhook-simple",
"name": "Webhook (Simple)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300],
"webhookId": "video-search-simple"
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "={{ $json.body }}"
},
{
"name": "limit",
"value": 5
}
]
},
"options": {
"headers": {
"Content-Type": "application/json"
}
}
},
"id": "http-search",
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [500, 300]
},
{
"parameters": {
"jsCode": "// 處理 Momentry 搜尋結果\nconst data = $input.first().json;\nconst hits = data.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n success: false,\n message: '找不到相關結果',\n query: data.query\n }\n };\n}\n\n// 格式化結果\nconst formattedResults = hits.map((hit, idx) => ({\n index: idx + 1,\n id: hit.id,\n title: hit.title,\n text: hit.text,\n startTime: hit.start,\n endTime: hit.end,\n relevance: Math.round(hit.score * 100) + '%',\n videoUrl: hit.media_url,\n videoLink: hit.media_url + '#t=' + hit.start + ',' + hit.end\n}));\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count,\n results: formattedResults\n }\n};"
},
"id": "code-process-simple",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [750, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
},
"id": "respond-webhook",
"name": "回傳結果",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1000, 300]
}
],
"connections": {
"Webhook (Simple)": {
"main": [
[
{
"node": "搜尋 Momentry",
"type": "main",
"index": 0
}
]
]
},
"搜尋 Momentry": {
"main": [
[
{
"node": "處理結果",
"type": "main",
"index": 0
}
]
]
},
"處理結果": {
"main": [
[
{
"node": "回傳結果",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"id": "momentry-video-search-simple",
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
}

View File

@@ -0,0 +1,89 @@
{
"name": "Momentry Video Search Test",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search-test",
"responseMode": "lastNode",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ query: $json.body.query || \"test\", limit: $json.body.limit || 5 }) }}",
"options": {}
},
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [500, 300]
},
{
"parameters": {
"jsCode": "return $input.first().json;"
},
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [750, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1000, 300]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null
}

View File

@@ -0,0 +1,94 @@
{
"name": "Momentry Video RAG MCP",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-rag-mcp",
"responseMode": "lastNode",
"options": {}
},
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": "={{ JSON.stringify({query: $json.body.query || $json.body, limit: $json.body.limit || 5, uuid: $json.body.uuid}) }}",
"options": {
"timeout": 30000
}
},
"name": "Search Momentry Core",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [500, 300]
},
{
"parameters": {
"jsCode": "// Process Momentry Core search results\nconst data = $input.first().json;\nconst hits = data.hits || [];\n\nif (hits.length === 0) {\n return {\n json: {\n success: false,\n message: 'No relevant results found',\n query: data.query,\n results: []\n }\n };\n}\n\n// Format results for RAG\nconst formattedResults = hits.map((hit, idx) => ({\n index: idx + 1,\n id: hit.id || hit.chunk_id,\n title: hit.title || 'Unknown Video',\n text: hit.text || hit.content || '',\n startTime: hit.start_time || hit.start || 0,\n endTime: hit.end_time || hit.end || 0,\n relevance: Math.round((hit.score || 0) * 100) + '%',\n videoUuid: hit.video_uuid || hit.uuid,\n mediaUrl: hit.media_url || '',\n deepLink: hit.media_url ? `${hit.media_url}#t=${hit.start_time || hit.start},${hit.end_time || hit.end}` : ''\n}));\n\n// Build context for RAG\nconst context = formattedResults\n .map(r => `[${r.index}] ${r.text} (Video: ${r.title}, Time: ${r.startTime}s-${r.endTime}s)`)\n .join('\\n\\n');\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count || hits.length,\n context: context,\n results: formattedResults\n }\n};"
},
"name": "Process RAG Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [750, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {
"statusCode": 200
}
},
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1000, 300]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Search Momentry Core",
"type": "main",
"index": 0
}
]
]
},
"Search Momentry Core": {
"main": [
[
{
"node": "Process RAG Results",
"type": "main",
"index": 0
}
]
]
},
"Process RAG Results": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null
}

View File

@@ -0,0 +1,125 @@
{
"name": "Momentry Video Search",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search",
"responseMode": "lastNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300],
"webhookId": "video-search"
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "={{ $json.body }}"
},
{
"name": "limit",
"value": 5
}
]
},
"options": {
"headers": {
"Content-Type": "application/json"
}
}
},
"id": "http-request-search",
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [500, 300]
},
{
"parameters": {
"jsCode": "const hits = $input.first().json.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n message: '找不到相關結果',\n query: $input.first().json.query\n }\n };\n}\n\nconst results = hits.map((hit, index) => ({\n number: index + 1,\n text: hit.text,\n start: hit.start,\n end: hit.end,\n score: Math.round(hit.score * 100) + '%',\n url: hit.media_url + '#t=' + hit.start + ',' + hit.end,\n video_title: hit.title\n}));\n\nreturn {\n json: {\n query: $input.first().json.query,\n count: $input.first().json.count,\n results: results\n }\n};"
},
"id": "code-process",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [750, 300]
},
{
"parameters": {
"method": "POST",
"url": "https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chat_id",
"value": "={{ $json.chat_id }}"
},
{
"name": "text",
"value": "=🎬 搜尋結果: \"{{ $json.query }}\"\n\n{{ $json.results.map(r => r.number + '️⃣ \"' + r.text.substring(0, 50) + '...\"\n⏱ ' + r.start + 's - ' + r.end + 's\n📊 相關度: ' + r.score).join('\n\n') }}"
}
]
},
"options": {}
},
"id": "telegram-send",
"name": "Telegram 通知",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [1000, 300],
"continueOnFail": true
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "搜尋 Momentry",
"type": "main",
"index": 0
}
]
]
},
"搜尋 Momentry": {
"main": [
[
{
"node": "處理結果",
"type": "main",
"index": 0
}
]
]
},
"處理結果": {
"main": [
[
{
"node": "Telegram 通知",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"id": "momentry-video-search",
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
}

100
docs/test_all.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
echo "=========================================="
echo "Momentry Core API 測試腳本"
echo "=========================================="
echo ""
# 顏色定義
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 測試 1: Momentry Core 搜尋
echo -e "${YELLOW}測試 1: Momentry Core 搜尋 API${NC}"
echo "URL: http://localhost:3002/api/v1/n8n/search"
echo ""
RESPONSE=$(curl -s -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":2}')
if echo "$RESPONSE" | grep -q '"hits"'; then
echo -e "${GREEN}✅ 成功!${NC}"
echo "$RESPONSE" | python3 -m json.tool | grep -E '"query"|"count"' | head -3
echo ""
echo "前 2 個結果:"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for i, hit in enumerate(data.get('hits', [])[:2]):
print(f\" [{i+1}] {hit.get('text', '')[:50]}...\")
print(f\" 時間: {hit.get('start')}s - {hit.get('end')}s\")
print(f\" 影片: {hit.get('title')}\")
"
else
echo -e "${RED}❌ 失敗${NC}"
echo "$RESPONSE"
fi
echo ""
# 測試 2: 列出影片
echo -e "${YELLOW}測試 2: 列出所有影片${NC}"
echo "URL: http://localhost:3002/api/v1/videos"
echo ""
RESPONSE=$(curl -s http://localhost:3002/api/v1/videos)
if echo "$RESPONSE" | grep -q '"videos"'; then
echo -e "${GREEN}✅ 成功!${NC}"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"找到 {len(data.get('videos', []))} 個影片:\")
for v in data.get('videos', []):
print(f\" - {v.get('file_name')} (UUID: {v.get('uuid')[:8]}...)\")
"
else
echo -e "${RED}❌ 失敗${NC}"
fi
echo ""
# 測試 3: n8n Webhook (Test Mode)
echo -e "${YELLOW}測試 3: n8n Webhook (Test Mode)${NC}"
echo "URL: http://localhost:5678/webhook-test/video-rag-mcp"
echo ""
echo "⚠️ 注意: 請先在 n8n UI 中點擊 'Execute workflow' 按鈕"
echo ""
RESPONSE=$(curl -s -X POST http://localhost:5678/webhook-test/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":2}')
if echo "$RESPONSE" | grep -q '"success": true'; then
echo -e "${GREEN}✅ Webhook 測試成功!${NC}"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"查詢: {data.get('query')}\")
print(f\"找到: {data.get('totalFound')} 個結果\")
print(f\"Context 長度: {len(data.get('context', ''))} 字元\")
"
elif echo "$RESPONSE" | grep -q '404'; then
echo -e "${RED}❌ Webhook 未找到${NC}"
echo "請在 n8n UI 中:"
echo " 1. 開啟 'Momentry Video RAG MCP' 工作流程"
echo " 2. 點擊 'Execute workflow' 按鈕"
echo " 3. 30 秒內再次執行此腳本"
else
echo -e "${RED}❌ 錯誤${NC}"
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
fi
echo ""
echo "=========================================="
echo "測試完成!"
echo "=========================================="
echo ""
echo "快速參考:"
echo " Momentry API: http://localhost:3002/api/v1"
echo " n8n UI: https://n8n.momentry.ddns.net"
echo " Webhook Test: http://localhost:5678/webhook-test/video-rag-mcp"

33
docs/test_momentry_api.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Test Momentry Core API directly
# This bypasses n8n and tests the API directly
echo "=========================================="
echo "Testing Momentry Core API Directly"
echo "=========================================="
echo ""
# Test 1: Health check
echo "Test 1: Health Check"
curl -s http://localhost:3002/api/v1/health 2>&1 | head -5
echo ""
# Test 2: Search API
echo "Test 2: Search API"
echo "Query: 'charade'"
curl -s -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}' | python3 -m json.tool
echo ""
# Test 3: List videos
echo "Test 3: List Videos"
curl -s http://localhost:3002/api/v1/videos | python3 -m json.tool 2>/dev/null || echo "No videos or endpoint error"
echo ""
echo "=========================================="
echo "If all tests show JSON responses, API is working!"
echo ""
echo "Next step: Use the API in n8n workflows"
echo "=========================================="

104
docs/test_workflow.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/bin/bash
# Test Momentry Video RAG MCP Workflow
# Usage: ./test_workflow.sh [query] [limit] [uuid]
N8N_URL="http://localhost:5678"
WEBHOOK_PATH="webhook-test/video-rag-mcp"
echo "⚠️ 注意:使用 Test Webhook URL"
echo "(生產環境 Webhook 需要工作流程手動激活後才能使用)"
echo ""
# Default values
QUERY="${1:-charade}"
LIMIT="${2:-3}"
UUID="${3:-}"
echo "=========================================="
echo "Testing Video RAG Workflow"
echo "=========================================="
echo "Query: $QUERY"
echo "Limit: $LIMIT"
if [ -n "$UUID" ]; then
echo "UUID: $UUID"
fi
echo ""
# Build JSON payload
if [ -n "$UUID" ]; then
PAYLOAD=$(cat <<JSON
{
"query": "$QUERY",
"limit": $LIMIT,
"uuid": "$UUID"
}
JSON
)
else
PAYLOAD=$(cat <<JSON
{
"query": "$QUERY",
"limit": $LIMIT
}
JSON
)
fi
echo "Request:"
echo "$PAYLOAD" | python3 -m json.tool 2>/dev/null || echo "$PAYLOAD"
echo ""
echo "=========================================="
echo "Response:"
echo "=========================================="
echo "URL: ${N8N_URL}/${WEBHOOK_PATH}"
echo ""
# Make the request
RESPONSE=$(curl -s -X POST "${N8N_URL}/${WEBHOOK_PATH}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
# Format and display response
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
echo ""
echo "=========================================="
# Check if successful
if echo "$RESPONSE" | grep -q '"success": true'; then
echo "✅ Test PASSED"
# Extract and display summary
echo ""
echo "Summary:"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('success'):
print(f\" Query: {data.get('query', 'N/A')}\")
print(f\" Total Found: {data.get('totalFound', 0)}\")
results = data.get('results', [])
print(f\" Results Count: {len(results)}\")
print()
print(' Top Results:')
for r in results[:3]:
print(f\" [{r.get('index')}] {r.get('title', 'Unknown')}\")
text = r.get('text', '')[:50]
print(f\" Text: {text}...\")
print(f\" Time: {r.get('startTime')}s - {r.get('endTime')}s\")
print(f\" Relevance: {r.get('relevance')}\")
print()
" 2>/dev/null || true
elif echo "$RESPONSE" | grep -q '"code": 404'; then
echo "❌ Webhook not found"
echo ""
echo "可能的解決方案:"
echo " 1. 在 n8n UI 中開啟工作流程: https://n8n.momentry.ddns.net"
echo " 2. 點擊右上角的 'Active' 開關"
echo " 3. 或者使用 test webhook: webhook-test/video-rag-mcp"
else
echo "❌ Test FAILED or no results found"
fi
echo "=========================================="

1
id_ecdsa.pub Normal file
View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKEMn20j9Eql4KMUBppJ58JpWr0F0HH856KTvpIGcWXYbauJA5eopQaffnziWNIE/HPN5lXaHZ8Q07JChfjhwUk=

1
id_ed25519.pub Normal file
View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN7goG9Dsorv8if/23gxjb7PkPM9rTbl5A+3sX8avcqu

1
id_rsa.pub Normal file
View File

@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDHH/RCUu3Bs92nAZENbU70O7rObhH1bc+Neb7+LAWGjZa0lcrQNTHucM45DhtDjUObc2A7MqWJfTg244Ny5SV3sfTKK9mCXfPLFt5W7aXU6w4p7QRHm/JIJ3sbg2qqt14JPTQOkUWahjMYTe5NWgTvJKp7mckWdqQ/y6Ltlsn4wSQs4rJlMJeTZuoTRxsPtQkBUq0UYK6E+EseCnmNLxEM+gpLZpJ9eNAcDIZPNLsbyghUc+wjCyPUGnAKp/XreMBPdATp9yYKrlIjCPewfr6hesN+A4/CpMBy/aX3X/Ru4v2SvEo3xotcemuvgBpEaPuQZuxtQaiHMJy8I7S8aulLUM/GVMCdDlVkamDBBDx2h4V8dRLWcQeg8lbtC+ZP+4CcQp/2IqddZs2E+6FmjVas8IntzfjDyx+Pq8UKk3zHL2/fIm3hNwY/44TDVdpi5EsPSn/wX1zz7TXfm4eOLgS9gODZKrRwaskWp/vv6WjT2my6imoOg530znZ1pABDSJU=

View File

@@ -0,0 +1,94 @@
-- API Key Management Migration
-- Version: 001
-- Date: 2026-03-21
-- Description: Add API key management tables for secure API access
-- API Keys Table
CREATE TABLE IF NOT EXISTS api_keys (
id SERIAL PRIMARY KEY,
key_id VARCHAR(48) UNIQUE NOT NULL,
key_hash VARCHAR(64) NOT NULL,
key_prefix VARCHAR(8) NOT NULL,
name VARCHAR(128) NOT NULL,
key_type VARCHAR(20) NOT NULL DEFAULT 'user',
user_id BIGINT,
service_name VARCHAR(64),
permissions JSONB DEFAULT '["read", "write"]',
expires_at TIMESTAMP,
last_used_at TIMESTAMP,
last_used_ip VARCHAR(45),
usage_count BIGINT DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'active',
rotation_required BOOLEAN DEFAULT FALSE,
rotation_reason TEXT,
grace_period_end TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for api_keys
CREATE INDEX IF NOT EXISTS idx_api_keys_key_id ON api_keys(key_id);
CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
CREATE INDEX IF NOT EXISTS idx_api_keys_type ON api_keys(key_type);
CREATE INDEX IF NOT EXISTS idx_api_keys_status ON api_keys(status);
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
-- API Key Audit Log Table
CREATE TABLE IF NOT EXISTS api_key_audit_log (
id SERIAL PRIMARY KEY,
key_id VARCHAR(32) NOT NULL,
action VARCHAR(50) NOT NULL,
actor VARCHAR(128),
ip_address VARCHAR(45),
user_agent TEXT,
request_path TEXT,
response_code INT,
anomaly_type VARCHAR(30),
details JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for audit log
CREATE INDEX IF NOT EXISTS idx_audit_key_id ON api_key_audit_log(key_id);
CREATE INDEX IF NOT EXISTS idx_audit_action ON api_key_audit_log(action);
CREATE INDEX IF NOT EXISTS idx_audit_created_at ON api_key_audit_log(created_at);
CREATE INDEX IF NOT EXISTS idx_audit_ip ON api_key_audit_log(ip_address);
-- API Key Anomalies Table
CREATE TABLE IF NOT EXISTS api_key_anomalies (
id SERIAL PRIMARY KEY,
key_id VARCHAR(32) NOT NULL,
anomaly_type VARCHAR(30) NOT NULL,
severity VARCHAR(10) NOT NULL,
ip_address VARCHAR(45),
request_count INT,
error_count INT,
error_rate DOUBLE PRECISION,
unique_ips INT,
details JSONB,
resolved BOOLEAN DEFAULT FALSE,
resolved_at TIMESTAMP,
resolved_by VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Indexes for anomalies
CREATE INDEX IF NOT EXISTS idx_anomalies_key_id ON api_key_anomalies(key_id);
CREATE INDEX IF NOT EXISTS idx_anomalies_resolved ON api_key_anomalies(resolved);
CREATE INDEX IF NOT EXISTS idx_anomalies_severity ON api_key_anomalies(severity);
CREATE INDEX IF NOT EXISTS idx_anomalies_created_at ON api_key_anomalies(created_at);
-- API Key Types Reference
-- System (msys_): Internal system use, 365 days TTL, 72h grace
-- User (muser_): User authentication, 90 days TTL, 24h grace
-- Service (msvc_): Service-to-service, 180 days TTL, 48h grace
-- Integration (mint_): Third-party integrations, 30 days TTL, 24h grace
-- Emergency (memg_): Emergency access, 1 day TTL, 0h grace (immediate)
-- Anomaly Types Reference
-- high_request_rate: Requests exceed threshold per minute
-- high_error_rate: Error rate exceeds threshold
-- multiple_ips: Unusual number of unique IPs
-- unusual_time: Activity at unusual hours
-- brute_force: Potential brute force attack
-- data_exfiltration: Unusual data access patterns

View File

@@ -0,0 +1,140 @@
-- ================================================================
-- Migration 003: Job Worker System
-- Version: 003
-- Date: 2026-03-24
-- Description: Add job worker system tables and columns for
-- automatic video processing after registration
-- ================================================================
-- 3.1.1: Update videos table - add status, user_id, job_id columns
ALTER TABLE videos ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'pending';
ALTER TABLE videos ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE videos ADD COLUMN IF NOT EXISTS job_id INTEGER;
COMMENT ON COLUMN videos.status IS 'Video processing status: pending, processing, completed, failed';
COMMENT ON COLUMN videos.user_id IS 'WordPress user ID (for user association tracking)';
COMMENT ON COLUMN videos.job_id IS 'Associated monitor_jobs ID';
-- 3.1.2: Add foreign key for job_id after ensuring monitor_jobs table exists
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_name = 'monitor_jobs'
) THEN
ALTER TABLE videos ADD CONSTRAINT fk_videos_job_id
FOREIGN KEY (job_id) REFERENCES monitor_jobs(id) ON DELETE SET NULL;
END IF;
END $$;
-- 3.1.3: Update monitor_jobs table - add video_id, user_id, processors columns
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS video_id BIGINT;
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS user_id BIGINT;
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS completed_processors VARCHAR(20)[];
ALTER TABLE monitor_jobs ADD COLUMN IF NOT EXISTS failed_processors VARCHAR(20)[];
COMMENT ON COLUMN monitor_jobs.video_id IS 'Foreign key to videos.id';
COMMENT ON COLUMN monitor_jobs.user_id IS 'WordPress user ID';
COMMENT ON COLUMN monitor_jobs.processors IS 'Processors to run: asr, cut, yolo, ocr, face, pose, asrx';
COMMENT ON COLUMN monitor_jobs.completed_processors IS 'Successfully completed processors';
COMMENT ON COLUMN monitor_jobs.failed_processors IS 'Failed processors';
-- 3.1.4: Add foreign key for video_id in monitor_jobs
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'monitor_jobs' AND column_name = 'video_id'
) THEN
ALTER TABLE monitor_jobs ADD CONSTRAINT fk_monitor_jobs_video_id
FOREIGN KEY (video_id) REFERENCES videos(id) ON DELETE CASCADE;
END IF;
END $$;
-- 3.1.5: Create processor_results table
CREATE TABLE IF NOT EXISTS processor_results (
id SERIAL PRIMARY KEY,
job_id INTEGER REFERENCES monitor_jobs(id) ON DELETE CASCADE,
video_id BIGINT REFERENCES videos(id) ON DELETE CASCADE,
processor VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
output_path TEXT,
started_at TIMESTAMP,
completed_at TIMESTAMP,
error_message TEXT,
progress_total INT DEFAULT 0,
progress_current INT DEFAULT 0,
last_checkpoint JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_job_processor UNIQUE(job_id, processor)
);
-- 3.1.6: Create indexes for processor_results
CREATE INDEX IF NOT EXISTS idx_processor_results_job ON processor_results(job_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_video ON processor_results(video_id);
CREATE INDEX IF NOT EXISTS idx_processor_results_status ON processor_results(status);
COMMENT ON TABLE processor_results IS 'Tracks individual processor execution status';
COMMENT ON COLUMN processor_results.status IS 'pending, running, completed, failed, skipped';
-- 3.1.7: Add function to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
-- 3.1.8: Create triggers for updated_at
DROP TRIGGER IF EXISTS update_videos_updated_at ON videos;
CREATE TRIGGER update_videos_updated_at
BEFORE UPDATE ON videos
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS update_monitor_jobs_updated_at ON monitor_jobs;
CREATE TRIGGER update_monitor_jobs_updated_at
BEFORE UPDATE ON monitor_jobs
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS update_processor_results_updated_at ON processor_results;
CREATE TRIGGER update_processor_results_updated_at
BEFORE UPDATE ON processor_results
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 3.1.9: Add check constraint for videos.status
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'videos' AND column_name = 'status'
) THEN
ALTER TABLE videos ADD CONSTRAINT chk_videos_status
CHECK (status IN ('pending', 'processing', 'completed', 'failed'));
END IF;
END $$;
-- 3.1.10: Add check constraint for monitor_jobs.status (update existing if needed)
ALTER TABLE monitor_jobs DROP CONSTRAINT IF EXISTS chk_monitor_jobs_status;
ALTER TABLE monitor_jobs ADD CONSTRAINT chk_monitor_jobs_status
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'cancelled'));
-- 3.1.11: Add check constraint for processor_results.status
ALTER TABLE processor_results DROP CONSTRAINT IF EXISTS chk_processor_results_status;
ALTER TABLE processor_results ADD CONSTRAINT chk_processor_results_status
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'skipped'));
-- 3.1.12: Create index on videos.status for efficient queries
CREATE INDEX IF NOT EXISTS idx_videos_status ON videos(status);
CREATE INDEX IF NOT EXISTS idx_videos_job_id ON videos(job_id);
CREATE INDEX IF NOT EXISTS idx_videos_user_id ON videos(user_id);
-- 3.1.13: Create index on monitor_jobs.video_id
CREATE INDEX IF NOT EXISTS idx_monitor_jobs_video_id ON monitor_jobs(video_id);
CREATE INDEX IF NOT EXISTS idx_monitor_jobs_status_created ON monitor_jobs(status, created_at);

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.api</string>
<key>UserName</key>
<string>accusys</string>
<key>GroupName</key>
<string>staff</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry_core_0.1</string>
<key>ProgramArguments</key>
<array>
<string>/Users/accusys/momentry_core_0.1/target/release/momentry</string>
<string>server</string>
<string>--port</string>
<string>3002</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>DATABASE_URL</key>
<string>postgres://accusys@localhost:5432/momentry</string>
<key>REDIS_URL</key>
<string>redis://:accusys@localhost:6379</string>
<key>REDIS_PASSWORD</key>
<string>accusys</string>
<key>OLLAMA_HOST</key>
<string>http://localhost:11434</string>
<key>QDRANT_URL</key>
<string>http://localhost:6333</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/momentry_api.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/momentry_api.error.log</string>
</dict>
</plist>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.gitea-mcp-server</string>
<key>UserName</key>
<string>accusys</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/gitea-mcp-server</string>
<string>-transport</string>
<string>http</string>
<string>-port</string>
<string>8787</string>
<string>-host</string>
<string>http://localhost:3000</string>
<string>-token</string>
<string><GITEA_TOKEN></string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/gitea-mcp-server.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/gitea-mcp-server.error.log</string>
</dict>
</plist>

View File

@@ -9,7 +9,7 @@
<array>
<string>/opt/homebrew/bin/mongod</string>
<string>--dbpath</string>
<string>/Users/accusys/momentry/var</string>
<string>/opt/homebrew/var/mongodb</string>
<string>--logpath</string>
<string>/Users/accusys/momentry/log/mongodb.log</string>
<string>--port</string>
@@ -24,9 +24,6 @@
<key>KeepAlive</key>
<true/>
<key>UserName</key>
<string>accusys</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/mongodb.error.log</string>
@@ -34,6 +31,6 @@
<string>/Users/accusys/momentry/log/mongodb.log</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry/var/mongodb/</string>
<string>/opt/homebrew/var/mongodb</string>
</dict>
</plist>

View File

@@ -35,12 +35,18 @@
<key>N8N_PORT</key>
<string>5678</string>
<key>N8N_HOST</key>
<string>127.0.0.1</string>
<key>N8N_LISTEN_ADDRESS</key>
<string>0.0.0.0</string>
<key>N8N_USER_MANAGEMENT_JWT_DURATION</key>
<string>1h</string>
<key>N8N_RUNNERS_ENABLED</key>
<string>false</string>
<key>GENERIC_TIMEZONE</key>
<string>Asia/Taipei</string>
@@ -85,6 +91,9 @@
<key>N8N_METRICS</key>
<string>true</string>
<key>N8N_PUBLIC_API_ENABLED</key>
<string>true</string>
</dict>
<key>RunAtLoad</key>

View File

@@ -29,14 +29,14 @@
<key>N8N_ENCRYPTION_KEY</key>
<string>Test3200Test3200Test3200</string>
<key>DOMAIN_NAME</key>
<string>n8n.momentry.ddns.net</string>
<key>N8N_PORT</key>
<string>5681</string>
<key>WEBHOOK_URL</key>
<string>https://n8n.momentry.ddns.net/</string>
<key>QUEUE_HEALTH_CHECK_PORT</key>
<string>5682</string>
<key>N8N_LISTEN_ADDRESS</key>
<string>0.0.0.0</string>
<key>N8N_HOST</key>
<string>127.0.0.1</string>
<key>GENERIC_TIMEZONE</key>
<string>Asia/Taipei</string>
@@ -80,14 +80,14 @@
<key>N8N_USER_FOLDER</key>
<string>/Users/accusys/momentry/var/n8n</string>
<key>N8N_METRICS</key>
<string>true</string>
<key>N8N_RUNNERS_BROKER_PORT</key>
<string>5690</string>
<key>N8N_RUNNERS_LAUNCHER_HEALTH_CHECK_PORT</key>
<string>5691</string>
<key>N8N_METRICS</key>
<string>true</string>
</dict>
<key>RunAtLoad</key>

View File

@@ -25,6 +25,10 @@
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin</string>
<key>HOME</key>
<string>/Users/accusys</string>
<key>SFTPGO_DEFAULT_ADMIN_USERNAME</key>
<string>admin</string>
<key>SFTPGO_DEFAULT_ADMIN_PASSWORD</key>
<string>Test3200Test3200</string>
</dict>
<key>RunAtLoad</key>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.momentry.worker</string>
<key>UserName</key>
<string>accusys</string>
<key>GroupName</key>
<string>staff</string>
<key>WorkingDirectory</key>
<string>/Users/accusys/momentry_core_0.1</string>
<key>ProgramArguments</key>
<array>
<string>/Users/accusys/momentry_core_0.1/target/release/momentry</string>
<string>worker</string>
<string>--max-concurrent</string>
<string>2</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>DATABASE_URL</key>
<string>postgres://accusys@localhost:5432/momentry</string>
<key>REDIS_URL</key>
<string>redis://:accusys@localhost:6379</string>
<key>REDIS_PASSWORD</key>
<string>accusys</string>
<key>OLLAMA_HOST</key>
<string>http://localhost:11434</string>
<key>QDRANT_URL</key>
<string>http://localhost:6333</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/accusys/momentry/log/momentry_worker.log</string>
<key>StandardErrorPath</key>
<string>/Users/accusys/momentry/log/momentry_worker.error.log</string>
</dict>
</plist>

View File

@@ -0,0 +1,35 @@
#!/bin/bash
# Momentry Monitor 密碼管理腳本
# 路徑: /Users/accusys/momentry_core_0.1/monitor/common/load_credentials.sh
#
# 使用方式:
# source /path/to/load_credentials.sh
# 載入環境變數(如果存在)
if [ -f "$HOME/.momentry/credentials" ]; then
set -a
source "$HOME/.momentry/credentials"
set +a
fi
# 預設值
export PG_USER="${PG_USER:-accusys}"
export PG_PASSWORD="${PG_PASSWORD:-accusys}"
export PG_HOST="${PG_HOST:-localhost}"
export PG_PORT="${PG_PORT:-5432}"
export REDIS_PASSWORD="${REDIS_PASSWORD:-accusys}"
export MONGO_USER="${MONGO_USER:-accusys}"
export MONGO_PASSWORD="${MONGO_PASSWORD:-Test3200Test3200}"
export MONGO_PORT="${MONGO_PORT:-27017}"
export QDRANT_API_KEY="${QDRANT_API_KEY:-Test3200Test3200Test3200}"
export SFTPGO_PASSWORD="${SFTPGO_PASSWORD:-sftpgo_pass_2026}"
export SFTPGO_USER="${SFTPGO_USER:-sftpgo}"
export N8N_PASSWORD="${N8N_PASSWORD:-accusys}"
export MARIADB_USER="${MARIADB_USER:-accusys}"
export MARIADB_PASSWORD="${MARIADB_PASSWORD:-Test3200Test3200Test3200}"

View File

@@ -405,14 +405,6 @@ backup:
retention:
weekly: 4
- name: "mongodb"
enabled: true
backup_type: "config"
method: "file"
schedule: "weekly"
retention:
weekly: 4
- name: "php"
enabled: true
backup_type: "config"

Some files were not shown because too many files have changed in this diff Show More