Files

127 lines
4.3 KiB
Go

package dnf90
import (
"encoding/json"
"io"
"net/http"
"strings"
appkit "longheng.io/server/internal/platform/app"
httpx "longheng.io/server/internal/platform/httpx"
"longheng.io/server/internal/services/dnfbridge"
)
// companionPanelAdminPath is the local-only endpoint used by the GM tool's
// "支援兵属性" page. It is intentionally unauthenticated: the admin mux binds
// to loopback, like the existing /local endpoints, and the page is served by
// the local GM tool.
const companionPanelAdminPath = "/local/companion-panel"
func registerCompanionPanelAdmin(env *appkit.Env, bridge *dnfbridge.Service) {
if env == nil || env.AdminMux == nil || bridge == nil {
return
}
env.AdminMux.HandleFunc(companionPanelAdminPath, func(w http.ResponseWriter, r *http.Request) {
applyCompanionPanelCORS(w)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
switch r.Method {
case http.MethodGet:
characterID := strings.TrimSpace(r.URL.Query().Get("character_id"))
if characterID == "" {
characterID = strings.TrimSpace(r.URL.Query().Get("characterId"))
}
values, found, err := bridge.CompanionPanelValues(r.Context(), characterID)
if err != nil {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"character_id": characterID,
"found": found,
"values": values,
})
case http.MethodPost:
var body struct {
CharacterID string `json:"character_id"`
Values map[string]string `json:"values"`
}
decoder := json.NewDecoder(io.LimitReader(r.Body, 1<<20))
if err := decoder.Decode(&body); err != nil {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
if strings.TrimSpace(body.CharacterID) == "" {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{
"error": "character_id is required",
})
return
}
if err := bridge.SaveCompanionPanelValues(r.Context(), body.CharacterID, body.Values); err != nil {
httpx.WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
values, found, _ := bridge.CompanionPanelValues(r.Context(), body.CharacterID)
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"ok": true,
"character_id": body.CharacterID,
"found": found,
"values": values,
})
default:
w.Header().Set("Allow", "GET, POST, OPTIONS")
httpx.WriteJSON(w, http.StatusMethodNotAllowed, map[string]string{
"error": "method not allowed",
})
}
})
// Base-estimate generation: PVF growth + worn equipment + weapon forging.
// Enchant cards, amplification, reinforcement, quality, sets, titles, auras
// and pets are intentionally not modelled; the GM page marks the result as
// an estimate and lets the operator correct it.
env.AdminMux.HandleFunc(companionPanelGeneratePath, func(w http.ResponseWriter, r *http.Request) {
applyCompanionPanelCORS(w)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
if r.Method != http.MethodPost {
w.Header().Set("Allow", "POST, OPTIONS")
httpx.WriteJSON(w, http.StatusMethodNotAllowed, map[string]string{
"error": "method not allowed",
})
return
}
var body struct {
CharacterID string `json:"character_id"`
}
if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&body); err != nil {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
values, err := bridge.GenerateCompanionPanelSnapshot(r.Context(), body.CharacterID)
if err != nil {
httpx.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
httpx.WriteJSON(w, http.StatusOK, map[string]any{
"ok": true,
"character_id": body.CharacterID,
"estimate": true,
"values": values,
})
})
}
const companionPanelGeneratePath = "/local/companion-panel/generate"
func applyCompanionPanelCORS(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Admin-Token")
w.Header().Set("Access-Control-Allow-Private-Network", "true")
}