From 4590f1c9518bd426b36357e7be026c96bd0c42fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Thu, 21 May 2026 17:15:25 +0800 Subject: [PATCH] docs(api): group OpenAPI by tags + add Chinese field descriptions and enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- AGENTS.md | 74 +++++++++++++++++ generate/api/README.md | 69 ++++++++++++++++ generate/api/auth.api | 72 ++++++++-------- generate/api/member.api | 26 +++--- generate/api/normal.api | 2 + generate/api/permission.api | 80 +++++++++--------- internal/types/types.go | 160 ++++++++++++++++++------------------ 7 files changed, 317 insertions(+), 166 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ca56fe5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,74 @@ +# AGENTS.md + +給 AI coding agent(Claude / Cursor / Codex / 其他)的專案工作準則。請在開始任務前讀過一遍,並在需要時翻閱對應子文件。 + +## 專案簡介 + +`template-monorepo` 是基於 [go-zero](https://github.com/zeromicro/go-zero) 的 API Gateway,採用「**模組化 Clean Architecture**」:每個業務模組(auth / member / notification / permission ...)放在 `internal/model//`,內部分 `domain`(介面 + enum + errors) / `repository`(Mongo / Redis 實作) / `usecase`(原子業務邏輯) / `config`。 + +跨模組編排(例如「發 OTP → 寄信 → 驗碼 → 更新 profile」)一律放在 `internal/logic//`,**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//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//_logic.go` 的業務邏輯 +5. `go build ./...` 確保編譯通過 +6. `make lint` / `make test` 視改動範圍跑 + +### 2. 新增 / 修改業務模組 + +- 領域介面與型別放 `internal/model//domain/` +- Mongo / Redis 實作放 `internal/model//repository/` +- 原子 usecase 放 `internal/model//usecase/`(**不可**互相呼叫) +- 多步驟流程編排放 `internal/logic//` +- 模組的對外裝配入口統一在 `internal/model//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//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()` 裡呼叫 `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`。 diff --git a/generate/api/README.md b/generate/api/README.md index 779cb89..61624c5 100644 --- a/generate/api/README.md +++ b/generate/api/README.md @@ -8,6 +8,7 @@ | `common.api` | 共用文件型別(`APIErrorStatus`、`ErrorDetail`) | | `auth.api` | Auth 路由(scope 28) | | `member.api` | Member 路由(scope 29) | +| `permission.api` | Permission / RBAC 路由(scope 31) | | `normal.api` | 路由與業務 `data` 型別 | ## 指令 @@ -25,6 +26,74 @@ make gen-doc # 生成 docs/openapi/gateway.yaml(OpenAPI 3.0) - 多狀態碼用 `/* @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 都正確顯示。 + ## 與 runtime 對齊 Handler 使用 `response.Write` 輸出: diff --git a/generate/api/auth.api b/generate/api/auth.api index c8e7532..6db1f4f 100644 --- a/generate/api/auth.api +++ b/generate/api/auth.api @@ -2,14 +2,14 @@ syntax = "v1" type ( RegisterReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - InviteCode string `json:"invite_code" validate:"required"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=128"` - DisplayName string `json:"display_name,optional"` - Language string `json:"language,optional"` - AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` - MarketingOptIn bool `json:"marketing_opt_in,optional"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug(小寫英數) + InviteCode string `json:"invite_code" validate:"required"` // 邀請碼 + Email string `json:"email" validate:"required,email"` // 電子郵件 + Password string `json:"password" validate:"required,min=8,max=128"` // 密碼(8-128 字元) + DisplayName string `json:"display_name,optional"` // 顯示名稱(可選) + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本 + MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息 } RegisterData { @@ -19,14 +19,14 @@ type ( } RegisterConfirmReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - ChallengeID string `json:"challenge_id" validate:"required"` - Code string `json:"code" validate:"required,len=6"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID(由 /register 回傳) + Code string `json:"code" validate:"required,len=6"` // 6 位數 OTP 驗證碼 } RegisterResendReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - ChallengeID string `json:"challenge_id" validate:"required"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID } AuthTokenData { @@ -38,13 +38,13 @@ type ( } RegisterSocialStartReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - InviteCode string `json:"invite_code" validate:"required"` - Provider string `json:"provider" validate:"required,oneof=google"` - AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` - Language string `json:"language,optional"` - RedirectURI string `json:"redirect_uri" validate:"required,url"` - MarketingOptIn bool `json:"marketing_opt_in,optional"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + InviteCode string `json:"invite_code" validate:"required"` // 邀請碼 + Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google + AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本 + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI + MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息 } RegisterSocialStartData { @@ -54,29 +54,29 @@ type ( } RegisterSocialCallbackReq { - Code string `form:"code" validate:"required"` - State string `form:"state" validate:"required"` + Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code + State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state(對應 session) } LoginReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=128"` + 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 字元) } TokenRefreshReq { - RefreshToken string `json:"refresh_token" validate:"required"` + RefreshToken string `json:"refresh_token" validate:"required"` // 先前核發的 refresh token } TokenExchangeReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - IDToken string `json:"id_token" validate:"required"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + IDToken string `json:"id_token" validate:"required"` // ZITADEL 發行的 id_token } LoginSocialStartReq { - TenantSlug string `json:"tenant_slug" validate:"required"` - Provider string `json:"provider" validate:"required,oneof=google"` - RedirectURI string `json:"redirect_uri" validate:"required,url"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google + RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI } LoginSocialStartData { @@ -86,8 +86,8 @@ type ( } LoginSocialCallbackReq { - Code string `form:"code" validate:"required"` - State string `form:"state" validate:"required"` + Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code + State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state(對應 session) } LogoutData { @@ -127,8 +127,10 @@ type ( ) @server( - group: auth - prefix: /api/v1/auth + group: auth + prefix: /api/v1/auth + tags: "Auth - 認證" + summary: "註冊 / 登入 / Token / 登出" ) service gateway { @doc "Email 註冊(建立 ZITADEL + member,寄 registration OTP)" diff --git a/generate/api/member.api b/generate/api/member.api index 3989e9d..9c49093 100644 --- a/generate/api/member.api +++ b/generate/api/member.api @@ -22,15 +22,15 @@ type ( } UpdateMemberMeReq { - DisplayName string `json:"display_name,optional"` - Avatar string `json:"avatar,optional"` - Language string `json:"language,optional"` - Currency string `json:"currency,optional"` - Phone string `json:"phone,optional"` + DisplayName string `json:"display_name,optional"` // 顯示名稱(可選) + Avatar string `json:"avatar,optional"` // 頭像 URL(可選) + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + Currency string `json:"currency,optional"` // 幣別代碼,如 TWD / USD(可選) + Phone string `json:"phone,optional"` // 聯絡電話 E.164 格式(可選) } VerificationStartReq { - Target string `json:"target"` + Target string `json:"target"` // 驗證目標:email 地址或 E.164 手機號(依端點而定) } VerificationStartData { @@ -39,8 +39,8 @@ type ( } VerificationConfirmReq { - ChallengeID string `json:"challenge_id"` - Code string `json:"code"` + ChallengeID string `json:"challenge_id"` // 驗證流程的 OTP challenge ID(由 /start 端點回傳) + Code string `json:"code"` // 6 位數 OTP 驗證碼 } TOTPStatusData { @@ -61,7 +61,7 @@ type ( } TOTPEnrollConfirmReq { - Code string `json:"code"` + Code string `json:"code"` // Google Authenticator 顯示的 6 位數 TOTP 碼 } TOTPEnrollConfirmData { @@ -69,7 +69,7 @@ type ( } TOTPVerifyReq { - Code string `json:"code"` + Code string `json:"code"` // TOTP 6 位數碼,或 8 位數備援碼 } TOTPBackupCodesData { @@ -115,8 +115,10 @@ type ( ) @server( - group: member - prefix: /api/v1/members + group: member + prefix: /api/v1/members + tags: "Member - 會員" + summary: "Profile / 業務 Email 與 Phone 驗證 / TOTP MFA" ) service gateway { @doc "取得當前會員 profile(Bearer JWT;本機 dev 可 fallback X-Tenant-ID + X-UID)" diff --git a/generate/api/normal.api b/generate/api/normal.api index 1412e87..a812944 100644 --- a/generate/api/normal.api +++ b/generate/api/normal.api @@ -17,6 +17,8 @@ type PingOKStatus { prefix: /api/v1 schemes: https timeout: 3s + tags: "Normal - 公開" + summary: "健康檢查 / Ping" ) service gateway { @doc( diff --git a/generate/api/permission.api b/generate/api/permission.api index 3c07cd6..e867775 100644 --- a/generate/api/permission.api +++ b/generate/api/permission.api @@ -4,9 +4,9 @@ type ( // ===== Permission catalog ===== PermissionCatalogQuery { - Status string `form:"status,optional" validate:"omitempty,oneof=open close"` - Type string `form:"type,optional" validate:"omitempty,oneof=backend_user frontend_user"` - Tree bool `form:"tree,optional"` + Status string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 狀態篩選;可選值: open / close + 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"` // 是否回傳樹狀結構(false 則扁平 list) } PermissionNode { @@ -28,7 +28,7 @@ type ( // ===== Me permissions ===== MePermissionsQuery { - IncludeTree bool `form:"include_tree,optional"` + IncludeTree bool `form:"include_tree,optional"` // 是否同時回傳 permission tree(true 會多包一份樹狀結構) } MePermissionsData { @@ -58,48 +58,48 @@ type ( } CreateRoleReq { - Key string `json:"key" validate:"required,min=2,max=64"` - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + Key string `json:"key" validate:"required,min=2,max=64"` // 角色 key(2-64 字元,不可以 system. / platform_ 開頭) + DisplayName string `json:"display_name,optional"` // 角色顯示名稱(給人看的) + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close(預設 open) } UpdateRoleReq { - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + DisplayName string `json:"display_name,optional"` // 角色顯示名稱 + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close } UpdateRoleByIDReq { - ID string `path:"id"` - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + ID string `path:"id"` // Role ID(path) + DisplayName string `json:"display_name,optional"` // 角色顯示名稱 + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close } DeleteRoleByIDReq { - ID string `path:"id"` + ID string `path:"id"` // Role ID(path) } GetRolePermissionsByIDReq { - ID string `path:"id"` + ID string `path:"id"` // Role ID(path) } ReplaceRolePermissionsByIDReq { - ID string `path:"id"` - PermissionIDs []string `json:"permission_ids"` + ID string `path:"id"` // Role ID(path) + PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限) } ListUserRolesReq { - UID string `path:"uid"` + UID string `path:"uid"` // 使用者 UID(path) } AssignUserRoleByUIDReq { - UID string `path:"uid"` - RoleID string `json:"role_id" validate:"required"` - Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID + Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim(預設 manual) } RevokeUserRoleByIDReq { - UID string `path:"uid"` - RoleID string `path:"role_id"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `path:"role_id"` // Role ID(path) } // ===== Role permissions ===== @@ -109,13 +109,13 @@ type ( } ReplaceRolePermissionsReq { - PermissionIDs []string `json:"permission_ids"` + PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限) } // ===== User roles ===== UIDPath { - UID string `path:"uid"` + UID string `path:"uid"` // 使用者 UID(path) } UserRoleData { @@ -135,13 +135,13 @@ type ( } AssignUserRoleReq { - RoleID string `json:"role_id" validate:"required"` - Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` + RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID + Source string `json:"source,optional,options=manual|zitadel|ldap|scim" validate:"omitempty,oneof=manual zitadel ldap scim"` // 指派來源;可選值: manual / zitadel / ldap / scim(預設 manual) } UserRoleIDPath { - UID string `path:"uid"` - RoleID string `path:"role_id"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `path:"role_id"` // Role ID(path) } // ===== Role mappings ===== @@ -165,26 +165,26 @@ type ( } RoleMappingListQuery { - Source string `form:"source,optional" validate:"omitempty,oneof=zitadel ldap scim"` - Offset int64 `form:"offset,optional"` - Limit int64 `form:"limit,optional"` + Source string `form:"source,optional,options=zitadel|ldap|scim" validate:"omitempty,oneof=zitadel ldap scim"` // 外部來源篩選;可選值: zitadel / ldap / scim + Offset int64 `form:"offset,optional"` // 分頁起點(從 0 起算) + Limit int64 `form:"limit,optional"` // 每頁筆數(最大值由 server 限制) } UpsertRoleMappingReq { - ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` - ExternalKey string `json:"external_key" validate:"required"` - InternalRoleKey string `json:"internal_role_key" validate:"required"` + 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"` // 外部群組 / claim 的 key(例如 zitadel role 名稱) + InternalRoleKey string `json:"internal_role_key" validate:"required"` // 對映的內部角色 key(必須存在於 roles 集合) } DeleteRoleMappingReq { - ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` - ExternalKey string `json:"external_key" validate:"required"` + 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"` // 外部群組 / claim 的 key } // ===== Policy reload ===== PolicyReloadReq { - TenantID string `json:"tenant_id,optional"` + TenantID string `json:"tenant_id,optional"` // 指定要 reload 的租戶 ID;留空則 reload 所有租戶 } PolicyReloadData { @@ -256,8 +256,10 @@ type ( ) @server( - group: permission - prefix: /api/v1/permissions + group: permission + prefix: /api/v1/permissions + tags: "Permission - 權限" + summary: "Catalog / 角色 / 使用者角色 / 外部映射 / Policy" ) service gateway { @doc "取得全局 Permission Catalog(樹狀或扁平;可篩 status/type)" diff --git a/internal/types/types.go b/internal/types/types.go index 905abb4..99a30cf 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -10,14 +10,14 @@ type APIErrorStatus struct { } type AssignUserRoleByUIDReq struct { - UID string `path:"uid"` - RoleID string `json:"role_id" validate:"required"` - Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID + 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 { - RoleID string `json:"role_id" validate:"required"` - Source string `json:"source,optional" validate:"omitempty,oneof=manual zitadel ldap scim"` + RoleID string `json:"role_id" validate:"required"` // 要指派的 Role ID + 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 { @@ -35,18 +35,18 @@ type AuthTokenOKStatus struct { } type CreateRoleReq struct { - Key string `json:"key" validate:"required,min=2,max=64"` - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + Key string `json:"key" validate:"required,min=2,max=64"` // 角色 key(2-64 字元,不可以 system. / platform_ 開頭) + DisplayName string `json:"display_name,optional"` // 角色顯示名稱(給人看的) + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close(預設 open) } type DeleteRoleByIDReq struct { - ID string `path:"id"` + ID string `path:"id"` // Role ID(path) } type DeleteRoleMappingReq struct { - ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` - ExternalKey string `json:"external_key" validate:"required"` + 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"` // 外部群組 / claim 的 key } type EmptyOKStatus struct { @@ -62,22 +62,22 @@ type ErrorDetail struct { } type GetRolePermissionsByIDReq struct { - ID string `path:"id"` + ID string `path:"id"` // Role ID(path) } type ListUserRolesReq struct { - UID string `path:"uid"` + UID string `path:"uid"` // 使用者 UID(path) } type LoginReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=128"` + 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 字元) } type LoginSocialCallbackReq struct { - Code string `form:"code" validate:"required"` - State string `form:"state" validate:"required"` + Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code + State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state(對應 session) } type LoginSocialStartData struct { @@ -93,9 +93,9 @@ type LoginSocialStartOKStatus struct { } type LoginSocialStartReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - Provider string `json:"provider" validate:"required,oneof=google"` - RedirectURI string `json:"redirect_uri" validate:"required,url"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google + RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI } type LogoutData struct { @@ -123,7 +123,7 @@ type MePermissionsOKStatus struct { } type MePermissionsQuery struct { - IncludeTree bool `form:"include_tree,optional"` + IncludeTree bool `form:"include_tree,optional"` // 是否同時回傳 permission tree(true 會多包一份樹狀結構) } type MemberMeData struct { @@ -164,9 +164,9 @@ type PermissionCatalogOKStatus struct { } type PermissionCatalogQuery struct { - Status string `form:"status,optional" validate:"omitempty,oneof=open close"` - Type string `form:"type,optional" validate:"omitempty,oneof=backend_user frontend_user"` - Tree bool `form:"tree,optional"` + Status string `form:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 狀態篩選;可選值: open / close + 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"` // 是否回傳樹狀結構(false 則扁平 list) } type PermissionNode struct { @@ -202,13 +202,13 @@ type PolicyReloadOKStatus struct { } type PolicyReloadReq struct { - TenantID string `json:"tenant_id,optional"` + TenantID string `json:"tenant_id,optional"` // 指定要 reload 的租戶 ID;留空則 reload 所有租戶 } type RegisterConfirmReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - ChallengeID string `json:"challenge_id" validate:"required"` - Code string `json:"code" validate:"required,len=6"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID(由 /register 回傳) + Code string `json:"code" validate:"required,len=6"` // 6 位數 OTP 驗證碼 } type RegisterData struct { @@ -224,24 +224,24 @@ type RegisterOKStatus struct { } type RegisterReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - InviteCode string `json:"invite_code" validate:"required"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=8,max=128"` - DisplayName string `json:"display_name,optional"` - Language string `json:"language,optional"` - AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` - MarketingOptIn bool `json:"marketing_opt_in,optional"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug(小寫英數) + InviteCode string `json:"invite_code" validate:"required"` // 邀請碼 + Email string `json:"email" validate:"required,email"` // 電子郵件 + Password string `json:"password" validate:"required,min=8,max=128"` // 密碼(8-128 字元) + DisplayName string `json:"display_name,optional"` // 顯示名稱(可選) + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本 + MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息 } type RegisterResendReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - ChallengeID string `json:"challenge_id" validate:"required"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + ChallengeID string `json:"challenge_id" validate:"required"` // 註冊流程的 OTP challenge ID } type RegisterSocialCallbackReq struct { - Code string `form:"code" validate:"required"` - State string `form:"state" validate:"required"` + Code string `form:"code" validate:"required"` // IdP 回傳的 OAuth authorization code + State string `form:"state" validate:"required"` // IdP 回傳的 OAuth state(對應 session) } type RegisterSocialStartData struct { @@ -257,27 +257,27 @@ type RegisterSocialStartOKStatus struct { } type RegisterSocialStartReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - InviteCode string `json:"invite_code" validate:"required"` - Provider string `json:"provider" validate:"required,oneof=google"` - AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` - Language string `json:"language,optional"` - RedirectURI string `json:"redirect_uri" validate:"required,url"` - MarketingOptIn bool `json:"marketing_opt_in,optional"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + InviteCode string `json:"invite_code" validate:"required"` // 邀請碼 + Provider string `json:"provider,options=google" validate:"required,oneof=google"` // 第三方登入提供者;可選值: google + AcceptTermsVersion string `json:"accept_terms_version" validate:"required"` // 使用者接受的服務條款版本 + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + RedirectURI string `json:"redirect_uri" validate:"required,url"` // OAuth 完成後要 redirect 回的 URI + MarketingOptIn bool `json:"marketing_opt_in,optional"` // 是否同意接收行銷訊息 } type ReplaceRolePermissionsByIDReq struct { - ID string `path:"id"` - PermissionIDs []string `json:"permission_ids"` + ID string `path:"id"` // Role ID(path) + PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限) } type ReplaceRolePermissionsReq struct { - PermissionIDs []string `json:"permission_ids"` + PermissionIDs []string `json:"permission_ids"` // 要勾選的 Permission ID 陣列(全量取代,會自動補齊父權限) } type RevokeUserRoleByIDReq struct { - UID string `path:"uid"` - RoleID string `path:"role_id"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `path:"role_id"` // Role ID(path) } type RoleData struct { @@ -327,9 +327,9 @@ type RoleMappingListOKStatus struct { } type RoleMappingListQuery struct { - Source string `form:"source,optional" validate:"omitempty,oneof=zitadel ldap scim"` - Offset int64 `form:"offset,optional"` - Limit int64 `form:"limit,optional"` + Source string `form:"source,optional,options=zitadel|ldap|scim" validate:"omitempty,oneof=zitadel ldap scim"` // 外部來源篩選;可選值: zitadel / ldap / scim + Offset int64 `form:"offset,optional"` // 分頁起點(從 0 起算) + Limit int64 `form:"limit,optional"` // 每頁筆數(最大值由 server 限制) } type RoleMappingOKStatus struct { @@ -375,7 +375,7 @@ type TOTPEnrollConfirmOKStatus struct { } type TOTPEnrollConfirmReq struct { - Code string `json:"code"` + Code string `json:"code"` // Google Authenticator 顯示的 6 位數 TOTP 碼 } type TOTPEnrollStartData struct { @@ -408,45 +408,45 @@ type TOTPStatusOKStatus struct { } type TOTPVerifyReq struct { - Code string `json:"code"` + Code string `json:"code"` // TOTP 6 位數碼,或 8 位數備援碼 } type TokenExchangeReq struct { - TenantSlug string `json:"tenant_slug" validate:"required"` - IDToken string `json:"id_token" validate:"required"` + TenantSlug string `json:"tenant_slug" validate:"required"` // 租戶 slug + IDToken string `json:"id_token" validate:"required"` // ZITADEL 發行的 id_token } type TokenRefreshReq struct { - RefreshToken string `json:"refresh_token" validate:"required"` + RefreshToken string `json:"refresh_token" validate:"required"` // 先前核發的 refresh token } type UIDPath struct { - UID string `path:"uid"` + UID string `path:"uid"` // 使用者 UID(path) } type UpdateMemberMeReq struct { - DisplayName string `json:"display_name,optional"` - Avatar string `json:"avatar,optional"` - Language string `json:"language,optional"` - Currency string `json:"currency,optional"` - Phone string `json:"phone,optional"` + DisplayName string `json:"display_name,optional"` // 顯示名稱(可選) + Avatar string `json:"avatar,optional"` // 頭像 URL(可選) + Language string `json:"language,optional"` // 語系代碼,如 zh-TW / en(可選) + Currency string `json:"currency,optional"` // 幣別代碼,如 TWD / USD(可選) + Phone string `json:"phone,optional"` // 聯絡電話 E.164 格式(可選) } type UpdateRoleByIDReq struct { - ID string `path:"id"` - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + ID string `path:"id"` // Role ID(path) + DisplayName string `json:"display_name,optional"` // 角色顯示名稱 + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close } type UpdateRoleReq struct { - DisplayName string `json:"display_name,optional"` - Status string `json:"status,optional" validate:"omitempty,oneof=open close"` + DisplayName string `json:"display_name,optional"` // 角色顯示名稱 + Status string `json:"status,optional,options=open|close" validate:"omitempty,oneof=open close"` // 角色狀態;可選值: open / close } type UpsertRoleMappingReq struct { - ExternalSource string `json:"external_source" validate:"required,oneof=zitadel ldap scim"` - ExternalKey string `json:"external_key" validate:"required"` - InternalRoleKey string `json:"internal_role_key" validate:"required"` + 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"` // 外部群組 / claim 的 key(例如 zitadel role 名稱) + InternalRoleKey string `json:"internal_role_key" validate:"required"` // 對映的內部角色 key(必須存在於 roles 集合) } type UserRoleData struct { @@ -462,8 +462,8 @@ type UserRoleData struct { } type UserRoleIDPath struct { - UID string `path:"uid"` - RoleID string `path:"role_id"` + UID string `path:"uid"` // 使用者 UID(path) + RoleID string `path:"role_id"` // Role ID(path) } type UserRoleListData struct { @@ -483,8 +483,8 @@ type UserRoleOKStatus struct { } type VerificationConfirmReq struct { - ChallengeID string `json:"challenge_id"` - Code string `json:"code"` + ChallengeID string `json:"challenge_id"` // 驗證流程的 OTP challenge ID(由 /start 端點回傳) + Code string `json:"code"` // 6 位數 OTP 驗證碼 } type VerificationStartData struct { @@ -499,5 +499,5 @@ type VerificationStartOKStatus struct { } type VerificationStartReq struct { - Target string `json:"target"` + Target string `json:"target"` // 驗證目標:email 地址或 E.164 手機號(依端點而定) }