Files

337 lines
9.5 KiB
Go

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
}