# API 定義（goctl + go-doc 共用）

## 檔案

| 檔案 | 用途 |
|------|------|
| `gateway.api` | 入口：`info()` + `import` |
| `common.api` | 共用文件型別（`APIErrorStatus`、`ErrorDetail`） |
| `auth.api` | Auth 路由（scope 28） |
| `member.api` | Member 路由（scope 29） |
| `permission.api` | Permission / RBAC 路由（scope 31） |
| `normal.api` | 路由與業務 `data` 型別 |

## 指令

```bash
make gen-api   # 生成 handler / logic / types
make gen-doc   # 生成 docs/openapi/gateway.yaml（OpenAPI 3.0）
```

## 註解約定

- **Logic `returns`**：只寫業務 data（如 `PingData`）
- **文件 `@respdoc`**：寫實際 HTTP JSON（如 `PingOKStatus`、`APIErrorStatus`）
- **`@doc`**：單一 API 的 summary / description
- 多狀態碼用 `/* @respdoc-200 ... */` 區塊，放在 `@handler` 前
- **Request 驗證**：欄位可加 `validate:"required,email"` 等 tag；`make gen-api` 後 handler 會自動 `ValidateAll`（見 `generate/goctl/api/handler.tpl`）

## 文件分組與欄位說明（go-doc 規則）

OpenAPI 由 `generate/doc-generate`（go-doc）從 `.api` 直接讀取，下列三種寫法**必須**遵守，AI agent 在新增 / 修改 API 時請一併照做：

### 1. Swagger UI 分組（tags）

每個 `.api` 檔的 `@server (...)` 區塊**必須**帶 `tags:` 與 `summary:`，否則 endpoint 會散落在 "default" 群組。

```go
@server (
    group:   permission        // goctl 用：決定 handler 子目錄
    prefix:  /api/v1/permissions
    tags:    "Permission - 權限"      // ← go-doc 用：Swagger UI 分組標題
    summary: "Catalog / 角色 / 使用者角色 / 外部映射 / Policy"
)
```

> 一個 `.api` 內可以開**多個** `@server` 區塊（相同 `group` / `prefix` 但不同 `tags:`），把同一模組再拆成子組。

### 2. 欄位中文 description

go-doc 把欄位的 description **只認 backtick 結尾的同一行 `//` 註解**，寫在前一行不會被解析。

```go
// ❌ 不會被當 description
RegisterReq {
    // 租戶 slug
    TenantSlug string `json:"tenant_slug" validate:"required"`
}

// ✅ 正確寫法（backtick 行末 //）
RegisterReq {
    TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug（小寫英數）
    Email      string `json:"email" validate:"required,email"` // 電子郵件
    Password   string `json:"password" validate:"required,min=8,max=128"` // 密碼（8-128 字元）
}
```

**所有 Request 型別**（命名 `*Req` / `*Query` / `*Path` 的 struct）的**每個欄位**都要加中文 description。Response / Data 型別可選。

### 3. Enum 列舉值（Swagger UI 下拉選單）

`validate:"oneof=A B C"` 是 runtime 驗證用的，**go-doc 不會解析**。要讓 Swagger UI 顯示下拉選單，必須在 tag 內加 `options=A|B|C`（管道分隔）：

```go
// ❌ 只有 validate 不會出 enum
Provider string `json:"provider" validate:"required,oneof=google"`

// ✅ 同時保留兩者：options= 給 go-doc，validate= 給 runtime
Provider string `json:"provider,options=google" validate:"required,oneof=google"`
Source   string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"`
Status   string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"`
```

**規則**：只要有 `oneof=...`，就一定要在同欄位 tag 加對應 `options=...`，並把可選值也寫進行末註解。

### 4. 修改流程

每次改 `.api` 後**必跑**：

```bash
make gen-api   # 同步 internal/types/types.go（json tag 帶 options= 給 go-zero runtime）
make gen-doc   # 同步 docs/openapi/gateway.yaml（gitignore，本地驗證用）
go build ./... # 確保編譯通過
```

驗證:把 `docs/openapi/gateway.yaml` 丟進 https://editor.swagger.io 或 Swagger UI，確認分組、description、enum 都正確顯示。

## Middleware（go-zero 正規手段）

**強制原則**：所有需要登入 / 授權的 endpoint 都用 `.api` 的 `middleware:` 宣告，**不要**在 `gateway.go` 用 `server.Use(...)` 全域掛載。

### 設定方式

在 `@server` 加 `middleware: AuthJWT` 或 `middleware: AuthJWT,CasbinRBAC`（多個用逗號）：

```go
// 公開（不掛）— 註冊 / 登入 / token refresh / health
@server (
    group:  auth
    prefix: /api/v1/auth
    tags:   "Auth - 認證（公開）"
)
service gateway { ... register / login / token/exchange / token/refresh ... }

// 需登入（AuthJWT）— logout / member 自身資料 / Permission catalog & me
@server (
    group:      auth
    prefix:     /api/v1/auth
    middleware: AuthJWT
    tags:       "Auth - 認證（需 Bearer）"
)
service gateway { ... logout ... }

// 需登入 + RBAC（AuthJWT,CasbinRBAC）— Permission 管理類 endpoint
@server (
    group:      permission
    prefix:     /api/v1/permissions
    middleware: AuthJWT,CasbinRBAC
    tags:       "Permission - 權限（管理）"
)
service gateway { ... roles / role-mappings / users / policy/reload ... }
```

跑 `make gen-api` 後 `internal/handler/routes.go` 會自動產生：

```go
server.AddRoutes(
    rest.WithMiddlewares(
        []rest.Middleware{serverCtx.AuthJWT, serverCtx.CasbinRBAC},
        []rest.Route{ ... }...,
    ),
    rest.WithPrefix("/api/v1/permissions"),
)
```

### 撰寫新 middleware（go-zero 標準 struct 模式）

每個 middleware **必須**是 struct + `NewXxxMiddleware(...)` 建構函式 + `Handle(next http.HandlerFunc) http.HandlerFunc` 方法。範例：

```go
// internal/middleware/authjwt_middleware.go  ← 檔名由 goctl stringx 決定
package middleware

type AuthJWTMiddleware struct {
    tokens domauth.TokenUseCase
}

func NewAuthJWTMiddleware(tokens domauth.TokenUseCase) *AuthJWTMiddleware {
    return &AuthJWTMiddleware{tokens: tokens}
}

func (m *AuthJWTMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // ...檢查 / 注入 actor / 呼叫 next(w, r)
    }
}
```

**檔名規則**：goctl 會依 `.api` 的 `middleware:` 名字產生 stub 檔，命名 = `stringx.From(<Name>).ToLower() + "_middleware.go"`，例如：
- `AuthJWT` → `authjwt_middleware.go`
- `CasbinRBAC` → `casbinrbac_middleware.go`

新 middleware **必須**直接用這個檔名寫實作，goctl 下次 gen-api 才會跳過(看到「已存在」)。

### ServiceContext 注入

在 `internal/svc/service_context.go` 暴露 `rest.Middleware` 欄位並在 `NewServiceContext` 結尾 wire 起來：

```go
type ServiceContext struct {
    ...
    AuthJWT    rest.Middleware
    CasbinRBAC rest.Middleware
}

func NewServiceContext(c config.Config) *ServiceContext {
    ...
    // 結尾，所有 use case 都建好之後
    sc.AuthJWT    = middleware.NewAuthJWTMiddleware(sc.AuthToken).Handle
    sc.CasbinRBAC = middleware.NewCasbinRBACMiddleware(sc.PermissionRBAC, middleware.CasbinRBACOptions{}).Handle
    return sc
}
```

`.Handle` 是 method value（`func(next http.HandlerFunc) http.HandlerFunc`），剛好符合 `rest.Middleware` 簽名。

### Actor context 共享

JWT middleware 解析完 token 後用 `internal/library/actor.WithActor(ctx, tenantID, uid)` 注入 actor。
**所有層**(logic / usecase / 其他 middleware)讀 actor 都用 `actor.ActorFromContext(ctx)` —— 不要在各 logic 套件各自定義 `actorKey struct{}`，否則 context value 進不來。

`internal/logic/member/actor.go` 與 `internal/logic/permission/actor.go` 是 `library/actor` 的 thin alias，方便既有程式用 `member.Actor` / `member.ActorFromContext`，但底層 key 是同一個。

## 與 runtime 對齊

Handler 使用 `response.Write` 輸出：

```json
{ "code": 102000, "message": "SUCCESS", "data": { ... } }
```

失敗時含 `error.biz_code` / `error.scope` 等欄位。Handler parse 錯誤為 Facade scope（`10101000`）；各模組 logic/usecase 使用對應 scope（Auth=28、Member=29）。
