thread-master/docs/product/demand-radar/tasks/T501-mongo-indexes-radar-cr...

92 lines
4.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.

# T501 — mongo-indexes-radar-crm
> Status: `done`2026-07-31
> Milestone: `M0`
> Kind: `feat`
> Est. change: `~120 lines`
## Goal
完成後系統應:`make init` 可重複執行並建立本 run 全部 collection 索引,含去重唯一鍵。
## Depends on
-
## Inputs
- Spec`../spec.md` §8實體與索引
- 既有索引初始化:`apps/backend/cmd/init/main.go`
## Outputs
### 程式變更(預期路徑)
| 路徑 | 動作 | 說明 |
|------|------|------|
| `apps/backend/generate/database/mongo/000014_demand_radar_indexes.up.json` | add | 全部 15 個索引,含兩個 unique |
| `apps/backend/generate/database/mongo/000014_demand_radar_indexes.down.json` | add | 對應 dropIndexes |
| `apps/backend/cmd/init/main.go` | edit | 七個 collection 的**非 unique** 索引(名稱與 spec 與 migration 完全一致) |
### 行為變更
- 建立索引:
- `radar_service_profiles``_id = owner_uid`(不需額外索引)
- `radar_watches``(owner_uid, status)`、`(owner_uid, created_at)`
- `radar_sweeps``(owner_uid, created_at)`、`(watch_id, created_at)`、`job_id`
- `radar_opportunities``(owner_uid, created_at)`、`(owner_uid, intent_band, status)`、`(owner_uid, external_id)` **unique**
- `radar_replies``(owner_uid, opportunity_id, variant)`
- `crm_contacts``(owner_uid, source_platform, author_handle)` **unique**、`(owner_uid, stage, last_touch_at)`、`(owner_uid, needs_follow_up)`
- `crm_touches``(owner_uid, contact_id, created_at)`
- `crm_followups``(status, due_at)`、`(owner_uid, contact_id)`
- 重複執行不報錯(沿用既有 idempotent 建法)。
## Out of scope
- domain struct 與 repository各 M 的 model task
- 資料遷移(本 run 無既有資料需搬)
## Acceptance
- [x] `make init` 連跑兩次皆成功(乾淨 DB 驗證,兩次都 `database indexes initialized`
- [x] Mongo `getIndexes()` 可見上列索引migrate 路徑 15 個且 unique 恰為 `radar_opportunities.owner_opportunity_external`、`crm_contacts.owner_contact_identity`init 路徑 13 個非 unique
- [x] 唯一鍵語意實測:同 owner 同 `external_id` 第二筆被擋11000、不同 owner 可共存;同 owner 同平台同 handle 第二筆被擋、跨平台可共存
- [x] `migrate-down 1` → 再 `up` 可逆
- [x] 指令radar/crm 部分;完整鏈路受既有缺陷阻塞,見下):
```bash
cd apps/backend && make migrate-up MONGO_URL=<scratch> && MONGO_DATABASE=<scratch> make init
```
## Notes
`(owner_uid, external_id)` 是 SW-06 去重的硬保證,不能只靠應用層判斷。
### 落點分工(沿用 growth-loop 先例)
唯一鍵放 migration、operational 索引放 `cmd/init`。**radar/crm 兩邊的索引名稱與 spec 完全一致**,因此同一顆 DB 先 migrate 再 init 不會衝突(已實測)。
Mongo 的衝突規則(實測 Mongo 7
| 情境 | 結果 |
|------|------|
| 同名同鍵、`unique` 不同 | `IndexKeySpecsConflict`86 |
| 同名同鍵同 spec 重建 | OKidempotent |
| **異名同鍵同 spec** | `IndexOptionsConflict`85"already exists with a different name" |
| 異名同鍵但 spec 不同 | 允許(會多一個索引) |
### 既有缺陷(**非本 task 造成,未修**
`make migrate-up && make init` 在**新 DB** 上無法連跑:多個既有 collection 的 migration 與 init 用同鍵不同名init 會在第一個衝突處 panic。已確認的衝突點
| Collection | 鍵 | migration 名 | init 名 |
|-----------|-----|-------------|---------|
| `notifications` | `(owner_uid, created_at)` | `owner_notifications_recent` | `owner_notifications_created` |
| `growth_outcomes` | `(owner_uid, source_type, source_id)` | `owner_outcome_source`unique | 同名但非 unique |
| `growth_checkups` | `(owner_uid, created_at)` | `owner_checkups_recent` | `owner_checkups_created` |
| `growth_workspaces` | `(owner_uid, archived)` | `owner_workspaces` | `owner_workspaces_archived` |
| `growth_invite_rewards` | `(inviter_uid, created_at)` | `inviter_rewards` | `inviter_rewards_created` |
| `growth_invite_rewards` | `(invitee_uid)` | `invitee_reward_once` | `invitee_reward` |
未修的原因:要決定「以哪邊的名字為準」會影響**既有部署**——若某環境的索引是 init 建的,把 init 改成 migration 的名字會讓該環境從此報 85。這是需要產品維運決定的收斂方向不該塞在本 task 裡。建議另開 task 處理(含各環境現況盤點)。