- feature-device-mgmt-tdd.md + api-device-mgmt.md(B 設備管理 TDD) - feature-model-sharing-tdd.md + api-model-sharing.md + PRD feature + 設計規格(C 模型共享三方規劃) - adr-020-ffmpeg-camera-indev.md(camera 三平台 indev) - PRD.md / TDD.md 索引增補 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
187 lines
8.6 KiB
Markdown
187 lines
8.6 KiB
Markdown
# API Spec — 模型共享(Model Sharing)
|
||
|
||
> 對齊 `feature-model-sharing-tdd.md`。base URL / envelope / 認證同 `api-spec.md`(§0)。
|
||
> 通用回應:`{ "success": true, "data": {...} }` / `{ "success": false, "error": { "code", "message" } }`。
|
||
> 認證:cookie session(BFF,同 `api-spec.md`),handler 從 `UserContext` 取 `userID` / `roles` / `orgId`。
|
||
>
|
||
> **狀態**:Draft(待三方互審)。標 `[需 PM 確認]` / `[需 Design 確認]` / `[需 security 審]` 的點見各節。
|
||
|
||
---
|
||
|
||
## 0. 名詞與前提
|
||
|
||
| 名詞 | 定義 |
|
||
|------|------|
|
||
| **visibility** | 模型的「公開對象」維度(廣播式):`private`(僅擁有者)/ `tenant`(同租戶可見)/ `public`(全平台可見)。存在 `models.visibility` 欄。 |
|
||
| **model_shares** | 「點對點分享給特定人」維度(ADR-017 決策 3 已定義,本功能沿用、不重造)。與 visibility 正交。 |
|
||
| **共享模型庫** | 「依身份權限的可見模型列表」= 我的 ∪ 公開(public) ∪ 同租戶(tenant 且同 org) ∪ 分享給我(model_shares) ∪ preset。 |
|
||
| **tenant / org** | 沿用既有 `users.org_id`(migration 0001 已有欄位、domain 尚未使用,見 TDD §2)。`tenant` visibility = 同 `org_id` 可見。`[需 PM 確認]` org 邊界語意。 |
|
||
|
||
> **與既有 `/api/models` 的分工**:既有 `GET /api/models`(`api-spec.md §4`)維持「**我的模型 + preset**」語意不變(owner dashboard 用),**不動**。共享模型庫是**新端點** `GET /api/models/library`(跨擁有者、依權限可見)。兩者刻意分開,避免破壞既有 owner dashboard 行為。`[需 Design 確認]` UI 是否分兩個入口。
|
||
|
||
---
|
||
|
||
## 1. GET `/api/models/library` — 共享模型庫列表(依身份權限可見)
|
||
|
||
依當前 user 身份,回「可見模型」的分頁列表。可見範圍 = 我的 ∪ public ∪ (tenant 且同 org) ∪ 分享給我 ∪ preset。
|
||
|
||
### Query 參數
|
||
|
||
| 參數 | 型別 | 預設 | 說明 |
|
||
|------|------|------|------|
|
||
| `cursor` | string | — | 分頁游標(不透明 base64)。首頁不帶。見 §1.3 分頁契約。 |
|
||
| `limit` | int | 20 | 每頁筆數,1–100,超出 clamp。 |
|
||
| `sort` | enum | `created_at` | 排序欄位:`created_at` / `name` / `file_size`。 |
|
||
| `order` | enum | `desc` | `asc` / `desc`。 |
|
||
| `q` | string | — | 搜尋關鍵字(比對 `name` + `description`,ILIKE 前綴/包含,見 TDD §5 效能)。 |
|
||
| `target_chip` | enum | — | filter:`kl520`/`kl720`/`kl630`/`kl730`。 |
|
||
| `source` | enum | — | filter:`uploaded`/`converted`/`preset`。 |
|
||
| `visibility` | enum | — | filter:`public`/`tenant`(僅過濾廣播類;`private` 不在共享庫語意內、傳了視為忽略)。 |
|
||
| `owned` | bool | — | `true`=只看我的、`false`=只看別人分享/公開給我的;不帶=全部可見。 |
|
||
|
||
### Response 200(envelope `data`)
|
||
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "uuid",
|
||
"name": "yolov5s-kl520",
|
||
"description": "...",
|
||
"target_chip": "kl520",
|
||
"file_size": 10485760,
|
||
"source": "converted",
|
||
"status": "ready",
|
||
"visibility": "public",
|
||
"owner": { "id": "uuid", "name": "Alice", "is_me": false },
|
||
"shared_with_me": false,
|
||
"my_access": "viewer",
|
||
"created_at": "2026-07-01T00:00:00Z",
|
||
"updated_at": "2026-07-02T00:00:00Z"
|
||
}
|
||
],
|
||
"next_cursor": "eyJ...",
|
||
"has_more": true
|
||
}
|
||
```
|
||
|
||
**欄位說明(權限相關,重要)**:
|
||
- `owner`:只揭露 `id` / `name` / `is_me`(**不揭露 owner email**——非擁有者不該看到他人 email,見 §4 安全)。`[需 security 審]`。
|
||
- `my_access`:當前 user 對此模型的有效權限(`owner`/`editor`/`viewer`)。由「owner_user_id 命中 / model_shares.role / visibility 落 viewer」計算,取最高。
|
||
- `shared_with_me`:是否因 model_shares 命中(供 UI 標「已分享給我」)。`[需 Design 確認]` 標示方式。
|
||
- `status`:沿用既有 DTO(`pending`/`ready` 由 `uploaded_at` 決定,見 `models.go toModelResponse`)。**共享庫只列 `ready`**(未 finalize 的不進共享庫,見 TDD §5)。
|
||
|
||
### 錯誤碼
|
||
|
||
| HTTP | code | 情境 |
|
||
|------|------|------|
|
||
| 400 | `VALIDATION_FAILED` | limit / cursor / sort 非法。 |
|
||
| 401 | (middleware) | 未登入。 |
|
||
| 500/503 | `INTERNAL_ERROR` | DB 錯誤(經 `WriteDBError` 映射,不洩漏 raw)。 |
|
||
|
||
---
|
||
|
||
## 2. GET `/api/models/:id/profile` — 模型 profile 頁(公開版詳情)
|
||
|
||
非擁有者也可看的模型詳情頁。**權限檢查為核心**:只有「對此 model 有可見性」的 user 能看,且回傳欄位依身份裁剪。
|
||
|
||
### 可見性判斷(handler 第一步)
|
||
|
||
user 能看此 profile ⟺ 命中任一:
|
||
1. `owner_user_id == userID`(我的)
|
||
2. `visibility == public`
|
||
3. `visibility == tenant` 且 model.owner.org_id == user.org_id
|
||
4. `model_shares` 命中(grantee == userID)
|
||
5. preset 模型(公用)
|
||
|
||
不命中 → **404**(不是 403):不揭露「這個 id 存在但你沒權限」,避免 enumeration 洩漏存在性。`[需 security 審]` 404 vs 403 策略。
|
||
|
||
### Response 200(envelope `data`)
|
||
|
||
```json
|
||
{
|
||
"id": "uuid",
|
||
"name": "...",
|
||
"description": "...",
|
||
"target_chip": "kl520",
|
||
"file_size": 10485760,
|
||
"source": "converted",
|
||
"status": "ready",
|
||
"visibility": "public",
|
||
"input_shape": [1, 3, 224, 224],
|
||
"classes": ["cat", "dog"],
|
||
"framework": "onnx",
|
||
"owner": { "id": "uuid", "name": "Alice", "is_me": false },
|
||
"my_access": "viewer",
|
||
"can_download": true,
|
||
"created_at": "...",
|
||
"updated_at": "...",
|
||
"uploaded_at": "..."
|
||
}
|
||
```
|
||
|
||
**依身份裁剪的欄位(重要,`[需 security 審]`)**:
|
||
- **絕不揭露**(無論身份):`storage_key`、`faa_object_key`(FAA 內部 key,ADR-017 決策 2 防曝露)、owner email、`file_checksum`(`[需 PM 確認]` checksum 是否對非 owner 公開)。
|
||
- `can_download`:`my_access != none` 時 true。前端據此決定是否顯示下載鈕。實際下載仍走既有 `GET /api/models/:id/download`(該端點需同步加共享權限檢查,見 §3)。
|
||
|
||
### 錯誤碼
|
||
|
||
| HTTP | code | 情境 |
|
||
|------|------|------|
|
||
| 404 | `NOT_FOUND` | model 不存在 **或** 無可見性(合併,防 enumeration)。 |
|
||
|
||
---
|
||
|
||
## 3. PATCH `/api/models/:id/visibility` — 設定公開對象
|
||
|
||
擁有者設定模型的公開對象。**只有 owner(或 editor?`[需 PM 確認]`)能改**。
|
||
|
||
### Request
|
||
|
||
```json
|
||
{ "visibility": "public" }
|
||
```
|
||
|
||
- `visibility`(required):`private` / `tenant` / `public`。
|
||
- `tenant` 但 user 無 `org_id` → 400(無租戶歸屬不能設 tenant 可見)。`[需 PM 確認]` demo/無 org user 的行為。
|
||
|
||
### Response 200(envelope `data`)
|
||
|
||
```json
|
||
{ "id": "uuid", "visibility": "public", "updated_at": "..." }
|
||
```
|
||
|
||
### 錯誤碼
|
||
|
||
| HTTP | code | 情境 |
|
||
|------|------|------|
|
||
| 400 | `VALIDATION_FAILED` | visibility 非法 / 設 tenant 但無 org。 |
|
||
| 403 | `FORBIDDEN` | 非 owner。 |
|
||
| 404 | `NOT_FOUND` | model 不存在。 |
|
||
| 409 | `CONFLICT` | model 未 `ready`(未 finalize 不允許公開,`[需 PM 確認]`)。 |
|
||
|
||
> **既有 `GET /api/models/:id/download` 的連帶變更(見 TDD §5)**:目前是 owner-only(`m.OwnerUserID != userID → 403`)。共享後需改為「有可見性且 `can_download` → 放行」。此變更**涉及 auth 繞過風險**,`[需 security 審]`。
|
||
|
||
---
|
||
|
||
## 4. 安全考量摘要(詳見 TDD §6)
|
||
|
||
| 風險 | 對策 |
|
||
|------|------|
|
||
| IDOR / enumeration(猜 id 看他人 model) | profile / download 的可見性檢查在 handler 第一步;不命中回 404(不回 403 揭露存在性)。`[需 security 審]` |
|
||
| 越權改 visibility | PATCH visibility 強制 owner 檢查。 |
|
||
| 資訊洩漏(email / storage_key / faa_object_key) | DTO 白名單序列化,內部 key 用 `json:"-"`(沿用 `model.Model.FAAObjectKey` 既有做法)。 |
|
||
| tenant 邊界誤放 | tenant 可見性需 `model.owner.org_id == user.org_id` 且兩者皆非空;空 org 不落 tenant 可見。 |
|
||
| 下載端點權限繞過 | `GET /api/models/:id/download` 的 owner-only 改共享權限檢查,須與 profile 可見性邏輯**共用同一函式**(single source of truth,避免兩處判斷不一致)。`[需 security 審]` |
|
||
|
||
---
|
||
|
||
## 5. 待三方確認清單(本檔)
|
||
|
||
| # | 項目 | 待誰 |
|
||
|---|------|------|
|
||
| S1 | org/tenant 邊界語意(org_id 是否等於租戶;跨 org 是否可能) | PM |
|
||
| S2 | editor 是否能改 visibility / 未 ready 能否公開 / checksum 是否對非 owner 公開 | PM |
|
||
| S3 | 共享庫 UI 入口(與既有「我的模型」分頁 or 合併 tab)、`shared_with_me` 標示、profile 頁欄位 | Design |
|
||
| S4 | 404 vs 403 防 enumeration、download 端點權限改動、owner email 不揭露 | security |
|