feat: complete remaining Go backend modules
This commit is contained in:
336
backend/internal/httpapi/misc.go
Normal file
336
backend/internal/httpapi/misc.go
Normal file
@@ -0,0 +1,336 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user