package workbench import ( "encoding/json" "errors" "net/http" "strconv" "strings" "aigateway.local/core/internal/identity" "aigateway.local/core/internal/platform/apiresponse" ) type AdminHTTPHandler struct { service *Service tools *ToolService notifications *NotificationService identity *identity.Service mux *http.ServeMux } func NewAdminHTTPHandler(service *Service, tools *ToolService, notifications *NotificationService, identityService *identity.Service) *AdminHTTPHandler { h := &AdminHTTPHandler{service: service, tools: tools, notifications: notifications, identity: identityService, mux: http.NewServeMux()} h.mux.HandleFunc("GET /api/v1/admin/prompt-categories", h.listPromptCategories) h.mux.HandleFunc("POST /api/v1/admin/prompt-categories", h.createPromptCategory) h.mux.HandleFunc("DELETE /api/v1/admin/prompt-categories/{id}", h.deletePromptCategory) h.mux.HandleFunc("GET /api/v1/admin/prompts", h.listPrompts) h.mux.HandleFunc("POST /api/v1/admin/prompts", h.createPrompt) h.mux.HandleFunc("GET /api/v1/admin/prompts/{id}", h.getPrompt) h.mux.HandleFunc("PUT /api/v1/admin/prompts/{id}", h.updatePrompt) h.mux.HandleFunc("DELETE /api/v1/admin/prompts/{id}", h.deletePrompt) h.mux.HandleFunc("GET /api/v1/admin/prompts/{id}/versions", h.listPromptVersions) h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/versions", h.addPromptVersion) h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/versions/{version}/activate", h.activatePromptVersion) h.mux.HandleFunc("POST /api/v1/admin/prompts/{id}/render", h.previewPrompt) h.mux.HandleFunc("GET /api/v1/admin/knowledge-bases", h.listKnowledgeBases) h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases", h.createKnowledgeBase) h.mux.HandleFunc("PUT /api/v1/admin/knowledge-bases/{id}", h.updateKnowledgeBase) h.mux.HandleFunc("DELETE /api/v1/admin/knowledge-bases/{id}", h.deleteKnowledgeBase) h.mux.HandleFunc("GET /api/v1/admin/knowledge-bases/{id}/documents", h.listDocuments) h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/documents", h.addDocument) h.mux.HandleFunc("DELETE /api/v1/admin/knowledge-bases/{id}/documents/{document_id}", h.deleteDocument) h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/documents/{document_id}/reprocess", h.reprocessDocument) h.mux.HandleFunc("POST /api/v1/admin/knowledge-bases/{id}/search", h.searchKnowledge) h.mux.HandleFunc("GET /api/v1/admin/tools", h.listTools) h.mux.HandleFunc("POST /api/v1/admin/tools", h.createTool) h.mux.HandleFunc("PUT /api/v1/admin/tools/{id}", h.updateTool) h.mux.HandleFunc("DELETE /api/v1/admin/tools/{id}", h.deleteTool) h.mux.HandleFunc("POST /api/v1/admin/tools/{id}/test", h.testTool) h.mux.HandleFunc("GET /api/v1/admin/tool-approvals", h.listToolApprovals) h.mux.HandleFunc("POST /api/v1/admin/tool-approvals/{id}/decide", h.decideToolApproval) h.mux.HandleFunc("GET /api/v1/admin/applications", h.listApplications) h.mux.HandleFunc("POST /api/v1/admin/applications", h.createApplication) h.mux.HandleFunc("GET /api/v1/admin/applications/catalog", h.applicationCatalog) h.mux.HandleFunc("GET /api/v1/admin/applications/asset-dependencies", h.applicationDependencies) h.mux.HandleFunc("GET /api/v1/admin/applications/{id}", h.getApplication) h.mux.HandleFunc("PUT /api/v1/admin/applications/{id}", h.updateApplication) h.mux.HandleFunc("DELETE /api/v1/admin/applications/{id}", h.deleteApplication) h.mux.HandleFunc("POST /api/v1/admin/applications/{id}/publish", h.publishApplication) h.mux.HandleFunc("POST /api/v1/admin/applications/{id}/rollback/{version}", h.rollbackApplication) h.mux.HandleFunc("GET /api/v1/admin/applications/{id}/versions", h.listApplicationVersions) h.mux.HandleFunc("GET /api/v1/admin/applications/{id}/runs", h.listApplicationRuns) h.mux.HandleFunc("GET /api/v1/admin/notification-channels", h.listChannels) h.mux.HandleFunc("POST /api/v1/admin/notification-channels", h.createChannel) h.mux.HandleFunc("PUT /api/v1/admin/notification-channels/{id}", h.updateChannel) h.mux.HandleFunc("DELETE /api/v1/admin/notification-channels/{id}", h.deleteChannel) h.mux.HandleFunc("GET /api/v1/admin/notification-deliveries", h.listDeliveries) h.mux.HandleFunc("POST /api/v1/admin/notification-deliveries/{id}/retry", h.retryDelivery) return h } func (h *AdminHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) } func (h *AdminHTTPHandler) require(w http.ResponseWriter, r *http.Request, permission string) (identity.Account, bool) { account, err := h.identity.Authenticate(r.Context(), identity.KindAdmin, r.Header.Get("Authorization")) if err != nil { apiresponse.Error(w, 401, "登录状态无效") return account, false } if !identity.HasPermission(account, permission) { apiresponse.Error(w, 403, "缺少 AI 资产管理权限") return account, false } return account, true } func decodeAsset(w http.ResponseWriter, r *http.Request, target any) bool { decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 3<<20)) decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { apiresponse.Error(w, 400, "请求格式无效") return false } return true } func assetError(w http.ResponseWriter, err error) { switch { case errors.Is(err, ErrNotFound): apiresponse.Error(w, 404, "资源不存在") case errors.Is(err, ErrConflict): apiresponse.Error(w, 409, "资源仍被已发布或草稿应用引用") default: apiresponse.Error(w, 400, err.Error()) } } func (h *AdminHTTPHandler) listPromptCategories(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok { return } items, err := h.service.ListPromptCategories(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) createPromptCategory(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } var input struct { Name string `json:"name"` Description string `json:"description"` } if !decodeAsset(w, r, &input) { return } item, err := h.service.CreatePromptCategory(r.Context(), input.Name, input.Description, a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deletePromptCategory(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } if err := h.service.DeletePromptCategory(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } type promptPayload struct { Name string `json:"name"` Description string `json:"description"` CategoryID *string `json:"category_id"` Tags []string `json:"tags"` DepartmentIDs []string `json:"department_ids"` Enabled bool `json:"enabled"` Content string `json:"content"` Variables []Variable `json:"variables"` ChangeNote string `json:"change_note"` } func promptInput(p promptPayload) PromptInput { return PromptInput{Name: p.Name, Description: p.Description, CategoryID: p.CategoryID, Tags: p.Tags, DepartmentIDs: p.DepartmentIDs, Enabled: p.Enabled, Content: p.Content, Variables: p.Variables, ChangeNote: p.ChangeNote} } func (h *AdminHTTPHandler) listPrompts(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok { return } items, err := h.service.ListPrompts(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) getPrompt(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok { return } item, err := h.service.GetPrompt(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) createPrompt(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } var p promptPayload if !decodeAsset(w, r, &p) { return } item, err := h.service.CreatePrompt(r.Context(), promptInput(p), a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) updatePrompt(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } var p promptPayload if !decodeAsset(w, r, &p) { return } item, err := h.service.UpdatePrompt(r.Context(), r.PathValue("id"), promptInput(p), a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deletePrompt(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } if err := h.service.DeletePrompt(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) listPromptVersions(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok { return } items, err := h.service.ListPromptVersions(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) addPromptVersion(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } var p struct { Content string `json:"content"` Variables []Variable `json:"variables"` ChangeNote string `json:"change_note"` Activate bool `json:"activate"` } if !decodeAsset(w, r, &p) { return } item, err := h.service.AddPromptVersion(r.Context(), r.PathValue("id"), p.Content, p.Variables, p.ChangeNote, a.ID, p.Activate) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) activatePromptVersion(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionPromptManage) if !ok { return } version, err := strconv.Atoi(r.PathValue("version")) if err != nil { apiresponse.Error(w, 400, "版本号无效") return } if err = h.service.ActivatePromptVersion(r.Context(), r.PathValue("id"), version, a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"activated": true}) } func (h *AdminHTTPHandler) previewPrompt(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionPromptRead); !ok { return } var p struct { Variables map[string]any `json:"variables"` } if !decodeAsset(w, r, &p) { return } prompt, err := h.service.GetPrompt(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } if prompt.Current == nil { apiresponse.Error(w, 409, "Prompt 没有生效版本") return } rendered, err := RenderPrompt(prompt.Current.Content, prompt.Current.Variables, p.Variables) if err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]any{"rendered": rendered, "version": prompt.Current.Version}) } func (h *AdminHTTPHandler) listKnowledgeBases(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok { return } items, err := h.service.ListKnowledgeBases(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) createKnowledgeBase(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } var k KnowledgeBase if !decodeAsset(w, r, &k) { return } item, err := h.service.SaveKnowledgeBase(r.Context(), k, a.ID, true) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) updateKnowledgeBase(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } var k KnowledgeBase if !decodeAsset(w, r, &k) { return } k.ID = r.PathValue("id") item, err := h.service.SaveKnowledgeBase(r.Context(), k, a.ID, false) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deleteKnowledgeBase(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } if err := h.service.DeleteKnowledgeBase(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) listDocuments(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok { return } items, err := h.service.ListKnowledgeDocuments(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) addDocument(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } var p struct { Title string `json:"title"` SourceType string `json:"source_type"` SourceURI string `json:"source_uri"` Content string `json:"content"` } if !decodeAsset(w, r, &p) { return } item, err := h.service.AddKnowledgeDocument(r.Context(), r.PathValue("id"), p.Title, p.SourceType, p.SourceURI, p.Content, a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deleteDocument(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } if err := h.service.DeleteKnowledgeDocument(r.Context(), r.PathValue("id"), r.PathValue("document_id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) reprocessDocument(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionKnowledgeManage) if !ok { return } item, err := h.service.ReprocessKnowledgeDocument(r.Context(), r.PathValue("id"), r.PathValue("document_id"), a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) searchKnowledge(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionKnowledgeRead); !ok { return } var p struct { Query string `json:"query"` TopK int `json:"top_k"` } if !decodeAsset(w, r, &p) { return } items, err := NewRetriever(h.service, h.service.Embedder()).Search(r.Context(), r.PathValue("id"), p.Query, p.TopK) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } type toolPayload struct { Code string `json:"code"` Name string `json:"name"` Description string `json:"description"` EndpointURL string `json:"endpoint_url"` HTTPMethod string `json:"http_method"` Headers *map[string]string `json:"headers"` InputSchema json.RawMessage `json:"input_schema"` TimeoutSeconds int `json:"timeout_seconds"` DepartmentIDs []string `json:"department_ids"` RateLimitRPM int `json:"rate_limit_rpm"` ApprovalReq bool `json:"approval_required"` Enabled bool `json:"enabled"` } func toolInput(p toolPayload, create bool) ToolInput { var headers map[string]string if p.Headers != nil { headers = *p.Headers } else if create { headers = map[string]string{} } return ToolInput{Code: p.Code, Name: p.Name, Description: p.Description, EndpointURL: p.EndpointURL, HTTPMethod: p.HTTPMethod, Headers: headers, InputSchema: p.InputSchema, TimeoutSeconds: p.TimeoutSeconds, DepartmentIDs: p.DepartmentIDs, RateLimitRPM: p.RateLimitRPM, ApprovalRequired: p.ApprovalReq, Enabled: p.Enabled} } // listToolApprovals 工具审批申请列表(治理中心)。 func (h *AdminHTTPHandler) listToolApprovals(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionToolRead); !ok { return } items, err := h.tools.ListApprovalRequests(r.Context(), r.URL.Query().Get("status")) if err != nil { apiresponse.Error(w, http.StatusServiceUnavailable, "工具审批查询失败") return } apiresponse.OK(w, items) } // decideToolApproval 审批工具申请(通过后工具可调用)。 func (h *AdminHTTPHandler) decideToolApproval(w http.ResponseWriter, r *http.Request) { admin, ok := h.require(w, r, identity.PermissionToolManage) if !ok { return } var input struct { Status string `json:"status"` Note string `json:"note"` } if !decodeAsset(w, r, &input) { return } if err := h.tools.DecideApprovalRequest(r.Context(), r.PathValue("id"), input.Status, input.Note, admin.ID); err != nil { apiresponse.Error(w, http.StatusBadRequest, err.Error()) return } apiresponse.OK(w, map[string]bool{"decided": true}) } func (h *AdminHTTPHandler) listTools(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionToolRead); !ok { return } items, err := h.tools.List(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) createTool(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionToolManage) if !ok { return } var p toolPayload if !decodeAsset(w, r, &p) { return } item, err := h.tools.Save(r.Context(), "", toolInput(p, true), a.ID, true) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) updateTool(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionToolManage) if !ok { return } var p toolPayload if !decodeAsset(w, r, &p) { return } item, err := h.tools.Save(r.Context(), r.PathValue("id"), toolInput(p, false), a.ID, false) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deleteTool(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionToolManage) if !ok { return } if err := h.tools.Delete(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) testTool(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionToolManage); !ok { return } var p struct { Input map[string]any `json:"input"` } if !decodeAsset(w, r, &p) { return } tool, err := h.tools.Get(r.Context(), r.PathValue("id")) if err == nil { result, executeErr := h.tools.Execute(r.Context(), tool, p.Input, "", "admin-test") err = executeErr if err == nil { apiresponse.OK(w, result) return } } assetError(w, err) } func (h *AdminHTTPHandler) listApplications(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } items, err := h.service.ListApplications(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) getApplication(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } item, err := h.service.GetApplication(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } versions, err := h.service.ListApplicationVersions(r.Context(), item.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]any{"application": item, "versions": versions}) } func (h *AdminHTTPHandler) applicationCatalog(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } prompts, err := h.service.ListPrompts(r.Context()) if err != nil { assetError(w, err) return } knowledge, err := h.service.ListKnowledgeBases(r.Context()) if err != nil { assetError(w, err) return } tools, err := h.tools.List(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]any{"prompts": prompts, "knowledge_bases": knowledge, "tools": tools}) } func (h *AdminHTTPHandler) applicationDependencies(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } assetType := strings.TrimSpace(r.URL.Query().Get("asset_type")) assetID := strings.TrimSpace(r.URL.Query().Get("asset_id")) if assetID == "" || (assetType != "prompt" && assetType != "knowledge" && assetType != "tool") { apiresponse.Error(w, 400, "asset_type 或 asset_id 无效") return } items, err := h.service.ApplicationDependencies(r.Context(), assetType, assetID) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) createApplication(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionApplicationManage) if !ok { return } var app Application if !decodeAsset(w, r, &app) { return } item, err := h.service.SaveApplication(r.Context(), app, a.ID, true) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) updateApplication(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionApplicationManage) if !ok { return } var app Application if !decodeAsset(w, r, &app) { return } app.ID = r.PathValue("id") item, err := h.service.SaveApplication(r.Context(), app, a.ID, false) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deleteApplication(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionApplicationManage) if !ok { return } if err := h.service.DeleteApplication(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) publishApplication(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionApplicationManage) if !ok { return } var p struct { ChangeNote string `json:"change_note"` } if !decodeAsset(w, r, &p) { return } app, err := h.service.PublishApplication(r.Context(), r.PathValue("id"), p.ChangeNote, a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, app) } func (h *AdminHTTPHandler) rollbackApplication(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionApplicationManage) if !ok { return } version, err := strconv.Atoi(r.PathValue("version")) if err != nil || version < 1 { apiresponse.Error(w, 400, "版本号无效") return } var input struct { ChangeNote string `json:"change_note"` } if !decodeAsset(w, r, &input) { return } item, err := h.service.RollbackApplication(r.Context(), r.PathValue("id"), version, input.ChangeNote, a.ID) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) listApplicationVersions(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } items, err := h.service.ListApplicationVersions(r.Context(), r.PathValue("id")) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) listApplicationRuns(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionApplicationRead); !ok { return } items, err := h.service.ListApplicationRuns(r.Context(), r.PathValue("id"), 100) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } type channelPayload struct { Name string `json:"name"` WebhookURL string `json:"webhook_url"` SigningSecret *string `json:"signing_secret"` EventPatterns []string `json:"event_patterns"` Enabled bool `json:"enabled"` } func (h *AdminHTTPHandler) listChannels(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionNotificationRead); !ok { return } items, err := h.notifications.ListChannels(r.Context()) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) createChannel(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionNotificationManage) if !ok { return } var p channelPayload if !decodeAsset(w, r, &p) { return } item, err := h.notifications.SaveChannel(r.Context(), "", NotificationInput{Name: p.Name, WebhookURL: p.WebhookURL, SigningSecret: p.SigningSecret, EventPatterns: p.EventPatterns, Enabled: p.Enabled}, a.ID, true) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) updateChannel(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionNotificationManage) if !ok { return } var p channelPayload if !decodeAsset(w, r, &p) { return } item, err := h.notifications.SaveChannel(r.Context(), r.PathValue("id"), NotificationInput{Name: p.Name, WebhookURL: p.WebhookURL, SigningSecret: p.SigningSecret, EventPatterns: p.EventPatterns, Enabled: p.Enabled}, a.ID, false) if err != nil { assetError(w, err) return } apiresponse.OK(w, item) } func (h *AdminHTTPHandler) deleteChannel(w http.ResponseWriter, r *http.Request) { a, ok := h.require(w, r, identity.PermissionNotificationManage) if !ok { return } if err := h.notifications.DeleteChannel(r.Context(), r.PathValue("id"), a.ID); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"deleted": true}) } func (h *AdminHTTPHandler) listDeliveries(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionNotificationRead); !ok { return } items, err := h.notifications.ListDeliveries(r.Context(), 100) if err != nil { assetError(w, err) return } apiresponse.OK(w, items) } func (h *AdminHTTPHandler) retryDelivery(w http.ResponseWriter, r *http.Request) { if _, ok := h.require(w, r, identity.PermissionNotificationManage); !ok { return } if err := h.notifications.RetryDelivery(r.Context(), r.PathValue("id")); err != nil { assetError(w, err) return } apiresponse.OK(w, map[string]bool{"retried": true}) } var _ = strings.TrimSpace