# T513 — radar-watch-api-crud-quota-gate > Status: `done`(2026-07-31) > Milestone: `M1` > Kind: `feat` > Est. change: `~200 lines` ## Goal 完成後系統應:watch CRUD/pause/resume/archive API 可用,且**未建服務檔案不得建 active watch**、**超過方案 `max_active_watches` 明確拒絕**。 ## Depends on - T511、T512 ## Inputs - Spec:`../spec.md` §3.1、§4.9、§5.2、§5.6、§9.1(SP-01、RW-01、RW-02、RW-04) - 既有方案/配額讀取:`apps/backend/internal/module/usage/**`、`billing` plan 定義 ## Outputs ### 程式變更(預期路徑) | 路徑 | 動作 | 說明 | |------|------|------| | `apps/backend/generate/api/radar.api` | edit | watches req/resp type 定稿 | | `apps/backend/internal/logic/radar/*Watch*Logic.go` | edit | list/create/get/update/delete(archive)/pause/resume | | `apps/backend/internal/module/radar/usecase/watch_quota.go` | add | 方案上限查詢與判斷(Free 1/Starter 5/Pro 20) | | `apps/backend/internal/module/billing/**` 或方案常數檔 | edit | 新增 `max_active_watches`、`max_daily_opportunities` 欄位(**不改價格、不動 `soft_caps`**) | ### 行為變更 - 建立或 resume 成 `active` 前檢查兩道閘:服務檔案存在、active 數 < 上限。 - 兩種拒絕皆為明確錯誤:前者 message 指向服務檔案;後者 message 含「目前上限 N」與升級提示。 - 既有超額者不強制降級(只擋新增)。 - `DELETE` 語意為封存(軟刪),非實刪。 ### API / 契約 - `GET /watches` 支援 `page`/`pageSize`、`status` filter,回 `pagination` + `list`。 ## Out of scope - 手動觸發 sweep(T533) - 關鍵字建議(T514) ## Acceptance - [x] SP-01:無服務檔案建 active watch → 明確錯誤且指向服務檔案 - [x] RW-01:Free 已有 1 個 active,再建 → 明確錯誤含上限與升級提示 - [x] RW-02/RW-04:pause 後不在 active 清單;archive 後不可 resume - [x] 指令: ```bash cd apps/backend && make gen-api && go test ./internal/logic/radar/... ./internal/module/radar/... -count=1 ``` ## Notes 配額欄位加在既有方案定義上,**不要**新增第五個 usage meter(RG-04)。 ## Notes(2026-07-31) | 路徑 | 動作 | |------|------| | `internal/module/usage/domain/usage.go` | edit(`PlanDef` 加 `MaxActiveWatches`/`MaxDailyOpportunities`;Free 1/5、Starter 5/30、Pro 20/100) | | `internal/module/usage/usecase/service.go` | edit(新增 `PlanFor`) | | `internal/module/radar/usecase/watch_quota.go` | add(`PlanQuota` 介面、`FixedQuota`、`assertCanActivate`) | | `internal/logic/radar/{list,create,get,update,archive,pause,resume}_*_logic.go` | edit | | `internal/svc/service_context.go` | edit(`radarQuotaBridge` 橋接既有方案) | | `internal/logic/radar/watch_logic_test.go` | add(8 則) | - **價格與 `soft_caps` 未動**,也沒有新增第五個 meter:兩個新欄位只是方案的規模上限,AI 判定與回覆仍計既有四個 meter。 - **配額介面留在 radar module,實作橋接在 `internal/svc`。** radar 不反向依賴 usage module。 - **沒接上 `PlanQuota` 時退回最低方案(1/5),不是無上限。** 漏接線的後果應該是使用者看到上限提示來問,而不是所有人都拿到 Pro 的常駐監控量。 - **`resume` 會重驗兩道閘。** 暫停期間方案可能已降級,不重驗就等於「暫停再恢復」是繞過上限的後門。測試明確蓋住這條路。 - **既有超額訂閱不強制降級**(spec §3.1):降級後只擋新增,已經在跑的照跑。 - **`DELETE` 是軟刪**:archive 後列表仍看得到、`active_count` 不含它,歷史商機與統計不動。 - `GET /watches` 額外回 `active_count`/`max_active`/`profile_exists`,讓雷達頁在使用者按下建立之前就能顯示引導或升級提示,而不是等 POST 被拒才說。