package httpapi import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/prompt" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/publicapi" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/templates" ) type SettingsService interface { Get(context.Context) (any, error) Save(context.Context, map[string]any) (any, error) } type LogFilters struct { Level, Q, Source string Limit int } type LogService interface { List(context.Context, LogFilters) (any, error) Clear(context.Context) error } type PublicRequestAuthenticator interface { Authenticate(*http.Request) (publicapi.PublicClient, string, error) } type MiscDependencies struct { Platform *PlatformAuthorizer Templates *templates.Service PromptAssembler func(prompt.Input) prompt.Result Settings SettingsService Logs LogService Public PublicRequestAuthenticator Capabilities func(context.Context) (any, error) PublicOrigin string } type miscHandler struct{ dependencies MiscDependencies } func NewMiscHandler(dependencies MiscDependencies) (http.Handler, error) { if dependencies.Platform == nil { return nil, fmt.Errorf("misc HTTP: platform authorizer is required") } if dependencies.PromptAssembler == nil { dependencies.PromptAssembler = prompt.Assemble } return &miscHandler{dependencies: dependencies}, nil } func (handler *miscHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch { case r.URL.Path == "/api/image-templates" || strings.HasPrefix(r.URL.Path, "/api/image-templates/"): handler.templates(w, r) case r.URL.Path == "/api/prompt/assemble": handler.assemble(w, r) case r.URL.Path == "/api/settings": handler.settings(w, r) case r.URL.Path == "/api/logs": handler.logs(w, r) case r.URL.Path == "/api/v1/capabilities": handler.capabilities(w, r) case r.URL.Path == "/api/v1/openapi.json": handler.openapi(w, r) default: http.NotFound(w, r) } } func (handler *miscHandler) templates(w http.ResponseWriter, r *http.Request) { if handler.dependencies.Templates == nil { writeMiscError(w, 500) return } session, err := handler.dependencies.Platform.Authorize(r, PlatformApp) if err != nil { writeMiscAuth(w, err) return } id := strings.TrimPrefix(r.URL.Path, "/api/image-templates/") collection := r.URL.Path == "/api/image-templates" if collection && r.Method == http.MethodGet { items, current := handler.dependencies.Templates.List(r.Context(), session.User.ID) if current != nil { writeMiscError(w, 500) return } writeJSON(w, 200, map[string]any{"templates": items}) return } if collection && r.Method == http.MethodPost { var command templates.CreateCommand if !decodeMiscJSON(w, r, &command) { return } item, current := handler.dependencies.Templates.Create(r.Context(), session.User.ID, command) if current != nil { writeTemplateError(w, current) return } writeJSON(w, 201, map[string]any{"template": item}) return } if !collection && id != "" && r.Method == http.MethodPatch { var command templates.UpdateCommand if !decodeMiscJSON(w, r, &command) { return } item, current := handler.dependencies.Templates.Update(r.Context(), session.User.ID, id, command) if current != nil { writeTemplateError(w, current) return } writeJSON(w, 200, map[string]any{"template": item}) return } if !collection && id != "" && r.Method == http.MethodDelete { item, current := handler.dependencies.Templates.Delete(r.Context(), session.User.ID, id) if current != nil { writeTemplateError(w, current) return } writeJSON(w, 200, map[string]any{"template": item}) return } writeMiscMethod(w, allowedTemplateMethods(collection)) } func (handler *miscHandler) assemble(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeMiscMethod(w, "POST") return } if _, err := handler.dependencies.Platform.Authorize(r, PlatformApp); err != nil { writeMiscAuth(w, err) return } var input prompt.Input if !decodeMiscJSON(w, r, &input) { return } writeJSON(w, 200, handler.dependencies.PromptAssembler(input)) } func (handler *miscHandler) settings(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodPost { writeMiscMethod(w, "GET, POST") return } if _, err := handler.dependencies.Platform.Authorize(r, PlatformSuperAdmin); err != nil { writeMiscAuth(w, err) return } if handler.dependencies.Settings == nil { writeMiscError(w, 500) return } var ( value any err error ) if r.Method == http.MethodGet { value, err = handler.dependencies.Settings.Get(r.Context()) } else { var body struct { Values map[string]any `json:"values"` } if !decodeMiscJSON(w, r, &body) { return } if body.Values == nil { body.Values = map[string]any{} } value, err = handler.dependencies.Settings.Save(r.Context(), body.Values) } if err != nil { writeMiscError(w, 500) return } writeJSON(w, 200, value) } func (handler *miscHandler) logs(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodDelete { writeMiscMethod(w, "GET, DELETE") return } if _, err := handler.dependencies.Platform.Authorize(r, PlatformSuperAdmin); err != nil { writeMiscAuth(w, err) return } if handler.dependencies.Logs == nil { writeMiscError(w, 500) return } if r.Method == http.MethodDelete { if err := handler.dependencies.Logs.Clear(r.Context()); err != nil { writeMiscError(w, 500) return } writeJSON(w, 200, map[string]any{"ok": true}) return } limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) if limit == 0 { limit = 100 } level := r.URL.Query().Get("level") if level != "info" && level != "warning" && level != "error" { level = "all" } entries, err := handler.dependencies.Logs.List(r.Context(), LogFilters{Level: level, Q: r.URL.Query().Get("q"), Limit: limit}) if err != nil { writeMiscError(w, 500) return } writeJSON(w, 200, map[string]any{"entries": entries}) } func (handler *miscHandler) capabilities(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMiscMethod(w, "GET") return } if handler.dependencies.Public == nil { writeMiscError(w, 500) return } if _, _, err := handler.dependencies.Public.Authenticate(r); err != nil { writePublicMiscError(w, err) return } if handler.dependencies.Capabilities == nil { writeJSON(w, 200, map[string]any{"capabilities": []any{}}) return } items, err := handler.dependencies.Capabilities(r.Context()) if err != nil { writeMiscError(w, 500) return } writeJSON(w, 200, map[string]any{"capabilities": items}) } func (handler *miscHandler) openapi(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { writeMiscMethod(w, "GET") return } origin := normalizeMiscPublicOrigin(handler.dependencies.PublicOrigin) if origin == "" { origin = miscRequestOrigin(r) } writeJSON(w, 200, openAPIDocument(origin)) } func decodeMiscJSON(w http.ResponseWriter, r *http.Request, destination any) bool { decoder := json.NewDecoder(io.LimitReader(r.Body, 1<<20)) if err := decoder.Decode(destination); err != nil && err != io.EOF { writeJSON(w, 400, map[string]string{"error": "请求参数无效。"}) return false } return true } func writeTemplateError(w http.ResponseWriter, err error) { if errors.Is(err, templates.ErrNotFound) { writeJSON(w, 404, map[string]string{"error": "模板不存在"}) return } if errors.Is(err, templates.ErrInvalidTemplate) { writeJSON(w, 400, map[string]string{"error": strings.TrimPrefix(err.Error(), templates.ErrInvalidTemplate.Error()+": ")}) return } writeMiscError(w, 500) } func writeMiscAuth(w http.ResponseWriter, err error) { var auth *PlatformAuthError if errors.As(err, &auth) { writeJSON(w, auth.Status, map[string]string{"error": auth.Message}) return } writeMiscError(w, 500) } func writePublicMiscError(w http.ResponseWriter, err error) { var auth *publicapi.AuthError if errors.As(err, &auth) { writeJSON(w, auth.Status, map[string]string{"error": auth.Message}) return } writeMiscError(w, 500) } func writeMiscError(w http.ResponseWriter, status int) { writeJSON(w, status, map[string]string{"error": "服务器内部错误。"}) } func writeMiscMethod(w http.ResponseWriter, allow string) { w.Header().Set("Allow", allow) writeJSON(w, 405, map[string]string{"error": "Method Not Allowed"}) } func allowedTemplateMethods(collection bool) string { if collection { return "GET, POST" } return "PATCH, DELETE" } func miscRequestOrigin(r *http.Request) string { scheme := "http" if r.TLS != nil { scheme = "https" } if forwarded := strings.TrimSpace(strings.Split(r.Header.Get("x-forwarded-proto"), ",")[0]); forwarded == "http" || forwarded == "https" { scheme = forwarded } host := r.Host if parsed, err := url.Parse(scheme + "://" + host); err == nil { if parsed.Hostname() == "0.0.0.0" { parsed.Host = strings.Replace(parsed.Host, "0.0.0.0", "127.0.0.1", 1) } return parsed.Scheme + "://" + parsed.Host } return scheme + "://" + host } func normalizeMiscPublicOrigin(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } parsed, err := url.Parse(value) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return strings.TrimSuffix(value, "/") } if parsed.Hostname() == "0.0.0.0" { parsed.Host = strings.Replace(parsed.Host, "0.0.0.0", "127.0.0.1", 1) } return parsed.Scheme + "://" + parsed.Host }