337 lines
9.5 KiB
Go
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
|
|
}
|