125 lines
3.9 KiB
Go
125 lines
3.9 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs"
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/layercompositions"
|
|
)
|
|
|
|
type LayerCompositionsConfig struct {
|
|
MaxJSONBytes int64
|
|
}
|
|
|
|
type layerCompositionsHandler struct {
|
|
platform *PlatformAuthorizer
|
|
jobs *jobs.Service
|
|
compositions *layercompositions.Service
|
|
maxJSONBytes int64
|
|
}
|
|
|
|
func NewLayerCompositionsHandler(platform *PlatformAuthorizer, jobService *jobs.Service, compositions *layercompositions.Service, config LayerCompositionsConfig) (http.Handler, error) {
|
|
if platform == nil || jobService == nil || compositions == nil {
|
|
return nil, errors.New("layer composition HTTP dependencies are not configured")
|
|
}
|
|
if config.MaxJSONBytes <= 0 {
|
|
config.MaxJSONBytes = 1 << 20
|
|
}
|
|
return &layerCompositionsHandler{platform: platform, jobs: jobService, compositions: compositions, maxJSONBytes: config.MaxJSONBytes}, nil
|
|
}
|
|
|
|
func (handler *layerCompositionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
jobID, matched := matchLayerCompositionRoute(r.URL.Path)
|
|
if !matched {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if r.Method == http.MethodOptions {
|
|
w.Header().Set("Allow", "GET, PUT")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet && r.Method != http.MethodPut {
|
|
w.Header().Set("Allow", "GET, PUT")
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
session, err := handler.platform.Authorize(r, PlatformApp)
|
|
if err != nil {
|
|
writeLayerCompositionError(w, err)
|
|
return
|
|
}
|
|
job, err := handler.jobs.Get(r.Context(), jobs.Scope{OwnerID: session.User.ID}, jobID)
|
|
if err != nil || job.Capability != "image.generate" {
|
|
writeJSON(w, http.StatusNotFound, map[string]string{"error": "图层任务不存在"})
|
|
return
|
|
}
|
|
if r.Method == http.MethodGet {
|
|
composition, found, err := handler.compositions.Get(r.Context(), session.User.ID, jobID)
|
|
if err != nil {
|
|
writeLayerCompositionError(w, err)
|
|
return
|
|
}
|
|
if !found {
|
|
writeJSON(w, http.StatusOK, map[string]any{"composition": nil})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"composition": composition})
|
|
return
|
|
}
|
|
input := struct {
|
|
Version int64 `json:"version"`
|
|
BaseAssetID string `json:"baseAssetId"`
|
|
BaseVisible bool `json:"baseVisible"`
|
|
Layers []layercompositions.Layer `json:"layers"`
|
|
}{}
|
|
if !decodeJobJSON(w, r, handler.maxJSONBytes, &input, false) {
|
|
return
|
|
}
|
|
if !containsString(job.OutputAssetIDs, input.BaseAssetID) {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "底图必须来自当前图层拆分任务"})
|
|
return
|
|
}
|
|
saved, err := handler.compositions.Save(r.Context(), session.User.ID, jobID, input.Version, layercompositions.Composition{
|
|
BaseAssetID: input.BaseAssetID,
|
|
BaseVisible: input.BaseVisible,
|
|
Layers: input.Layers,
|
|
})
|
|
if err != nil {
|
|
writeLayerCompositionError(w, err)
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]any{"composition": saved})
|
|
}
|
|
|
|
func matchLayerCompositionRoute(path string) (string, bool) {
|
|
const prefix = "/api/layer-compositions/"
|
|
if !strings.HasPrefix(path, prefix) {
|
|
return "", false
|
|
}
|
|
jobID := strings.TrimPrefix(path, prefix)
|
|
return jobID, jobID != "" && !strings.Contains(jobID, "/")
|
|
}
|
|
|
|
func writeLayerCompositionError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, layercompositions.ErrConflict):
|
|
writeJSON(w, http.StatusConflict, map[string]string{"error": "图层工程已在其他页面更新,请重试"})
|
|
case errors.Is(err, layercompositions.ErrInvalid), errors.Is(err, layercompositions.ErrAssetNotFound):
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
default:
|
|
writeJobError(w, err, false)
|
|
}
|
|
}
|
|
|
|
func containsString(values []string, wanted string) bool {
|
|
for _, value := range values {
|
|
if value == wanted {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|