# T511 — service-profile-api-get-put > Status: `done`(2026-07-31) > Milestone: `M1` > Kind: `feat` > Est. change: `~160 lines` ## Goal 完成後系統應:`GET`/`PUT /api/v1/radar/service-profile` 真實可用,欄位完整往返並以 `owner_uid` 隔離。 ## Depends on - T500、T510 ## Inputs - Spec:`../spec.md` §4.1、§5.1、§5.6、§9.1(SP-02) - 既有 logic 模式:`apps/backend/internal/logic/scout/**` ## Outputs ### 程式變更(預期路徑) | 路徑 | 動作 | 說明 | |------|------|------| | `apps/backend/generate/api/radar.api` | edit | service-profile req/resp type 定稿 | | `apps/backend/internal/logic/radar/getServiceProfileLogic.go` | edit | 讀取;未建立回明確「尚未建立」狀態 | | `apps/backend/internal/logic/radar/putServiceProfileLogic.go` | edit | 整份 upsert+驗證錯誤映射 | | `apps/backend/internal/svc/servicecontext.go` | edit | 掛入 radar usecase | ### 行為變更 - 成功 `102000`;驗證失敗回明確 message(欄位名+原因),非空成功。 - 只能讀寫自己的檔案;`owner_uid` 一律取 JWT,request 帶的 uid 忽略。 ### API / 契約 - `GET` 回 `{ exists, services[], cases[], forbidden[], faq[], service_areas[], remote_ok, availability, tone_note, updated_at }`。 - `PUT` 回更新後全量。 ## Out of scope - 前端畫面(T515) - watch 前置條件檢查(T513) ## Acceptance - [x] SP-02:填寫後 `GET` 欄位完整、`forbidden[]` 可讀 - [x] 別人的 uid 無法讀寫(回越權錯誤) - [x] 指令: ```bash cd apps/backend && make gen-api && go test ./internal/logic/radar/... -count=1 && make build ``` ## Notes(2026-07-31) | 路徑 | 動作 | |------|------| | `internal/logic/radarmap/map.go` | add(domain ↔ types 映射,含 `Pagination` helper) | | `internal/logic/radar/owner.go` | add(`ownerUID`:一律取 JWT,request 帶的 uid 忽略) | | `internal/logic/radar/{get,upsert}_service_profile_logic.go` | edit | | `internal/svc/service_context.go` | edit(掛 `Radar *radarUC.Service`) | | `internal/middleware/auth.go` | edit(新增 `WithUID`,見下) | | `internal/logic/radar/service_profile_logic_test.go` | add(5 則) | - `generate/api/radar.api` **未改**:T500 定的 req/resp 型別已足夠,不需要重跑契約。 - **未建檔回 `exists=false` 的 200,不是 404。** 表單得先開得起來才有東西填;誠實之處在 `exists` 欄位,而不是用錯誤碼表達。空陣列而非 `null`,前端不必每個欄位判空。 - **驗證錯誤映射成 400100**,`cleanBizMessage` 會把 `radar validation: ` 前綴切掉,所以前端拿到的是「services[0].name required」這種能對到欄位的訊息。 - **`middleware.WithUID` 是新增的匯出入口。** `ctxUID` 是私有型別,沒有它就只能繞過 `UIDFrom` 來測,那測到的就不是真的授權路徑。測試證明別人的 uid 讀不到也寫不壞自己的檔案。