3.0 KiB
3.0 KiB
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
- unit test:upsert → get 欄位完整、
forbidden[]可讀、價格區間反向被拒 - 指令:
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。 不做欄位合併,否則使用者永遠刪不掉一個服務項目或禁語。