Merge pull request '补全 API OpenAPI 文档' (#2) from feature/openapi-docs into main

Reviewed-on: #2
This commit is contained in:
chensipeng 2026-05-15 09:46:18 +08:00
commit 7abb6a1baf
18 changed files with 16240 additions and 7 deletions

View File

@ -43,6 +43,15 @@ pnpm dev
后端热更新可通过 `GO_WATCH_SHUTDOWN_GRACE_MS``GO_WATCH_RESTART_DELAY_MS` 调整旧进程退出等待时间与重启间隔。
## OpenAPI 文档
修改 `apps/api/internal/httpapi` 下的接口、请求或响应类型后,请重新执行:
```bash
pnpm openapi
```
默认 EasyAI 部署里,`easyai-pgvector` 在容器网络内的连接串是:
```dotenv

View File

@ -15,6 +15,16 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// @title EasyAI AI Gateway API
// @version 0.1.0
// @description EasyAI AI Gateway 的本地鉴权、平台模型管理、定价、运行策略、钱包和 AI 任务接口。
// @description 受保护接口使用 Authorization: Bearer <JWT 或 API Key>,管理接口只接受 JWT 用户凭证。
// @BasePath /
// @schemes http https
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Bearer JWT 或 API Key。
func main() {
cfg := config.Load()
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{

9027
apps/api/docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

5913
apps/api/docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,17 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// listAccessRules godoc
// @Summary 列出访问规则
// @Description 管理端返回用户组、租户、用户或 API Key 到平台、平台模型、基础模型的访问规则。
// @Tags access-rules
// @Produce json
// @Security BearerAuth
// @Success 200 {object} AccessRuleListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules [get]
func (s *Server) listAccessRules(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListAccessRules(r.Context())
if err != nil {
@ -20,6 +31,17 @@ func (s *Server) listAccessRules(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listAPIKeyAccessRules godoc
// @Summary 列出 API Key 访问规则
// @Description 返回当前本地用户可管理的 API Key 访问规则。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Success 200 {object} AccessRuleListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/access-rules [get]
func (s *Server) listAPIKeyAccessRules(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
items, err := s.store.ListAPIKeyAccessRules(r.Context(), user)
@ -35,6 +57,21 @@ func (s *Server) listAPIKeyAccessRules(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createAccessRule godoc
// @Summary 创建访问规则
// @Description 管理端创建一条访问控制规则。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleInput true "访问规则请求"
// @Success 201 {object} store.AccessRule
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules [post]
func (s *Server) createAccessRule(w http.ResponseWriter, r *http.Request) {
var input store.AccessRuleInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -58,6 +95,20 @@ func (s *Server) createAccessRule(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// batchAccessRules godoc
// @Summary 批量写入访问规则
// @Description 管理端为同一主体批量新增、更新或删除资源访问规则。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleBatchInput true "访问规则批量请求"
// @Success 200 {object} AccessRuleListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/batch [post]
func (s *Server) batchAccessRules(w http.ResponseWriter, r *http.Request) {
var input store.AccessRuleBatchInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -77,6 +128,21 @@ func (s *Server) batchAccessRules(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// batchAPIKeyAccessRules godoc
// @Summary 批量写入 API Key 访问规则
// @Description 当前本地用户为自己的 API Key 批量新增、更新或删除可访问资源。
// @Tags api-keys
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.AccessRuleBatchInput true "API Key 访问规则批量请求subjectType 必须为 api_key"
// @Success 200 {object} AccessRuleListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/access-rules/batch [post]
func (s *Server) batchAPIKeyAccessRules(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
var input store.AccessRuleBatchInput
@ -109,6 +175,23 @@ func (s *Server) batchAPIKeyAccessRules(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// updateAccessRule godoc
// @Summary 更新访问规则
// @Description 管理端更新一条访问控制规则。
// @Tags access-rules
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param ruleID path string true "访问规则 ID"
// @Param input body store.AccessRuleInput true "访问规则请求"
// @Success 200 {object} store.AccessRule
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/{ruleID} [patch]
func (s *Server) updateAccessRule(w http.ResponseWriter, r *http.Request) {
var input store.AccessRuleInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -136,6 +219,19 @@ func (s *Server) updateAccessRule(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deleteAccessRule godoc
// @Summary 删除访问规则
// @Description 管理端删除一条访问控制规则。
// @Tags access-rules
// @Produce json
// @Security BearerAuth
// @Param ruleID path string true "访问规则 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/access-rules/{ruleID} [delete]
func (s *Server) deleteAccessRule(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteAccessRule(r.Context(), r.PathValue("ruleID")); err != nil {
if store.IsNotFound(err) {

View File

@ -12,13 +12,29 @@ import (
)
type walletBalanceRequest struct {
Currency string `json:"currency"`
Balance float64 `json:"balance"`
Reason string `json:"reason"`
IdempotencyKey string `json:"idempotencyKey"`
Currency string `json:"currency" example:"USD"`
Balance float64 `json:"balance" example:"100"`
Reason string `json:"reason" example:"manual recharge"`
IdempotencyKey string `json:"idempotencyKey" example:"wallet-set-20260514-001"`
Metadata map[string]any `json:"metadata"`
}
// setUserWalletBalance godoc
// @Summary 设置用户钱包余额
// @Description 管理端把指定用户钱包余额调整到目标值并记录审计日志balance 不允许为负数。
// @Tags billing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userID path string true "用户 ID"
// @Param input body walletBalanceRequest true "钱包余额设置请求"
// @Success 200 {object} WalletAdjustmentResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/{userID}/wallet [patch]
func (s *Server) setUserWalletBalance(w http.ResponseWriter, r *http.Request) {
actor, _ := auth.UserFromContext(r.Context())
var input walletBalanceRequest
@ -79,6 +95,23 @@ func (s *Server) setUserWalletBalance(w http.ResponseWriter, r *http.Request) {
})
}
// listAuditLogs godoc
// @Summary 列出审计日志
// @Description 管理端按分类、动作、目标类型和目标 ID 查询审计日志。
// @Tags billing
// @Produce json
// @Security BearerAuth
// @Param category query string false "审计分类"
// @Param action query string false "审计动作"
// @Param targetType query string false "目标类型"
// @Param targetId query string false "目标 ID"
// @Param limit query int false "返回数量" default(100)
// @Success 200 {object} AuditLogListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/audit-logs [get]
func (s *Server) listAuditLogs(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
limit, err := positiveQueryInt(query.Get("limit"), 100)

View File

@ -9,6 +9,15 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// listCatalogProviders godoc
// @Summary 列出目录供应商
// @Description 返回模型目录使用的供应商元数据;公共路径和管理路径返回同一结构。
// @Tags catalog
// @Produce json
// @Success 200 {object} CatalogProviderListResponse
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/public/catalog/providers [get]
// @Router /api/admin/catalog/providers [get]
func (s *Server) listCatalogProviders(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListCatalogProviders(r.Context())
if err != nil {
@ -19,6 +28,21 @@ func (s *Server) listCatalogProviders(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createCatalogProvider godoc
// @Summary 创建目录供应商
// @Description 管理端新增模型目录供应商providerKey 和 displayName 必填。
// @Tags catalog
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.CatalogProviderInput true "目录供应商请求"
// @Success 201 {object} store.CatalogProvider
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/providers [post]
func (s *Server) createCatalogProvider(w http.ResponseWriter, r *http.Request) {
var input store.CatalogProviderInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -42,6 +66,23 @@ func (s *Server) createCatalogProvider(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updateCatalogProvider godoc
// @Summary 更新目录供应商
// @Description 管理端更新目录供应商展示信息、图标和元数据。
// @Tags catalog
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param providerID path string true "目录供应商 ID"
// @Param input body store.CatalogProviderInput true "目录供应商请求"
// @Success 200 {object} store.CatalogProvider
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/providers/{providerID} [patch]
func (s *Server) updateCatalogProvider(w http.ResponseWriter, r *http.Request) {
var input store.CatalogProviderInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -69,6 +110,19 @@ func (s *Server) updateCatalogProvider(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deleteCatalogProvider godoc
// @Summary 删除目录供应商
// @Description 管理端删除目录供应商。
// @Tags catalog
// @Produce json
// @Security BearerAuth
// @Param providerID path string true "目录供应商 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/providers/{providerID} [delete]
func (s *Server) deleteCatalogProvider(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteCatalogProvider(r.Context(), r.PathValue("providerID")); err != nil {
if store.IsNotFound(err) {
@ -82,6 +136,15 @@ func (s *Server) deleteCatalogProvider(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// listBaseModels godoc
// @Summary 列出基础模型
// @Description 返回基础模型目录;公共路径和管理路径返回同一结构。
// @Tags catalog
// @Produce json
// @Success 200 {object} BaseModelListResponse
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/public/catalog/base-models [get]
// @Router /api/admin/catalog/base-models [get]
func (s *Server) listBaseModels(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListBaseModels(r.Context())
if err != nil {
@ -92,6 +155,21 @@ func (s *Server) listBaseModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createBaseModel godoc
// @Summary 创建基础模型
// @Description 管理端新增基础模型目录项providerKey、providerModelName 和 modelType 必填。
// @Tags catalog
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.BaseModelInput true "基础模型请求"
// @Success 201 {object} store.BaseModel
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/base-models [post]
func (s *Server) createBaseModel(w http.ResponseWriter, r *http.Request) {
var input store.BaseModelInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -115,6 +193,23 @@ func (s *Server) createBaseModel(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updateBaseModel godoc
// @Summary 更新基础模型
// @Description 管理端更新基础模型目录项及能力、图标、默认快照等元数据。
// @Tags catalog
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param baseModelID path string true "基础模型 ID"
// @Param input body store.BaseModelInput true "基础模型请求"
// @Success 200 {object} store.BaseModel
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/base-models/{baseModelID} [patch]
func (s *Server) updateBaseModel(w http.ResponseWriter, r *http.Request) {
var input store.BaseModelInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -142,6 +237,20 @@ func (s *Server) updateBaseModel(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// resetBaseModel godoc
// @Summary 重置基础模型
// @Description 将指定基础模型恢复为系统默认快照;无默认快照时返回 409。
// @Tags catalog
// @Produce json
// @Security BearerAuth
// @Param baseModelID path string true "基础模型 ID"
// @Success 200 {object} store.BaseModel
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/base-models/{baseModelID}/reset [post]
func (s *Server) resetBaseModel(w http.ResponseWriter, r *http.Request) {
item, err := s.store.ResetBaseModelToDefault(r.Context(), r.PathValue("baseModelID"))
if err != nil {
@ -160,6 +269,17 @@ func (s *Server) resetBaseModel(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// resetAllBaseModels godoc
// @Summary 重置全部基础模型
// @Description 将所有具备系统默认快照的基础模型恢复为默认配置。
// @Tags catalog
// @Produce json
// @Security BearerAuth
// @Success 200 {object} BaseModelListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/base-models/reset-all [post]
func (s *Server) resetAllBaseModels(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ResetAllBaseModelsToDefault(r.Context())
if err != nil {
@ -170,6 +290,19 @@ func (s *Server) resetAllBaseModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// deleteBaseModel godoc
// @Summary 删除基础模型
// @Description 管理端删除基础模型目录项。
// @Tags catalog
// @Produce json
// @Security BearerAuth
// @Param baseModelID path string true "基础模型 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/catalog/base-models/{baseModelID} [delete]
func (s *Server) deleteBaseModel(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteBaseModel(r.Context(), r.PathValue("baseModelID")); err != nil {
if store.IsNotFound(err) {

View File

@ -5,6 +5,16 @@ import (
"strings"
)
// getNetworkProxyConfig godoc
// @Summary 获取网络代理配置
// @Description 管理端查看服务当前使用的全局 HTTP 代理配置及来源。
// @Tags config
// @Produce json
// @Security BearerAuth
// @Success 200 {object} NetworkProxyConfigResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Router /api/admin/config/network-proxy [get]
func (s *Server) getNetworkProxyConfig(w http.ResponseWriter, r *http.Request) {
globalHTTPProxy := strings.TrimSpace(s.cfg.GlobalHTTPProxy)
writeJSON(w, http.StatusOK, map[string]any{

View File

@ -16,6 +16,13 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// health godoc
// @Summary 健康检查
// @Description 返回服务进程、运行环境和身份模式,供负载均衡或人工排障使用。
// @Tags system
// @Produce json
// @Success 200 {object} HealthResponse
// @Router /healthz [get]
func (s *Server) health(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{
"ok": true,
@ -25,6 +32,14 @@ func (s *Server) health(w http.ResponseWriter, r *http.Request) {
})
}
// ready godoc
// @Summary 就绪检查
// @Description 检查 Postgres 是否可用;数据库不可用时返回 503。
// @Tags system
// @Produce json
// @Success 200 {object} ReadyResponse
// @Failure 503 {object} ErrorEnvelope
// @Router /readyz [get]
func (s *Server) ready(w http.ResponseWriter, r *http.Request) {
if err := s.store.Ping(r.Context()); err != nil {
writeError(w, http.StatusServiceUnavailable, "postgres unavailable")
@ -33,11 +48,33 @@ func (s *Server) ready(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
// me godoc
// @Summary 获取当前用户
// @Description 返回鉴权中解析出的用户、租户、用户组和 API Key 上下文。
// @Tags auth
// @Produce json
// @Security BearerAuth
// @Success 200 {object} auth.User
// @Failure 401 {object} ErrorEnvelope
// @Router /api/v1/me [get]
func (s *Server) me(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
writeJSON(w, http.StatusOK, user)
}
// register godoc
// @Summary 本地注册
// @Description 在 standalone 或 hybrid 身份模式下创建本地用户,并返回 24 小时 JWT。
// @Tags auth
// @Accept json
// @Produce json
// @Param input body store.LocalRegisterInput true "注册请求password 至少 8 位invitationCode 取决于部署策略"
// @Success 201 {object} AuthResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/auth/register [post]
func (s *Server) register(w http.ResponseWriter, r *http.Request) {
if !s.localIdentityEnabled() {
writeError(w, http.StatusForbidden, "local registration is disabled")
@ -69,6 +106,19 @@ func (s *Server) register(w http.ResponseWriter, r *http.Request) {
s.writeAuthResponse(w, http.StatusCreated, user)
}
// login godoc
// @Summary 本地登录
// @Description 使用用户名或邮箱登录本地账号,并返回 24 小时 JWT。
// @Tags auth
// @Accept json
// @Produce json
// @Param input body store.LocalLoginInput true "登录请求account 可为用户名或邮箱"
// @Success 200 {object} AuthResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/auth/login [post]
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
if !s.localIdentityEnabled() {
writeError(w, http.StatusForbidden, "local login is disabled")
@ -136,6 +186,17 @@ func authUserFromGatewayUser(user store.GatewayUser) *auth.User {
}
}
// listPlatforms godoc
// @Summary 列出平台
// @Description 管理端返回所有接入平台及其优先级、定价和运行策略摘要。
// @Tags platforms
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PlatformListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms [get]
func (s *Server) listPlatforms(w http.ResponseWriter, r *http.Request) {
platforms, err := s.store.ListPlatforms(r.Context())
if err != nil {
@ -146,6 +207,16 @@ func (s *Server) listPlatforms(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": platforms})
}
// listPlayablePlatforms godoc
// @Summary 列出可用平台
// @Description 按当前用户可访问模型过滤平台,仅返回启用且存在可访问模型的平台。
// @Tags playground
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PlatformListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/platforms [get]
func (s *Server) listPlayablePlatforms(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
models, err := s.store.ListAccessiblePlatformModels(r.Context(), user)
@ -173,6 +244,20 @@ func (s *Server) listPlayablePlatforms(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": filtered})
}
// createPlatform godoc
// @Summary 创建平台
// @Description 新增模型供应商平台配置credentials 会被服务端保存并在返回值中脱敏。
// @Tags platforms
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.CreatePlatformInput true "平台配置请求"
// @Success 201 {object} store.Platform
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms [post]
func (s *Server) createPlatform(w http.ResponseWriter, r *http.Request) {
var input store.CreatePlatformInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -209,6 +294,23 @@ func (s *Server) createPlatform(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, platform)
}
// updatePlatform godoc
// @Summary 更新平台
// @Description 覆盖指定平台的基础配置、凭证、优先级、定价和运行策略。
// @Tags platforms
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param platformID path string true "平台 ID"
// @Param input body store.CreatePlatformInput true "平台配置请求"
// @Success 200 {object} store.Platform
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms/{platformID} [patch]
func (s *Server) updatePlatform(w http.ResponseWriter, r *http.Request) {
var input store.CreatePlatformInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -253,6 +355,19 @@ func (s *Server) updatePlatform(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, platform)
}
// deletePlatform godoc
// @Summary 删除平台
// @Description 删除指定平台及关联配置;不存在时返回 404。
// @Tags platforms
// @Produce json
// @Security BearerAuth
// @Param platformID path string true "平台 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms/{platformID} [delete]
func (s *Server) deletePlatform(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeletePlatform(r.Context(), r.PathValue("platformID")); err != nil {
if store.IsNotFound(err) {
@ -266,6 +381,23 @@ func (s *Server) deletePlatform(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// createPlatformModel godoc
// @Summary 创建平台模型
// @Description 为平台新增一个可路由模型;路径中的 platformID 会覆盖请求体 platformId。
// @Tags platform-models
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param platformID path string true "平台 ID使用 /api/admin/platforms/{platformID}/models 时由路径提供"
// @Param input body store.CreatePlatformModelInput true "平台模型配置请求"
// @Success 201 {object} store.PlatformModel
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms/{platformID}/models [post]
// @Router /api/admin/platform-models [post]
func (s *Server) createPlatformModel(w http.ResponseWriter, r *http.Request) {
var input store.CreatePlatformModelInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -292,6 +424,22 @@ func (s *Server) createPlatformModel(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, s.platformModelResponse(r.Context(), model))
}
// replacePlatformModels godoc
// @Summary 替换平台模型
// @Description 用请求体中的 models 列表整体替换指定平台下的模型配置。
// @Tags platform-models
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param platformID path string true "平台 ID"
// @Param input body ReplacePlatformModelsRequest true "模型列表请求"
// @Success 200 {object} PlatformModelListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms/{platformID}/models [put]
func (s *Server) replacePlatformModels(w http.ResponseWriter, r *http.Request) {
platformID := r.PathValue("platformID")
if platformID == "" {
@ -320,6 +468,19 @@ func (s *Server) replacePlatformModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": s.platformModelResponses(r.Context(), models)})
}
// deletePlatformModel godoc
// @Summary 删除平台模型
// @Description 删除指定平台模型路由配置。
// @Tags platform-models
// @Produce json
// @Security BearerAuth
// @Param modelID path string true "平台模型 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platform-models/{modelID} [delete]
func (s *Server) deletePlatformModel(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeletePlatformModel(r.Context(), r.PathValue("modelID")); err != nil {
if store.IsNotFound(err) {
@ -333,6 +494,17 @@ func (s *Server) deletePlatformModel(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// listModels godoc
// @Summary 列出平台模型
// @Description 管理端返回所有平台模型,并补齐有效计费配置。
// @Tags platform-models
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PlatformModelListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/models [get]
func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
models, err := s.store.ListModels(r.Context())
if err != nil {
@ -343,6 +515,17 @@ func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": s.platformModelResponses(r.Context(), models)})
}
// listPlayableModels godoc
// @Summary 列出可调用模型
// @Description 按当前用户权限返回可用于 Playground 或 API 调用的模型列表。
// @Tags playground
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PlatformModelListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/models [get]
// @Router /api/v1/playground/models [get]
func (s *Server) listPlayableModels(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
models, err := s.store.ListAccessiblePlatformModels(r.Context(), user)
@ -354,6 +537,17 @@ func (s *Server) listPlayableModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": s.platformModelResponses(r.Context(), models)})
}
// listPricingRules godoc
// @Summary 列出定价规则
// @Description 返回所有定价规则明细,便于管理端排查有效价格。
// @Tags pricing
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PricingRuleListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/pricing/rules [get]
func (s *Server) listPricingRules(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListPricingRules(r.Context())
if err != nil {
@ -364,6 +558,17 @@ func (s *Server) listPricingRules(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listTenants godoc
// @Summary 列出租户
// @Description 管理端返回网关租户列表。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Success 200 {object} TenantListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/tenants [get]
func (s *Server) listTenants(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListTenants(r.Context())
if err != nil {
@ -374,6 +579,17 @@ func (s *Server) listTenants(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listUsers godoc
// @Summary 列出用户
// @Description 管理端返回网关用户列表及钱包摘要。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Success 200 {object} UserListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users [get]
func (s *Server) listUsers(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListUsers(r.Context())
if err != nil {
@ -384,6 +600,17 @@ func (s *Server) listUsers(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listUserGroups godoc
// @Summary 列出用户组
// @Description 管理端返回用户组及其计费、限流和配额策略。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Success 200 {object} UserGroupListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/user-groups [get]
func (s *Server) listUserGroups(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListUserGroups(r.Context())
if err != nil {
@ -394,6 +621,16 @@ func (s *Server) listUserGroups(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listAPIKeys godoc
// @Summary 列出 API Key
// @Description 返回当前用户创建的 API Key 元数据secret 只在创建时返回。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Success 200 {object} APIKeyListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys [get]
func (s *Server) listAPIKeys(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
items, err := s.store.ListAPIKeys(r.Context(), user)
@ -405,6 +642,17 @@ func (s *Server) listAPIKeys(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listPlayableAPIKeys godoc
// @Summary 列出 Playground API Key
// @Description 返回当前本地用户可在 Playground 中直接使用的 API Key 和 secret。
// @Tags playground
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PlayableAPIKeyListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/playground/api-keys [get]
func (s *Server) listPlayableAPIKeys(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
items, err := s.store.ListPlayableAPIKeys(r.Context(), user)
@ -420,6 +668,19 @@ func (s *Server) listPlayableAPIKeys(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createAPIKey godoc
// @Summary 创建 API Key
// @Description 为当前本地用户创建 API Keysecret 仅在本次响应中返回。
// @Tags api-keys
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.CreateAPIKeyInput true "API Key 创建请求"
// @Success 201 {object} store.CreatedAPIKey
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys [post]
func (s *Server) createAPIKey(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
var input store.CreateAPIKeyInput
@ -440,6 +701,19 @@ func (s *Server) createAPIKey(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, created)
}
// disableAPIKey godoc
// @Summary 禁用 API Key
// @Description 禁用当前用户拥有的 API Key保留记录但不再允许调用。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Param apiKeyID path string true "API Key ID"
// @Success 200 {object} store.APIKey
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/{apiKeyID}/disable [patch]
func (s *Server) disableAPIKey(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
item, err := s.store.DisableAPIKey(r.Context(), r.PathValue("apiKeyID"), user)
@ -459,6 +733,19 @@ func (s *Server) disableAPIKey(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusInternalServerError, "disable api key failed")
}
// deleteAPIKey godoc
// @Summary 删除 API Key
// @Description 删除当前用户拥有的 API Key。
// @Tags api-keys
// @Produce json
// @Security BearerAuth
// @Param apiKeyID path string true "API Key ID"
// @Success 204 "No Content"
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/api-keys/{apiKeyID} [delete]
func (s *Server) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
err := s.store.DeleteAPIKey(r.Context(), r.PathValue("apiKeyID"), user)
@ -478,6 +765,22 @@ func (s *Server) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusInternalServerError, "delete api key failed")
}
// estimatePricing godoc
// @Summary 估算请求价格
// @Description 按当前用户、模型候选、任务类型和请求参数估算计费条目。
// @Tags pricing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body PricingEstimateRequest true "计费估算请求kind 默认为 chat.completions"
// @Success 200 {object} PricingEstimateResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 429 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/pricing/estimate [post]
func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
var body map[string]any
@ -511,6 +814,17 @@ func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, estimate)
}
// listRateLimitWindows godoc
// @Summary 列出限流窗口
// @Description 管理端查看当前运行时限流窗口状态。
// @Tags runtime
// @Produce json
// @Security BearerAuth
// @Success 200 {object} RateLimitWindowListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/rate-limit-windows [get]
func (s *Server) listRateLimitWindows(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListRateLimitWindows(r.Context())
if err != nil {
@ -521,6 +835,17 @@ func (s *Server) listRateLimitWindows(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// listModelRateLimitStatuses godoc
// @Summary 列出模型限流状态
// @Description 管理端查看平台模型维度的限流和冷却状态。
// @Tags runtime
// @Produce json
// @Security BearerAuth
// @Success 200 {object} ModelRateLimitStatusListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/model-rate-limits [get]
func (s *Server) listModelRateLimitStatuses(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListModelRateLimitStatuses(r.Context())
if err != nil {
@ -531,6 +856,37 @@ func (s *Server) listModelRateLimitStatuses(w http.ResponseWriter, r *http.Reque
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createTask godoc
// @Summary 创建或执行 AI 任务
// @Description 网关任务接口按 model 选择平台模型;/api/v1 路径返回任务受理结果OpenAI-compatible 路径同步返回兼容响应或 SSE 流。
// @Tags tasks
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param X-Async header bool false "true 时异步创建任务并返回 202"
// @Param input body TaskRequest true "AI 任务请求,字段随任务类型变化"
// @Success 200 {object} CompatibleResponse
// @Success 202 {object} TaskAcceptedResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 402 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 429 {object} ErrorEnvelope
// @Failure 502 {object} ErrorEnvelope
// @Router /api/v1/chat/completions [post]
// @Router /api/v1/responses [post]
// @Router /api/v1/images/generations [post]
// @Router /api/v1/images/edits [post]
// @Router /api/v1/videos/generations [post]
// @Router /chat/completions [post]
// @Router /v1/chat/completions [post]
// @Router /responses [post]
// @Router /v1/responses [post]
// @Router /images/generations [post]
// @Router /v1/images/generations [post]
// @Router /images/edits [post]
// @Router /v1/images/edits [post]
func (s *Server) createTask(kind string, compatible bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
@ -877,6 +1233,24 @@ func matchedRateLimitRule(policy map[string]any, metric string) map[string]any {
return nil
}
// listTasks godoc
// @Summary 列出任务
// @Description 按当前用户列出任务,支持关键字、模型类型、时间范围和分页过滤。
// @Tags tasks
// @Produce json
// @Security BearerAuth
// @Param q query string false "搜索关键字,别名 query"
// @Param modelType query string false "模型类型,别名 type"
// @Param createdFrom query string false "创建时间起点,支持 RFC3339 或日期格式,别名 from"
// @Param createdTo query string false "创建时间终点,支持 RFC3339 或日期格式,别名 to"
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页数量,别名 limit" default(50)
// @Success 200 {object} TaskListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/tasks [get]
// @Router /api/v1/tasks [get]
func (s *Server) listTasks(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
@ -972,6 +1346,19 @@ func boolValue(body map[string]any, key string) bool {
return value
}
// getTask godoc
// @Summary 获取任务详情
// @Description 返回指定任务的请求、状态、输出和执行摘要。
// @Tags tasks
// @Produce json
// @Security BearerAuth
// @Param taskID path string true "任务 ID"
// @Success 200 {object} store.GatewayTask
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/tasks/{taskID} [get]
// @Router /api/v1/tasks/{taskID} [get]
func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err == nil {
@ -986,6 +1373,19 @@ func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusInternalServerError, "get task failed")
}
// taskParamPreprocessing godoc
// @Summary 获取任务参数预处理日志
// @Description 返回指定任务在执行前的参数改写、校验或模板处理日志。
// @Tags tasks
// @Produce json
// @Security BearerAuth
// @Param taskID path string true "任务 ID"
// @Success 200 {object} TaskParamPreprocessingLogListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/tasks/{taskID}/param-preprocessing [get]
// @Router /api/v1/tasks/{taskID}/param-preprocessing [get]
func (s *Server) taskParamPreprocessing(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err != nil {
@ -1006,6 +1406,19 @@ func (s *Server) taskParamPreprocessing(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, map[string]any{"items": logs})
}
// taskEvents godoc
// @Summary 订阅任务事件
// @Description 以 text/event-stream 返回指定任务的历史事件;无事件时返回 task.accepted 占位事件。
// @Tags tasks
// @Produce text/event-stream
// @Security BearerAuth
// @Param taskID path string true "任务 ID"
// @Success 200 {string} string "Server-Sent Eventsdata 为 store.TaskEvent 或 TaskAcceptedEvent"
// @Failure 401 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/tasks/{taskID}/events [get]
// @Router /api/v1/tasks/{taskID}/events [get]
func (s *Server) taskEvents(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err != nil {

View File

@ -8,6 +8,21 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// createTenant godoc
// @Summary 创建租户
// @Description 管理端创建网关租户tenantKey 和 name 必填。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.GatewayTenantInput true "租户请求"
// @Success 201 {object} store.GatewayTenant
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/tenants [post]
func (s *Server) createTenant(w http.ResponseWriter, r *http.Request) {
var input store.GatewayTenantInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -31,6 +46,23 @@ func (s *Server) createTenant(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updateTenant godoc
// @Summary 更新租户
// @Description 管理端更新网关租户信息。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param tenantID path string true "租户 ID"
// @Param input body store.GatewayTenantInput true "租户请求"
// @Success 200 {object} store.GatewayTenant
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/tenants/{tenantID} [patch]
func (s *Server) updateTenant(w http.ResponseWriter, r *http.Request) {
var input store.GatewayTenantInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -58,6 +90,19 @@ func (s *Server) updateTenant(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deleteTenant godoc
// @Summary 删除租户
// @Description 管理端删除网关租户。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param tenantID path string true "租户 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/tenants/{tenantID} [delete]
func (s *Server) deleteTenant(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteTenant(r.Context(), r.PathValue("tenantID")); err != nil {
if store.IsNotFound(err) {
@ -71,6 +116,21 @@ func (s *Server) deleteTenant(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// createGatewayUser godoc
// @Summary 创建用户
// @Description 管理端创建网关用户password 为空时不设置本地密码,非空时至少 8 位。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.GatewayUserInput true "用户请求"
// @Success 201 {object} store.GatewayUser
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users [post]
func (s *Server) createGatewayUser(w http.ResponseWriter, r *http.Request) {
var input store.GatewayUserInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -98,6 +158,23 @@ func (s *Server) createGatewayUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updateGatewayUser godoc
// @Summary 更新用户
// @Description 管理端更新网关用户资料、角色、默认用户组和可选本地密码。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param userID path string true "用户 ID"
// @Param input body store.GatewayUserInput true "用户请求"
// @Success 200 {object} store.GatewayUser
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/{userID} [patch]
func (s *Server) updateGatewayUser(w http.ResponseWriter, r *http.Request) {
var input store.GatewayUserInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -129,6 +206,19 @@ func (s *Server) updateGatewayUser(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deleteGatewayUser godoc
// @Summary 删除用户
// @Description 管理端删除网关用户。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param userID path string true "用户 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/users/{userID} [delete]
func (s *Server) deleteGatewayUser(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteGatewayUser(r.Context(), r.PathValue("userID")); err != nil {
if store.IsNotFound(err) {
@ -142,6 +232,21 @@ func (s *Server) deleteGatewayUser(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// createUserGroup godoc
// @Summary 创建用户组
// @Description 管理端创建用户组,可配置默认定价、运行策略、限流和配额策略。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.UserGroupInput true "用户组请求"
// @Success 201 {object} store.UserGroup
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/user-groups [post]
func (s *Server) createUserGroup(w http.ResponseWriter, r *http.Request) {
var input store.UserGroupInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -165,6 +270,23 @@ func (s *Server) createUserGroup(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updateUserGroup godoc
// @Summary 更新用户组
// @Description 管理端更新用户组基础信息和策略配置。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param groupID path string true "用户组 ID"
// @Param input body store.UserGroupInput true "用户组请求"
// @Success 200 {object} store.UserGroup
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/user-groups/{groupID} [patch]
func (s *Server) updateUserGroup(w http.ResponseWriter, r *http.Request) {
var input store.UserGroupInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -192,6 +314,19 @@ func (s *Server) updateUserGroup(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deleteUserGroup godoc
// @Summary 删除用户组
// @Description 管理端删除用户组。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param groupID path string true "用户组 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/user-groups/{groupID} [delete]
func (s *Server) deleteUserGroup(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteUserGroup(r.Context(), r.PathValue("groupID")); err != nil {
if store.IsNotFound(err) {

View File

@ -123,6 +123,16 @@ type catalogGroup struct {
enabled bool
}
// listModelCatalog godoc
// @Summary 列出模型目录
// @Description 聚合平台模型、基础模型、供应商、运行策略和访问规则,返回前端模型目录所需的过滤器、摘要和展示字段。
// @Tags model-catalog
// @Produce json
// @Security BearerAuth
// @Success 200 {object} ModelCatalogResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/v1/model-catalog [get]
func (s *Server) listModelCatalog(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
models, err := s.store.ListModels(ctx)

View File

@ -0,0 +1,243 @@
package httpapi
import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
type HealthResponse struct {
OK bool `json:"ok" example:"true"`
Service string `json:"service" example:"easyai-ai-gateway"`
Env string `json:"env" example:"development"`
IdentityMode string `json:"identityMode" example:"standalone"`
}
type ReadyResponse struct {
OK bool `json:"ok" example:"true"`
}
type ErrorEnvelope struct {
Error ErrorPayload `json:"error"`
}
type ErrorPayload struct {
Message string `json:"message" example:"invalid json body"`
Status int `json:"status" example:"400"`
Code string `json:"code,omitempty" example:"rate_limit"`
}
type AuthResponse struct {
AccessToken string `json:"accessToken" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
TokenType string `json:"tokenType" example:"Bearer"`
ExpiresIn int `json:"expiresIn" example:"86400"`
User *auth.User `json:"user"`
}
type ItemListResponse struct {
Items []map[string]interface{} `json:"items"`
}
type PlatformListResponse struct {
Items []store.Platform `json:"items"`
}
type PlatformModelListResponse struct {
Items []store.PlatformModel `json:"items"`
}
type CatalogProviderListResponse struct {
Items []store.CatalogProvider `json:"items"`
}
type BaseModelListResponse struct {
Items []store.BaseModel `json:"items"`
}
type TenantListResponse struct {
Items []store.GatewayTenant `json:"items"`
}
type UserListResponse struct {
Items []store.GatewayUser `json:"items"`
}
type UserGroupListResponse struct {
Items []store.UserGroup `json:"items"`
}
type AccessRuleListResponse struct {
Items []store.AccessRule `json:"items"`
}
type APIKeyListResponse struct {
Items []store.APIKey `json:"items"`
}
type PlayableAPIKeyListResponse struct {
Items []store.PlayableAPIKey `json:"items"`
}
type PricingRuleListResponse struct {
Items []store.PricingRule `json:"items"`
}
type PricingRuleSetListResponse struct {
Items []store.PricingRuleSet `json:"items"`
}
type RuntimePolicySetListResponse struct {
Items []store.RuntimePolicySet `json:"items"`
}
type RateLimitWindowListResponse struct {
Items []store.RateLimitWindow `json:"items"`
}
type ModelRateLimitStatusListResponse struct {
Items []store.ModelRateLimitStatus `json:"items"`
}
type AuditLogListResponse struct {
Items []store.AuditLog `json:"items"`
}
type WalletTransactionListResponse struct {
Items []store.GatewayWalletTransaction `json:"items"`
Total int `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"pageSize" example:"50"`
}
type TaskListResponse struct {
Items []store.GatewayTask `json:"items"`
Total int `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"pageSize" example:"50"`
}
type TaskParamPreprocessingLogListResponse struct {
Items []store.TaskParamPreprocessingLog `json:"items"`
}
type TaskEventListResponse struct {
Items []store.TaskEvent `json:"items"`
}
type ReplacePlatformModelsRequest struct {
Models []store.CreatePlatformModelInput `json:"models"`
}
type TaskAcceptedResponse struct {
TaskID string `json:"taskId" example:"9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25"`
Task store.GatewayTask `json:"task"`
Next TaskNextLinks `json:"next"`
}
type TaskNextLinks struct {
Events string `json:"events" example:"/api/v1/tasks/9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25/events"`
Detail string `json:"detail" example:"/api/v1/tasks/9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25"`
}
type TaskAcceptedEvent struct {
TaskID string `json:"taskId" example:"9f4d8f3d-5f5f-4bb7-a4be-344a9f930e25"`
Status string `json:"status" example:"pending"`
}
type PricingEstimateRequest struct {
Kind string `json:"kind" example:"chat.completions"`
Model string `json:"model" example:"gpt-4o-mini"`
Messages []ChatMessage `json:"messages,omitempty"`
Prompt string `json:"prompt,omitempty" example:"A small orange cat"`
MaxTokens int `json:"max_tokens,omitempty" example:"512"`
N int `json:"n,omitempty" example:"1"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type PricingEstimateResponse struct {
Items []map[string]interface{} `json:"items"`
Resolver string `json:"resolver" example:"effective-pricing-v1"`
}
type TaskRequest struct {
Model string `json:"model" example:"gpt-4o-mini"`
Messages []ChatMessage `json:"messages,omitempty"`
Input string `json:"input,omitempty" example:"Tell me a short story"`
Prompt string `json:"prompt,omitempty" example:"A watercolor robot reading a book"`
Stream bool `json:"stream,omitempty" example:"false"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
MaxTokens int `json:"max_tokens,omitempty" example:"512"`
Size string `json:"size,omitempty" example:"1024x1024"`
Duration int `json:"duration,omitempty" example:"5"`
Resolution string `json:"resolution,omitempty" example:"720p"`
}
type ChatCompletionRequest struct {
Model string `json:"model" example:"gpt-4o-mini"`
Messages []ChatMessage `json:"messages"`
Temperature float64 `json:"temperature,omitempty" example:"0.7"`
MaxTokens int `json:"max_tokens,omitempty" example:"512"`
Stream bool `json:"stream,omitempty" example:"false"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type ChatMessage struct {
Role string `json:"role" example:"user"`
Content string `json:"content" example:"Hello"`
}
type ResponsesRequest struct {
Model string `json:"model" example:"gpt-4o-mini"`
Input interface{} `json:"input" example:"Tell me a short story"`
Stream bool `json:"stream,omitempty" example:"false"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type ImageGenerationRequest struct {
Model string `json:"model" example:"gpt-image-1"`
Prompt string `json:"prompt" example:"A watercolor robot reading a book"`
N int `json:"n,omitempty" example:"1"`
Size string `json:"size,omitempty" example:"1024x1024"`
Quality string `json:"quality,omitempty" example:"standard"`
ResponseFormat string `json:"response_format,omitempty" example:"url"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type ImageEditRequest struct {
Model string `json:"model" example:"gpt-image-1"`
Prompt string `json:"prompt" example:"Add a sunset background"`
Image string `json:"image,omitempty" example:"https://example.com/image.png"`
Mask string `json:"mask,omitempty" example:"https://example.com/mask.png"`
N int `json:"n,omitempty" example:"1"`
Size string `json:"size,omitempty" example:"1024x1024"`
ResponseFormat string `json:"response_format,omitempty" example:"url"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type VideoGenerationRequest struct {
Model string `json:"model" example:"video-model"`
Prompt string `json:"prompt" example:"A cinematic drone shot over mountains"`
Duration int `json:"duration,omitempty" example:"5"`
Resolution string `json:"resolution,omitempty" example:"720p"`
RunMode string `json:"runMode,omitempty" example:"simulation"`
}
type CompatibleResponse struct {
ID string `json:"id" example:"chatcmpl-123"`
Object string `json:"object" example:"chat.completion"`
Model string `json:"model" example:"gpt-4o-mini"`
Choices []map[string]interface{} `json:"choices,omitempty"`
Usage map[string]interface{} `json:"usage,omitempty"`
}
type NetworkProxyConfigResponse struct {
GlobalHTTPProxy string `json:"globalHttpProxy" example:"http://127.0.0.1:7890"`
GlobalHTTPProxySet bool `json:"globalHttpProxySet" example:"true"`
GlobalHTTPProxySource string `json:"globalHttpProxySource" example:"env"`
}
type WalletAdjustmentResponse struct {
Account store.GatewayWalletAccount `json:"account"`
Before store.GatewayWalletAccount `json:"before"`
Transaction store.GatewayWalletTransaction `json:"transaction"`
AuditLog store.AuditLog `json:"auditLog"`
}

View File

@ -9,6 +9,17 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// listPricingRuleSets godoc
// @Summary 列出定价规则集
// @Description 管理端返回可分配给平台、模型、租户或用户组的定价规则集。
// @Tags pricing
// @Produce json
// @Security BearerAuth
// @Success 200 {object} PricingRuleSetListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/pricing/rule-sets [get]
func (s *Server) listPricingRuleSets(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListPricingRuleSets(r.Context())
if err != nil {
@ -19,6 +30,21 @@ func (s *Server) listPricingRuleSets(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// createPricingRuleSet godoc
// @Summary 创建定价规则集
// @Description 管理端创建定价规则集ruleSetKey、name 和至少一条 rule 必填。
// @Tags pricing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.PricingRuleSetInput true "定价规则集请求"
// @Success 201 {object} store.PricingRuleSet
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/pricing/rule-sets [post]
func (s *Server) createPricingRuleSet(w http.ResponseWriter, r *http.Request) {
var input store.PricingRuleSetInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -42,6 +68,23 @@ func (s *Server) createPricingRuleSet(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, item)
}
// updatePricingRuleSet godoc
// @Summary 更新定价规则集
// @Description 管理端更新定价规则集及其规则列表。
// @Tags pricing
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param ruleSetID path string true "定价规则集 ID"
// @Param input body store.PricingRuleSetInput true "定价规则集请求"
// @Success 200 {object} store.PricingRuleSet
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/pricing/rule-sets/{ruleSetID} [patch]
func (s *Server) updatePricingRuleSet(w http.ResponseWriter, r *http.Request) {
var input store.PricingRuleSetInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -69,6 +112,19 @@ func (s *Server) updatePricingRuleSet(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// deletePricingRuleSet godoc
// @Summary 删除定价规则集
// @Description 管理端删除非默认定价规则集;默认规则集受保护。
// @Tags pricing
// @Produce json
// @Security BearerAuth
// @Param ruleSetID path string true "定价规则集 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/pricing/rule-sets/{ruleSetID} [delete]
func (s *Server) deletePricingRuleSet(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeletePricingRuleSet(r.Context(), r.PathValue("ruleSetID")); err != nil {
if store.IsNotFound(err) {

View File

@ -9,6 +9,17 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// listRuntimePolicySets godoc
// @Summary 列出运行策略集
// @Description 管理端返回可分配给平台、模型或用户组的运行策略集。
// @Tags runtime
// @Produce json
// @Security BearerAuth
// @Success 200 {object} RuntimePolicySetListResponse
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/policy-sets [get]
func (s *Server) listRuntimePolicySets(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListRuntimePolicySets(r.Context())
if err != nil {
@ -19,6 +30,17 @@ func (s *Server) listRuntimePolicySets(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
// getRunnerPolicy godoc
// @Summary 获取 Runner 策略
// @Description 管理端获取当前生效的默认 Runner 调度策略。
// @Tags runtime
// @Produce json
// @Security BearerAuth
// @Success 200 {object} store.RunnerPolicy
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/runner-policy [get]
func (s *Server) getRunnerPolicy(w http.ResponseWriter, r *http.Request) {
item, err := s.store.GetActiveRunnerPolicy(r.Context())
if err != nil {
@ -29,6 +51,20 @@ func (s *Server) getRunnerPolicy(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, item)
}
// updateRunnerPolicy godoc
// @Summary 更新 Runner 策略
// @Description 管理端写入默认 Runner 调度策略。
// @Tags runtime
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.RunnerPolicyInput true "Runner 策略请求"
// @Success 200 {object} store.RunnerPolicy
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/runner-policy [patch]
func (s *Server) updateRunnerPolicy(w http.ResponseWriter, r *http.Request) {
var input store.RunnerPolicyInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -45,10 +81,26 @@ func (s *Server) updateRunnerPolicy(w http.ResponseWriter, r *http.Request) {
}
type updatePlatformDynamicPriorityRequest struct {
DynamicPriority *int `json:"dynamicPriority"`
Reset bool `json:"reset"`
DynamicPriority *int `json:"dynamicPriority" example:"10"`
Reset bool `json:"reset" example:"false"`
}
// updatePlatformDynamicPriority godoc
// @Summary 更新平台动态优先级
// @Description 管理端调整平台运行时动态优先级reset 为 true 时清空动态值。
// @Tags runtime
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param platformID path string true "平台 ID"
// @Param input body updatePlatformDynamicPriorityRequest true "动态优先级请求"
// @Success 200 {object} store.Platform
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/platforms/{platformID}/dynamic-priority [patch]
func (s *Server) updatePlatformDynamicPriority(w http.ResponseWriter, r *http.Request) {
var input updatePlatformDynamicPriorityRequest
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -82,6 +134,21 @@ func (s *Server) updatePlatformDynamicPriority(w http.ResponseWriter, r *http.Re
writeJSON(w, http.StatusOK, item)
}
// createRuntimePolicySet godoc
// @Summary 创建运行策略集
// @Description 管理端创建运行策略集policyKey 和 name 必填。
// @Tags runtime
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param input body store.RuntimePolicySetInput true "运行策略集请求"
// @Success 201 {object} store.RuntimePolicySet
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/policy-sets [post]
func (s *Server) createRuntimePolicySet(w http.ResponseWriter, r *http.Request) {
var input store.RuntimePolicySetInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -105,6 +172,23 @@ func (s *Server) createRuntimePolicySet(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusCreated, item)
}
// updateRuntimePolicySet godoc
// @Summary 更新运行策略集
// @Description 管理端更新运行策略集及其限流、重试、超时等策略配置。
// @Tags runtime
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param policySetID path string true "运行策略集 ID"
// @Param input body store.RuntimePolicySetInput true "运行策略集请求"
// @Success 200 {object} store.RuntimePolicySet
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/policy-sets/{policySetID} [patch]
func (s *Server) updateRuntimePolicySet(w http.ResponseWriter, r *http.Request) {
var input store.RuntimePolicySetInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
@ -132,6 +216,19 @@ func (s *Server) updateRuntimePolicySet(w http.ResponseWriter, r *http.Request)
writeJSON(w, http.StatusOK, item)
}
// deleteRuntimePolicySet godoc
// @Summary 删除运行策略集
// @Description 管理端删除非默认运行策略集;默认策略集受保护。
// @Tags runtime
// @Produce json
// @Security BearerAuth
// @Param policySetID path string true "运行策略集 ID"
// @Success 204 "No Content"
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/admin/runtime/policy-sets/{policySetID} [delete]
func (s *Server) deleteRuntimePolicySet(w http.ResponseWriter, r *http.Request) {
if err := s.store.DeleteRuntimePolicySet(r.Context(), r.PathValue("policySetID")); err != nil {
if store.IsNotFound(err) {

View File

@ -18,6 +18,16 @@ const simulationVideoMP4Base64 = "AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAA
var simulationVideoMP4 = mustDecodeSimulationAsset(simulationVideoMP4Base64)
// serveSimulationAsset godoc
// @Summary 获取模拟资源
// @Description 返回本地模拟模式使用的图片、视频封面或短视频资源。
// @Tags simulation
// @Produce image/svg+xml
// @Produce video/mp4
// @Param asset path string true "资源文件名,可选 image.svg、image.png、image-edit.svg、image-edit.png、video-poster.svg、video.mp4"
// @Success 200 {file} binary
// @Failure 404 {string} string "Not Found"
// @Router /static/simulation/{asset} [get]
func serveSimulationAsset(w http.ResponseWriter, r *http.Request) {
asset := strings.ToLower(strings.TrimSpace(r.PathValue("asset")))
switch asset {

View File

@ -7,6 +7,17 @@ import (
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)
// getWallet godoc
// @Summary 获取钱包摘要
// @Description 返回当前用户的钱包账户、余额和最近消费摘要,可按 currency 过滤。
// @Tags wallet
// @Produce json
// @Security BearerAuth
// @Param currency query string false "币种" default(USD)
// @Success 200 {object} store.WalletSummary
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/wallet [get]
func (s *Server) getWallet(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
@ -22,6 +33,24 @@ func (s *Server) getWallet(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, summary)
}
// listWalletTransactions godoc
// @Summary 列出钱包交易
// @Description 返回当前用户的钱包交易流水,支持关键字、方向、交易类型、时间范围和分页过滤。
// @Tags wallet
// @Produce json
// @Security BearerAuth
// @Param q query string false "搜索关键字,别名 query"
// @Param direction query string false "交易方向"
// @Param transactionType query string false "交易类型"
// @Param createdFrom query string false "创建时间起点,别名 from"
// @Param createdTo query string false "创建时间终点,别名 to"
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页数量,别名 limit" default(50)
// @Success 200 {object} WalletTransactionListResponse
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 500 {object} ErrorEnvelope
// @Router /api/workspace/wallet/transactions [get]
func (s *Server) listWalletTransactions(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {

View File

@ -19,6 +19,14 @@
"command": "go run ./cmd/migrate"
}
},
"openapi": {
"executor": "nx:run-commands",
"outputs": ["{projectRoot}/docs/swagger.json", "{projectRoot}/docs/swagger.yaml"],
"options": {
"cwd": "apps/api",
"command": "go run github.com/swaggo/swag/cmd/swag@v1.16.4 init --parseInternal -d ./cmd/gateway,./internal/httpapi,./internal/store,./internal/auth -g main.go -o docs --outputTypes json,yaml"
}
},
"test": {
"executor": "nx:run-commands",
"outputs": ["{workspaceRoot}/coverage/apps/api"],

View File

@ -9,7 +9,8 @@
"test": "nx run-many -t test -p api web",
"lint": "nx run-many -t lint -p web contracts",
"db:create": "scripts/create-database.sh",
"migrate": "nx run api:migrate"
"migrate": "nx run api:migrate",
"openapi": "nx run api:openapi"
},
"devDependencies": {
"@nx/vite": "^21.0.0",