# T510 — service-profile-model-repository > Status: `done`(2026-07-31) > Milestone: `M1` > Kind: `feat` > Est. change: `~170 lines` ## Goal 完成後系統應:`radar` module 有 ServiceProfile domain 與 repository(mongo/memory),每會員一份可讀寫。 ## Depends on - T501 ## Inputs - Spec:`../spec.md` §2、§4.1、§8 - 既有 module 模式:`apps/backend/internal/module/scout/{domain,repository,usecase}` ## Outputs ### 程式變更(預期路徑) | 路徑 | 動作 | 說明 | |------|------|------| | `apps/backend/internal/module/radar/domain/service_profile.go` | add | struct+驗證:`services[]`(項目+價格區間)、`cases[]`、`forbidden[]`、`faq[]`、`service_areas[]`、`remote_ok`、`availability`、`tone_note` | | `apps/backend/internal/module/radar/repository/service_profile_mongo.go` | add | `radar_service_profiles`,`_id = owner_uid` upsert | | `apps/backend/internal/module/radar/repository/service_profile_memory.go` | add | 測試用 | | `apps/backend/internal/module/radar/usecase/service_profile.go` | add | Get/Upsert usecase | ### 行為變更 - `Upsert` 為整份覆寫(每會員一份),`updated_at` 為 ns UTC。 - `Get` 未建立時回「不存在」語意,供上層判斷 SP-01 前置條件。 - 驗證:價格區間下限 ≤ 上限;`service_areas[]` 為縣市代碼白名單;`forbidden[]` 去空白去重。 ## Out of scope - HTTP logic(T511) - 判定與回覆如何使用本檔(T529/T549) ## Acceptance - [x] unit test:upsert → get 欄位完整、`forbidden[]` 可讀、價格區間反向被拒 - [x] 指令: ```bash cd apps/backend && go test ./internal/module/radar/... -count=1 ``` ## Notes 縣市代碼白名單放 `domain` 常數,不猜測、不做模糊比對(OP-04 行為靠這個約束)。 ### 實作結果(2026-07-31) | 路徑 | 動作 | |------|------| | `internal/module/radar/domain/service_profile.go` | add(含 22 個縣市代碼白名單、`Normalize`、`MatchesRegion`) | | `internal/module/radar/domain/repository.go` | add(`Repository` 介面) | | `internal/module/radar/repository/service_profile_{mongo,memory}.go` | add | | `internal/module/radar/usecase/service_profile.go` | add(Get/Has/Upsert) | | `internal/module/radar/usecase/service_profile_test.go` | add(9 則) | ### 幾個刻意的決定 - **`Get` 未建檔回 `ErrNotFound`,不回零值檔案。** 「沒建檔」與「建了一份空的」必須分得出來,SP-01 的訂閱閘門就是靠這個差別;HTTP 層才把它翻成 `exists=false` 的 200。 - **價格只在兩端都填時比大小。** 只填 `price_min` 是「起價」、只填 `price_max` 是「上限」,都是真實的報價方式,硬要成對會逼使用者亂填。填了價格但沒填幣別時補 `TWD`。 - **`service_areas` 空且 `remote_ok=false` 直接拒絕。** 兩者皆空時,五問判定的地區這一問沒有任何依據可用。 - **`Upsert` 是整份覆寫,但保留 `created_at`。** 不做欄位合併,否則使用者永遠刪不掉一個服務項目或禁語。