docs(api): group OpenAPI by tags + add Chinese field descriptions and enums

Make the generated docs/openapi/gateway.yaml usable by adding three things
go-doc parses out of the .api source:

- @server tags + summary on every block → Swagger UI groups endpoints
  (Auth / Member / Permission / Normal) instead of dumping everything
  under "default".
- backtick end-of-line // 中文 on every Request field → property
  descriptions in the schema. go-doc only reads the trailing comment,
  not the line above, so all comments are placed on the same line as
  the tag.
- options=A|B|C in json/form tags wherever validate:"oneof=..." exists
  → enum dropdowns. The validate tag is kept for runtime validation;
  go-zero also enforces options= at bind time.

Codify the rules in generate/api/README.md (tags / 行末註解 / options=)
and add AGENTS.md at repo root so any AI agent (Claude / Cursor / Codex)
picks them up automatically when working on the project.

types.go regenerated via make gen-api to keep json tags in sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王性驊 2026-05-21 17:15:25 +08:00
parent fa50c64ee4
commit 4590f1c951
7 changed files with 317 additions and 166 deletions

74
AGENTS.md Normal file
View File

@ -0,0 +1,74 @@
# AGENTS.md
給 AI coding agentClaude / Cursor / Codex / 其他)的專案工作準則。請在開始任務前讀過一遍,並在需要時翻閱對應子文件。
## 專案簡介
`template-monorepo` 是基於 [go-zero](https://github.com/zeromicro/go-zero) 的 API Gateway,採用「**模組化 Clean Architecture**」:每個業務模組auth / member / notification / permission ...)放在 `internal/model/<module>/`,內部分 `domain`(介面 + enum + errors) / `repository`(Mongo / Redis 實作) / `usecase`(原子業務邏輯) / `config`
跨模組編排(例如「發 OTP → 寄信 → 驗碼 → 更新 profile」一律放在 `internal/logic/<module>/`,**usecase 不可呼叫其他 usecase**。
## 必讀文件
| 文件 | 何時讀 |
|---|---|
| [`generate/api/README.md`](generate/api/README.md) | 新增 / 修改 API 端點、type、文件分組、欄位描述、enum 列舉前 |
| [`generate/doc-generate/README.md`](generate/doc-generate/README.md) | 需要查 go-doc 支援的 tag / `@respdoc` 寫法時 |
| `internal/model/<module>/README.md` | 動到該模組的領域邏輯時 |
| `docs/model.md`(若存在) | 全局架構規範 |
## 標準工作流程
### 1. 修改 API`.api` → handler / types / docs
1. 編輯 `generate/api/*.api`(遵守 `generate/api/README.md` 的三條規則tags 分組 / backtick 行末 `//` 中文 description / `options=A|B|C` enum)
2. `make gen-api` — 重新產生 `internal/handler/`、`internal/logic/`(已存在則不覆蓋)、`internal/types/types.go`
3. `make gen-doc` — 重新產生 `docs/openapi/gateway.yaml`(gitignore,本地驗證用)
4. 實作 / 修改 `internal/logic/<module>/<handler>_logic.go` 的業務邏輯
5. `go build ./...` 確保編譯通過
6. `make lint` / `make test` 視改動範圍跑
### 2. 新增 / 修改業務模組
- 領域介面與型別放 `internal/model/<module>/domain/`
- Mongo / Redis 實作放 `internal/model/<module>/repository/`
- 原子 usecase 放 `internal/model/<module>/usecase/`(**不可**互相呼叫)
- 多步驟流程編排放 `internal/logic/<module>/`
- 模組的對外裝配入口統一在 `internal/model/<module>/usecase/module.go`,並從 `internal/svc/service_context.go` 注入
### 3. 錯誤碼
- 業務碼格式 `SSCCCDDD`(scope * 1_000_000 + category * 1_000 + detail)
- Scope 註冊在 `internal/library/errors/code/types.go`(Facade=10, Auth=28, Member=29, Notification=30, Permission=31)
- 新增 scope 時:同步更新 `gateway.api``bizCodeEnumDescription`
### 4. Redis / Mongo / 設定
- 每個模組的設定型別放 `internal/model/<module>/config/config.go`,再合入 `internal/config/config.go``Config` struct
- Redis client 共用 `internal/library/redis/`,需 Pub/Sub 用 `client.PubSubClient()`
- Mongo index 註冊到 `cmd/mongo-index/main.go`(在 `run()` 裡呼叫 `<module>repo.EnsureMongoIndexes`)
## 通用準則
- **回應 traditional Chinese**(繁體中文)
- 程式碼註解只寫「為什麼」、邊界條件、trade-off,**不寫**「import the module / increment counter」這類顯而易見的描述
- 不主動建立 `*.md` 文件,除非使用者明確要求
- 改 git config / 強制 push / `rm -rf` 等破壞性操作 **必須**先取得使用者同意
- 不要在沒被要求時直接 commit;commit 前先 `git status` / `git diff` 確認
- commit message 用繁中描述「為什麼」改,不是「改了什麼」
## 指令速查
| 指令 | 用途 |
|---|---|
| `make gen-api` | `.api` → handler / logic(skip exists)/ types |
| `make gen-doc` | `.api``docs/openapi/gateway.yaml` |
| `make gen-mock` | 模組 mock(gomock) |
| `make tools` | 安裝 goctl / goimports / golangci-lint |
| `make fix` | gofmt + goimports + lint --fix + lint |
| `make check` | fix + test(提交前) |
| `make run-dev` | 本機啟動(需 `make deps-up`) |
| `make deps-up` | docker compose Mongo + Redis |
| `make mongo-index` | 建立 / 更新 Mongo 索引 |
完整列表跑 `make help`

View File

@ -8,6 +8,7 @@
| `common.api` | 共用文件型別(`APIErrorStatus`、`ErrorDetail` | | `common.api` | 共用文件型別(`APIErrorStatus`、`ErrorDetail` |
| `auth.api` | Auth 路由scope 28 | | `auth.api` | Auth 路由scope 28 |
| `member.api` | Member 路由scope 29 | | `member.api` | Member 路由scope 29 |
| `permission.api` | Permission / RBAC 路由scope 31 |
| `normal.api` | 路由與業務 `data` 型別 | | `normal.api` | 路由與業務 `data` 型別 |
## 指令 ## 指令
@ -25,6 +26,74 @@ make gen-doc # 生成 docs/openapi/gateway.yamlOpenAPI 3.0
- 多狀態碼用 `/* @respdoc-200 ... */` 區塊,放在 `@handler` - 多狀態碼用 `/* @respdoc-200 ... */` 區塊,放在 `@handler`
- **Request 驗證**:欄位可加 `validate:"required,email"` 等 tag`make gen-api` 後 handler 會自動 `ValidateAll`(見 `generate/goctl/api/handler.tpl` - **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-docvalidate= 給 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.gojson tag 帶 options= 給 go-zero runtime
make gen-doc # 同步 docs/openapi/gateway.yamlgitignore本地驗證用
go build ./... # 確保編譯通過
```
驗證:把 `docs/openapi/gateway.yaml` 丟進 https://editor.swagger.io 或 Swagger UI確認分組、description、enum 都正確顯示。
## 與 runtime 對齊 ## 與 runtime 對齊
Handler 使用 `response.Write` 輸出: Handler 使用 `response.Write` 輸出:

View File

@ -2,14 +2,14 @@ syntax = "v1"
type ( type (
RegisterReq { RegisterReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug小寫英數
InviteCode string `json:"invite_code" validate:"required"` InviteCode string `json:"invite_code" validate:"required"` // 邀請碼
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"` // 電子郵件
Password string `json:"password" validate:"required,min=8,max=128"` Password string `json:"password" validate:"required,min=8,max=128"` // 密碼8-128 字元)
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 顯示名稱(可選)
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本
MarketingOptIn bool `json:"marketing_opt_in,optional"` MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息
} }
RegisterData { RegisterData {
@ -19,14 +19,14 @@ type (
} }
RegisterConfirmReq { RegisterConfirmReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
ChallengeID string `json:"challenge_id" validate:"required"` ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID由 /register 回傳)
Code string `json:"code" validate:"required,len=6"` Code string `json:"code" validate:"required,len=6"` // 6 位數 OTP 驗證碼
} }
RegisterResendReq { RegisterResendReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
ChallengeID string `json:"challenge_id" validate:"required"` ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID
} }
AuthTokenData { AuthTokenData {
@ -38,13 +38,13 @@ type (
} }
RegisterSocialStartReq { RegisterSocialStartReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
InviteCode string `json:"invite_code" validate:"required"` InviteCode string `json:"invite_code" validate:"required"` // 邀請碼
Provider string `json:"provider" validate:"required,oneof=google"` Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google
AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
RedirectURI string `json:"redirect_uri" validate:"required,url"` RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI
MarketingOptIn bool `json:"marketing_opt_in,optional"` MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息
} }
RegisterSocialStartData { RegisterSocialStartData {
@ -54,29 +54,29 @@ type (
} }
RegisterSocialCallbackReq { RegisterSocialCallbackReq {
Code string `form:"code" validate:"required"` Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code
State string `form:"state" validate:"required"` State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state對應 session
} }
LoginReq { LoginReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"` // 電子郵件
Password string `json:"password" validate:"required,min=8,max=128"` Password string `json:"password" validate:"required,min=8,max=128"` // 密碼8-128 字元)
} }
TokenRefreshReq { TokenRefreshReq {
RefreshToken string `json:"refresh_token" validate:"required"` RefreshToken string `json:"refresh_token" validate:"required"` // 先前核發的 refresh token
} }
TokenExchangeReq { TokenExchangeReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
IDToken string `json:"id_token" validate:"required"` IDToken string `json:"id_token" validate:"required"` // ZITADEL 發行的 id_token
} }
LoginSocialStartReq { LoginSocialStartReq {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
Provider string `json:"provider" validate:"required,oneof=google"` Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google
RedirectURI string `json:"redirect_uri" validate:"required,url"` RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI
} }
LoginSocialStartData { LoginSocialStartData {
@ -86,8 +86,8 @@ type (
} }
LoginSocialCallbackReq { LoginSocialCallbackReq {
Code string `form:"code" validate:"required"` Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code
State string `form:"state" validate:"required"` State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state對應 session
} }
LogoutData { LogoutData {
@ -129,6 +129,8 @@ type (
@server( @server(
group: auth group: auth
prefix: /api/v1/auth prefix: /api/v1/auth
tags: "Auth - 認證"
summary: "註冊 / 登入 / Token / 登出"
) )
service gateway { service gateway {
@doc "Email 註冊(建立 ZITADEL + member寄 registration OTP" @doc "Email 註冊(建立 ZITADEL + member寄 registration OTP"

View File

@ -22,15 +22,15 @@ type (
} }
UpdateMemberMeReq { UpdateMemberMeReq {
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 顯示名稱(可選)
Avatar string `json:"avatar,optional"` Avatar string `json:"avatar,optional"` // 頭像 URL可選
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
Currency string `json:"currency,optional"` Currency string `json:"currency,optional"` // 幣別代碼,如 TWD / USD可選
Phone string `json:"phone,optional"` Phone string `json:"phone,optional"` // 聯絡電話 E.164 格式(可選)
} }
VerificationStartReq { VerificationStartReq {
Target string `json:"target"` Target string `json:"target"` // 驗證目標email 地址或 E.164 手機號(依端點而定)
} }
VerificationStartData { VerificationStartData {
@ -39,8 +39,8 @@ type (
} }
VerificationConfirmReq { VerificationConfirmReq {
ChallengeID string `json:"challenge_id"` ChallengeID string `json:"challenge_id"` // 驗證流程的 OTP challenge ID由 /start 端點回傳)
Code string `json:"code"` Code string `json:"code"` // 6 位數 OTP 驗證碼
} }
TOTPStatusData { TOTPStatusData {
@ -61,7 +61,7 @@ type (
} }
TOTPEnrollConfirmReq { TOTPEnrollConfirmReq {
Code string `json:"code"` Code string `json:"code"` // Google Authenticator 顯示的 6 位數 TOTP 碼
} }
TOTPEnrollConfirmData { TOTPEnrollConfirmData {
@ -69,7 +69,7 @@ type (
} }
TOTPVerifyReq { TOTPVerifyReq {
Code string `json:"code"` Code string `json:"code"` // TOTP 6 位數碼,或 8 位數備援碼
} }
TOTPBackupCodesData { TOTPBackupCodesData {
@ -117,6 +117,8 @@ type (
@server( @server(
group: member group: member
prefix: /api/v1/members prefix: /api/v1/members
tags: "Member - 會員"
summary: "Profile / 業務 Email 與 Phone 驗證 / TOTP MFA"
) )
service gateway { service gateway {
@doc "取得當前會員 profileBearer JWT本機 dev 可 fallback X-Tenant-ID + X-UID" @doc "取得當前會員 profileBearer JWT本機 dev 可 fallback X-Tenant-ID + X-UID"

View File

@ -17,6 +17,8 @@ type PingOKStatus {
prefix: /api/v1 prefix: /api/v1
schemes: https schemes: https
timeout: 3s timeout: 3s
tags: "Normal - 公開"
summary: "健康檢查 / Ping"
) )
service gateway { service gateway {
@doc( @doc(

View File

@ -4,9 +4,9 @@ type (
// ===== Permission catalog ===== // ===== Permission catalog =====
PermissionCatalogQuery { PermissionCatalogQuery {
Status string `form:"status,optional" validate:"omitempty,oneof=open close"` Status string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 狀態篩選;可選值: open / close
Type string `form:"type,optional" validate:"omitempty,oneof=backend_user frontend_user"` Type string `form:"type,optional,options=backend_user|frontend_user" validate:"omitempty,oneof=backend_user frontend_user"` // 權限類型篩選;可選值: backend_user / frontend_user
Tree bool `form:"tree,optional"` Tree bool `form:"tree,optional"` // 是否回傳樹狀結構false 則扁平 list
} }
PermissionNode { PermissionNode {
@ -28,7 +28,7 @@ type (
// ===== Me permissions ===== // ===== Me permissions =====
MePermissionsQuery { MePermissionsQuery {
IncludeTree bool `form:"include_tree,optional"` IncludeTree bool `form:"include_tree,optional"` // 是否同時回傳 permission treetrue 會多包一份樹狀結構)
} }
MePermissionsData { MePermissionsData {
@ -58,48 +58,48 @@ type (
} }
CreateRoleReq { CreateRoleReq {
Key string `json:"key" validate:"required,min=2,max=64"` Key string `json:"key" validate:"required,min=2,max=64"` // 角色 key2-64 字元,不可以 system. / platform_ 開頭)
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱(給人看的)
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close預設 open
} }
UpdateRoleReq { UpdateRoleReq {
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close
} }
UpdateRoleByIDReq { UpdateRoleByIDReq {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close
} }
DeleteRoleByIDReq { DeleteRoleByIDReq {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
} }
GetRolePermissionsByIDReq { GetRolePermissionsByIDReq {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
} }
ReplaceRolePermissionsByIDReq { ReplaceRolePermissionsByIDReq {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
PermissionIDs []string `json:"permission_ids"` PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限)
} }
ListUserRolesReq { ListUserRolesReq {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
} }
AssignUserRoleByUIDReq { AssignUserRoleByUIDReq {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `json:"role_id" validate:"required"` RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID
Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim預設 manual
} }
RevokeUserRoleByIDReq { RevokeUserRoleByIDReq {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `path:"role_id"` RoleID string `path:"role_id"` // Role IDpath
} }
// ===== Role permissions ===== // ===== Role permissions =====
@ -109,13 +109,13 @@ type (
} }
ReplaceRolePermissionsReq { ReplaceRolePermissionsReq {
PermissionIDs []string `json:"permission_ids"` PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限)
} }
// ===== User roles ===== // ===== User roles =====
UIDPath { UIDPath {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
} }
UserRoleData { UserRoleData {
@ -135,13 +135,13 @@ type (
} }
AssignUserRoleReq { AssignUserRoleReq {
RoleID string `json:"role_id" validate:"required"` RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID
Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim預設 manual
} }
UserRoleIDPath { UserRoleIDPath {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `path:"role_id"` RoleID string `path:"role_id"` // Role IDpath
} }
// ===== Role mappings ===== // ===== Role mappings =====
@ -165,26 +165,26 @@ type (
} }
RoleMappingListQuery { RoleMappingListQuery {
Source string `form:"source,optional" validate:"omitempty,oneof=zitadel ldap scim"` Source string `form:"source,optional,options=zitadel|ldap|scim" validate:"omitempty,oneof=zitadel ldap scim"` // 外部來源篩選;可選值: zitadel / ldap / scim
Offset int64 `form:"offset,optional"` Offset int64 `form:"offset,optional"` // 分頁起點(從 0 起算)
Limit int64 `form:"limit,optional"` Limit int64 `form:"limit,optional"` // 每頁筆數(最大值由 server 限制)
} }
UpsertRoleMappingReq { UpsertRoleMappingReq {
ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` ExternalSource string `json:"external_source,options=zitadel|ldap|scim" validate:"required,oneof=zitadel ldap scim"` // 外部來源;可選值: zitadel / ldap / scim
ExternalKey string `json:"external_key" validate:"required"` ExternalKey string `json:"external_key" validate:"required"` // 外部群組 / claim 的 key例如 zitadel role 名稱)
InternalRoleKey string `json:"internal_role_key" validate:"required"` InternalRoleKey string `json:"internal_role_key" validate:"required"` // 對映的內部角色 key必須存在於 roles 集合)
} }
DeleteRoleMappingReq { DeleteRoleMappingReq {
ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` ExternalSource string `json:"external_source,options=zitadel|ldap|scim" validate:"required,oneof=zitadel ldap scim"` // 外部來源;可選值: zitadel / ldap / scim
ExternalKey string `json:"external_key" validate:"required"` ExternalKey string `json:"external_key" validate:"required"` // 外部群組 / claim 的 key
} }
// ===== Policy reload ===== // ===== Policy reload =====
PolicyReloadReq { PolicyReloadReq {
TenantID string `json:"tenant_id,optional"` TenantID string `json:"tenant_id,optional"` // 指定要 reload 的租戶 ID留空則 reload 所有租戶
} }
PolicyReloadData { PolicyReloadData {
@ -258,6 +258,8 @@ type (
@server( @server(
group: permission group: permission
prefix: /api/v1/permissions prefix: /api/v1/permissions
tags: "Permission - 權限"
summary: "Catalog / 角色 / 使用者角色 / 外部映射 / Policy"
) )
service gateway { service gateway {
@doc "取得全局 Permission Catalog樹狀或扁平可篩 status/type" @doc "取得全局 Permission Catalog樹狀或扁平可篩 status/type"

View File

@ -10,14 +10,14 @@ type APIErrorStatus struct {
} }
type AssignUserRoleByUIDReq struct { type AssignUserRoleByUIDReq struct {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `json:"role_id" validate:"required"` RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID
Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim預設 manual
} }
type AssignUserRoleReq struct { type AssignUserRoleReq struct {
RoleID string `json:"role_id" validate:"required"` RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID
Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim預設 manual
} }
type AuthTokenData struct { type AuthTokenData struct {
@ -35,18 +35,18 @@ type AuthTokenOKStatus struct {
} }
type CreateRoleReq struct { type CreateRoleReq struct {
Key string `json:"key" validate:"required,min=2,max=64"` Key string `json:"key" validate:"required,min=2,max=64"` // 角色 key2-64 字元,不可以 system. / platform_ 開頭)
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱(給人看的)
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close預設 open
} }
type DeleteRoleByIDReq struct { type DeleteRoleByIDReq struct {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
} }
type DeleteRoleMappingReq struct { type DeleteRoleMappingReq struct {
ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` ExternalSource string `json:"external_source,options=zitadel|ldap|scim" validate:"required,oneof=zitadel ldap scim"` // 外部來源;可選值: zitadel / ldap / scim
ExternalKey string `json:"external_key" validate:"required"` ExternalKey string `json:"external_key" validate:"required"` // 外部群組 / claim 的 key
} }
type EmptyOKStatus struct { type EmptyOKStatus struct {
@ -62,22 +62,22 @@ type ErrorDetail struct {
} }
type GetRolePermissionsByIDReq struct { type GetRolePermissionsByIDReq struct {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
} }
type ListUserRolesReq struct { type ListUserRolesReq struct {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
} }
type LoginReq struct { type LoginReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"` // 電子郵件
Password string `json:"password" validate:"required,min=8,max=128"` Password string `json:"password" validate:"required,min=8,max=128"` // 密碼8-128 字元)
} }
type LoginSocialCallbackReq struct { type LoginSocialCallbackReq struct {
Code string `form:"code" validate:"required"` Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code
State string `form:"state" validate:"required"` State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state對應 session
} }
type LoginSocialStartData struct { type LoginSocialStartData struct {
@ -93,9 +93,9 @@ type LoginSocialStartOKStatus struct {
} }
type LoginSocialStartReq struct { type LoginSocialStartReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
Provider string `json:"provider" validate:"required,oneof=google"` Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google
RedirectURI string `json:"redirect_uri" validate:"required,url"` RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI
} }
type LogoutData struct { type LogoutData struct {
@ -123,7 +123,7 @@ type MePermissionsOKStatus struct {
} }
type MePermissionsQuery struct { type MePermissionsQuery struct {
IncludeTree bool `form:"include_tree,optional"` IncludeTree bool `form:"include_tree,optional"` // 是否同時回傳 permission treetrue 會多包一份樹狀結構)
} }
type MemberMeData struct { type MemberMeData struct {
@ -164,9 +164,9 @@ type PermissionCatalogOKStatus struct {
} }
type PermissionCatalogQuery struct { type PermissionCatalogQuery struct {
Status string `form:"status,optional" validate:"omitempty,oneof=open close"` Status string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 狀態篩選;可選值: open / close
Type string `form:"type,optional" validate:"omitempty,oneof=backend_user frontend_user"` Type string `form:"type,optional,options=backend_user|frontend_user" validate:"omitempty,oneof=backend_user frontend_user"` // 權限類型篩選;可選值: backend_user / frontend_user
Tree bool `form:"tree,optional"` Tree bool `form:"tree,optional"` // 是否回傳樹狀結構false 則扁平 list
} }
type PermissionNode struct { type PermissionNode struct {
@ -202,13 +202,13 @@ type PolicyReloadOKStatus struct {
} }
type PolicyReloadReq struct { type PolicyReloadReq struct {
TenantID string `json:"tenant_id,optional"` TenantID string `json:"tenant_id,optional"` // 指定要 reload 的租戶 ID留空則 reload 所有租戶
} }
type RegisterConfirmReq struct { type RegisterConfirmReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
ChallengeID string `json:"challenge_id" validate:"required"` ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID由 /register 回傳)
Code string `json:"code" validate:"required,len=6"` Code string `json:"code" validate:"required,len=6"` // 6 位數 OTP 驗證碼
} }
type RegisterData struct { type RegisterData struct {
@ -224,24 +224,24 @@ type RegisterOKStatus struct {
} }
type RegisterReq struct { type RegisterReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug小寫英數
InviteCode string `json:"invite_code" validate:"required"` InviteCode string `json:"invite_code" validate:"required"` // 邀請碼
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"` // 電子郵件
Password string `json:"password" validate:"required,min=8,max=128"` Password string `json:"password" validate:"required,min=8,max=128"` // 密碼8-128 字元)
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 顯示名稱(可選)
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本
MarketingOptIn bool `json:"marketing_opt_in,optional"` MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息
} }
type RegisterResendReq struct { type RegisterResendReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
ChallengeID string `json:"challenge_id" validate:"required"` ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID
} }
type RegisterSocialCallbackReq struct { type RegisterSocialCallbackReq struct {
Code string `form:"code" validate:"required"` Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code
State string `form:"state" validate:"required"` State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state對應 session
} }
type RegisterSocialStartData struct { type RegisterSocialStartData struct {
@ -257,27 +257,27 @@ type RegisterSocialStartOKStatus struct {
} }
type RegisterSocialStartReq struct { type RegisterSocialStartReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
InviteCode string `json:"invite_code" validate:"required"` InviteCode string `json:"invite_code" validate:"required"` // 邀請碼
Provider string `json:"provider" validate:"required,oneof=google"` Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google
AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
RedirectURI string `json:"redirect_uri" validate:"required,url"` RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI
MarketingOptIn bool `json:"marketing_opt_in,optional"` MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息
} }
type ReplaceRolePermissionsByIDReq struct { type ReplaceRolePermissionsByIDReq struct {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
PermissionIDs []string `json:"permission_ids"` PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限)
} }
type ReplaceRolePermissionsReq struct { type ReplaceRolePermissionsReq struct {
PermissionIDs []string `json:"permission_ids"` PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限)
} }
type RevokeUserRoleByIDReq struct { type RevokeUserRoleByIDReq struct {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `path:"role_id"` RoleID string `path:"role_id"` // Role IDpath
} }
type RoleData struct { type RoleData struct {
@ -327,9 +327,9 @@ type RoleMappingListOKStatus struct {
} }
type RoleMappingListQuery struct { type RoleMappingListQuery struct {
Source string `form:"source,optional" validate:"omitempty,oneof=zitadel ldap scim"` Source string `form:"source,optional,options=zitadel|ldap|scim" validate:"omitempty,oneof=zitadel ldap scim"` // 外部來源篩選;可選值: zitadel / ldap / scim
Offset int64 `form:"offset,optional"` Offset int64 `form:"offset,optional"` // 分頁起點(從 0 起算)
Limit int64 `form:"limit,optional"` Limit int64 `form:"limit,optional"` // 每頁筆數(最大值由 server 限制)
} }
type RoleMappingOKStatus struct { type RoleMappingOKStatus struct {
@ -375,7 +375,7 @@ type TOTPEnrollConfirmOKStatus struct {
} }
type TOTPEnrollConfirmReq struct { type TOTPEnrollConfirmReq struct {
Code string `json:"code"` Code string `json:"code"` // Google Authenticator 顯示的 6 位數 TOTP 碼
} }
type TOTPEnrollStartData struct { type TOTPEnrollStartData struct {
@ -408,45 +408,45 @@ type TOTPStatusOKStatus struct {
} }
type TOTPVerifyReq struct { type TOTPVerifyReq struct {
Code string `json:"code"` Code string `json:"code"` // TOTP 6 位數碼,或 8 位數備援碼
} }
type TokenExchangeReq struct { type TokenExchangeReq struct {
TenantSlug string `json:"tenant_slug" validate:"required"` TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug
IDToken string `json:"id_token" validate:"required"` IDToken string `json:"id_token" validate:"required"` // ZITADEL 發行的 id_token
} }
type TokenRefreshReq struct { type TokenRefreshReq struct {
RefreshToken string `json:"refresh_token" validate:"required"` RefreshToken string `json:"refresh_token" validate:"required"` // 先前核發的 refresh token
} }
type UIDPath struct { type UIDPath struct {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
} }
type UpdateMemberMeReq struct { type UpdateMemberMeReq struct {
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 顯示名稱(可選)
Avatar string `json:"avatar,optional"` Avatar string `json:"avatar,optional"` // 頭像 URL可選
Language string `json:"language,optional"` Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en可選
Currency string `json:"currency,optional"` Currency string `json:"currency,optional"` // 幣別代碼,如 TWD / USD可選
Phone string `json:"phone,optional"` Phone string `json:"phone,optional"` // 聯絡電話 E.164 格式(可選)
} }
type UpdateRoleByIDReq struct { type UpdateRoleByIDReq struct {
ID string `path:"id"` ID string `path:"id"` // Role IDpath
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close
} }
type UpdateRoleReq struct { type UpdateRoleReq struct {
DisplayName string `json:"display_name,optional"` DisplayName string `json:"display_name,optional"` // 角色顯示名稱
Status string `json:"status,optional" validate:"omitempty,oneof=open close"` Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close
} }
type UpsertRoleMappingReq struct { type UpsertRoleMappingReq struct {
ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` ExternalSource string `json:"external_source,options=zitadel|ldap|scim" validate:"required,oneof=zitadel ldap scim"` // 外部來源;可選值: zitadel / ldap / scim
ExternalKey string `json:"external_key" validate:"required"` ExternalKey string `json:"external_key" validate:"required"` // 外部群組 / claim 的 key例如 zitadel role 名稱)
InternalRoleKey string `json:"internal_role_key" validate:"required"` InternalRoleKey string `json:"internal_role_key" validate:"required"` // 對映的內部角色 key必須存在於 roles 集合)
} }
type UserRoleData struct { type UserRoleData struct {
@ -462,8 +462,8 @@ type UserRoleData struct {
} }
type UserRoleIDPath struct { type UserRoleIDPath struct {
UID string `path:"uid"` UID string `path:"uid"` // 使用者 UIDpath
RoleID string `path:"role_id"` RoleID string `path:"role_id"` // Role IDpath
} }
type UserRoleListData struct { type UserRoleListData struct {
@ -483,8 +483,8 @@ type UserRoleOKStatus struct {
} }
type VerificationConfirmReq struct { type VerificationConfirmReq struct {
ChallengeID string `json:"challenge_id"` ChallengeID string `json:"challenge_id"` // 驗證流程的 OTP challenge ID由 /start 端點回傳)
Code string `json:"code"` Code string `json:"code"` // 6 位數 OTP 驗證碼
} }
type VerificationStartData struct { type VerificationStartData struct {
@ -499,5 +499,5 @@ type VerificationStartOKStatus struct {
} }
type VerificationStartReq struct { type VerificationStartReq struct {
Target string `json:"target"` Target string `json:"target"` // 驗證目標email 地址或 E.164 手機號(依端點而定)
} }