81 lines
3.9 KiB
Markdown
81 lines
3.9 KiB
Markdown
# 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 被拒才說。
|