# 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 讀不到也寫不壞自己的檔案。
