visionA/docs/autoflow/04-architecture/api/api-device-mgmt.md
jim800121chen f6d15b7b14 docs(arch): B 設備管理 + C 模型共享 + camera ADR-020 規劃文件
- 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>
2026-08-02 16:31:38 +08:00

132 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API 規格 — 設備註冊 / 取消註冊(個人設備管理 P0
- **上位**[`../feature-device-mgmt-tdd.md`](../feature-device-mgmt-tdd.md)、[`api-spec.md`](api-spec.md) §3 Devices
- **狀態**Draft契約contract-first single source of truth
- **最後更新**2026-08-02
> 本檔為 register / unregister 兩個新端點的權威契約。前端對此形狀寫 normalize、後端對此形狀保證回應。既有 `GET /api/devices`、`GET /api/devices/:id`、`POST /api/devices/:id/unpair` 見 `api-spec.md` §3、**本 P0 不改**。
---
## 通用
- **認證**JWT/OIDC bearer同既有 `/api/devices/*` route group
- **識別值**`:id` = device UUID雲端 DB 主鍵。register/unregister 是純雲端 DB 操作、不路由到 local agent故用 UUID對齊 ADR-018 FE-ADB 操作用 UUID、路由操作才用 serial
- **回應信封**:沿用既有 `{ "success": true, "data": ... }`(前端 api client 已 unwrap `data`)。錯誤為 `{ "success": false, "error": { "code", "message" } }`
### DeviceListItem回應 data 形狀沿用既有snake_case
register/unregister 成功皆回**更新後的單筆 DeviceListItem**(與 `GET /api/devices/:id` 同形狀):
```json
{
"id": "uuid",
"name": "KL520 #A1B2",
"device_type": "kl520",
"serial_number": "0x1234ABCD",
"agent_id": "uuid",
"registered_at": "2026-08-02T10:00:00Z",
"remote_status": "online",
"last_seen_at": "2026-08-02T10:00:00Z",
"last_connected_at": null,
"status": "connected",
"tunnel_online": true,
"created_at": "2026-08-01T00:00:00Z",
"updated_at": "2026-08-02T10:00:00Z"
}
```
- `registered_at`register 後為 ISO 8601 時間unregister 後為 `null`omitempty → 欄位可能不出現,前端 normalize 皆視為 null=未註冊)。
---
## 1. `POST /api/devices/:id/register` — 註冊設備
把裝置從「未註冊」翻成「已註冊」(`registered_at` NULL → now())。
- **Request body**:無。
- **Success**`200 OK` + 更新後 DeviceListItem`registered_at` 非 null
### 行為順序(後端)
| 步驟 | 條件 | 回應 |
|------|------|------|
| 1 | 缺 UserContext | 500 `INTERNAL_ERROR` |
| 2 | `:id` 空 | 400 `VALIDATION_FAILED` |
| 3 | device 不存在 / 已軟刪 | 404 `NOT_FOUND` |
| 4 | `owner_user_id != caller` | 403 `FORBIDDEN`IDOR 防護) |
| 5 | `is_representative == true` | 409 `CONFLICT`(代表 device 不可註冊) |
| 6 | `registered_at != null`(已註冊) | 409 `ALREADY_REGISTERED` |
| 7 | 正常 | 200 + DeviceListItem |
### 範例
```
POST /api/devices/7f3a.../register
→ 200 { "success": true, "data": { ...,"registered_at":"2026-08-02T10:00:00Z" } }
(已註冊再打)
→ 409 { "success": false, "error": { "code":"ALREADY_REGISTERED","message":"device already registered" } }
```
---
## 2. `POST /api/devices/:id/unregister` — 取消註冊(退回未註冊)
把裝置退回「未註冊」(`registered_at` → NULL**保留裝置列**、不軟刪、不撤 token。
> ⚠️ **與 `unpair` 完全不同**unpair 是軟刪整台 + cascade 撤 tokendevice 從清單消失unregister 只清 `registered_at`device 仍在清單、顯示為未註冊)。兩端點各走各的,不可合併。詳見 TDD §1。
- **Request body**:無。
- **Success**`200 OK` + 更新後 DeviceListItem`registered_at` = null
### 行為順序(後端)
| 步驟 | 條件 | 回應 |
|------|------|------|
| 1 | 缺 UserContext | 500 `INTERNAL_ERROR` |
| 2 | `:id` 空 | 400 `VALIDATION_FAILED` |
| 3 | device 不存在 / 已軟刪 | 404 `NOT_FOUND` |
| 4 | `owner_user_id != caller` | 403 `FORBIDDEN` |
| 5 | `is_representative == true` | 409 `CONFLICT` |
| 6 | 已是未註冊(`registered_at == null` | **200**(冪等 no-op非錯誤 |
| 7 | 正常 | 200 + DeviceListItemregistered_at=null |
### 範例
```
POST /api/devices/7f3a.../unregister
→ 200 { "success": true, "data": { ...,"registered_at": null } }
device 仍存在於 GET /api/devices
```
---
## 3. 錯誤碼
| code | HTTP | 情境 | 新增? |
|------|------|------|-------|
| `VALIDATION_FAILED` | 400 | id 空 | 既有 |
| `FORBIDDEN` | 403 | 非 ownerIDOR | 既有 |
| `NOT_FOUND` | 404 | device 不存在/軟刪 | 既有 |
| `CONFLICT` | 409 | representative device 不可註冊/取消 | 既有(或沿用既有 CONFLICT 碼) |
| `ALREADY_REGISTERED` | 409 | register 時已註冊 | **新增** |
| `INTERNAL_ERROR` | 500 | 缺 UserContext / DB 錯 | 既有DB down 經 errors.go 映射 503 |
- `ALREADY_REGISTERED` 為本功能新增錯誤碼,需在後端 errors 常數 + 前端 i18n`devices.register.error.alreadyRegistered`)同步登記。
- representative 衝突若不想新增碼,可沿用既有 `CONFLICT` + message 區分;由 backend agent 依既有錯誤碼慣例定,前端據 message/context 提示。
---
## 4. 前端呼叫(對齊 device-store.ts 範式)
```
// 皆用 UUIDDB 操作);不帶 serial。
registerDevice(id): POST /api/devices/{id}/register → 更新 store 該筆 registeredAt
unregisterDevice(id): POST /api/devices/{id}/unregister → 清 store 該筆 registeredAt保留列
```
- 成功後就地更新 store 對應 device 的 `registeredAt`(避免 refetch 延遲,比照既有 unpairDevice 就地移除的範式),或 refetch 該筆。
- 409 `ALREADY_REGISTERED` → toast「已註冊」+ refetch。
- **不像 unpair 從 list 移除**——unregister 只改欄、device 留在 list。