docs(api): 补全 OpenAPI 注释与生成文档
为接口、模型与脚本补齐 Swagger/OpenAPI 注释,生成最新文档,并增加一键生成与查看入口。
This commit is contained in:
parent
2685450f3e
commit
918dfbfee1
@ -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
|
||||
|
||||
@ -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
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
5913
apps/api/docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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{
|
||||
|
||||
@ -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 Key;secret 仅在本次响应中返回。
|
||||
// @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())
|
||||
@ -742,6 +1098,24 @@ func runErrorCode(err error) string {
|
||||
return clients.ErrorCode(err)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -837,6 +1211,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 {
|
||||
@ -851,6 +1238,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 {
|
||||
@ -871,6 +1271,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 Events,data 为 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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
243
apps/api/internal/httpapi/openapi_models.go
Normal file
243
apps/api/internal/httpapi/openapi_models.go
Normal 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"`
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user