nsfwapp/backend/models_api.go
2025-12-19 17:52:14 +01:00

192 lines
5.2 KiB
Go

package main
import (
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
)
// ✅ umbenannt, damit es nicht mit models.go kollidiert
func modelsWriteJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func modelsReadJSON(r *http.Request, v any) error {
if r.Body == nil {
return errors.New("missing body")
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(v)
}
type parseReq struct {
Input string `json:"input"`
}
func parseModelFromURL(raw string) (ParsedModelDTO, error) {
in := strings.TrimSpace(raw)
if in == "" {
return ParsedModelDTO{}, errors.New("Bitte eine URL eingeben.")
}
// scheme ergänzen, falls User "chaturbate.com/xyz" eingibt
if !strings.Contains(in, "://") {
in = "https://" + in
}
u, err := url.Parse(in)
if err != nil || u.Scheme == "" || u.Hostname() == "" {
return ParsedModelDTO{}, errors.New("Ungültige URL.")
}
host := strings.ToLower(u.Hostname())
host = strings.TrimPrefix(host, "www.")
// ModelKey aus Pfad/Fragment ableiten
path := strings.Trim(u.Path, "/")
segs := strings.Split(path, "/")
skip := map[string]bool{
"models": true, "model": true, "profile": true, "users": true, "user": true,
}
var key string
for _, s := range segs {
s = strings.TrimSpace(s)
if s == "" || skip[strings.ToLower(s)] {
continue
}
key = s
break
}
if key == "" && strings.TrimSpace(u.Fragment) != "" {
key = strings.Trim(strings.TrimSpace(u.Fragment), "/")
}
if key == "" {
return ParsedModelDTO{}, errors.New("Konnte keinen Modelnamen aus der URL ableiten.")
}
// URL-decode + kleines Sanitizing
if dec, err := url.PathUnescape(key); err == nil {
key = dec
}
key = strings.TrimPrefix(strings.TrimSpace(key), "@")
key = strings.Map(func(r rune) rune {
switch {
case r >= 'a' && r <= 'z':
return r
case r >= 'A' && r <= 'Z':
return r
case r >= '0' && r <= '9':
return r
case r == '_' || r == '-' || r == '.':
return r
default:
return -1
}
}, key)
if key == "" {
return ParsedModelDTO{}, errors.New("Ungültiger Modelname in URL.")
}
return ParsedModelDTO{
Input: u.String(), // ✅ speicherst du als URL
IsURL: true,
Host: host,
Path: u.Path,
ModelKey: key, // ✅ kommt IMMER aus URL
}, nil
}
func RegisterModelAPI(mux *http.ServeMux, store *ModelStore) {
// ✅ NEU: Parse-Endpoint (nur URL erlaubt)
mux.HandleFunc("/api/models/parse", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
modelsWriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
var req parseReq
if err := modelsReadJSON(r, &req); err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
dto, err := parseModelFromURL(req.Input)
if err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
modelsWriteJSON(w, http.StatusOK, dto)
})
mux.HandleFunc("/api/models/list", func(w http.ResponseWriter, r *http.Request) {
modelsWriteJSON(w, http.StatusOK, store.List())
})
mux.HandleFunc("/api/models/upsert", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
modelsWriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
var req ParsedModelDTO
if err := modelsReadJSON(r, &req); err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
// ✅ Server-seitig: nur URL akzeptieren (wird zusätzlich im Store geprüft)
if !req.IsURL {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": "Nur URL erlaubt."})
return
}
m, err := store.UpsertFromParsed(req)
if err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
modelsWriteJSON(w, http.StatusOK, m)
})
mux.HandleFunc("/api/models/flags", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
modelsWriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
var req ModelFlagsPatch
if err := modelsReadJSON(r, &req); err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
m, err := store.PatchFlags(req)
if err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
modelsWriteJSON(w, http.StatusOK, m)
})
mux.HandleFunc("/api/models/delete", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
modelsWriteJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
var req struct {
ID string `json:"id"`
}
if err := modelsReadJSON(r, &req); err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
if err := store.Delete(req.ID); err != nil {
modelsWriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
modelsWriteJSON(w, http.StatusOK, map[string]any{"ok": true})
})
}