Files

104 lines
3.3 KiB
Go
Raw Permalink Normal View History

package sdkserver
import (
"encoding/json"
"fmt"
"os"
"gac-hkrpg-go/internal/common"
)
// POST patches fields; PUT explicitly replaces a complete player document.
// Keep raw JSON values so unrelated IDs and numeric values are not rounded.
func preparePlayerDataUpdate(existing, payload []byte, replace bool) ([]byte, error) {
var patch map[string]json.RawMessage
if err := json.Unmarshal(payload, &patch); err != nil || patch == nil {
return nil, fmt.Errorf("player data must be a JSON object")
}
result := make(map[string]json.RawMessage)
if replace {
for _, key := range []string{"avatars", "lightcones", "relics", "battle_config"} {
if _, present := patch[key]; !present {
return nil, fmt.Errorf("full replacement is missing %s; use POST for a partial update", key)
}
}
// FreeSR exports do not necessarily include the inventory editor's
// material overrides. Omission preserves them; an explicit materials
// object (including zero counts or {}) still replaces that field.
if _, present := patch["materials"]; !present && len(existing) != 0 {
var current map[string]json.RawMessage
if err := json.Unmarshal(existing, &current); err != nil || current == nil {
return nil, fmt.Errorf("existing player data is invalid; restore it before applying updates")
}
if value, present := current["materials"]; present {
result["materials"] = value
}
}
} else if err := json.Unmarshal(existing, &result); err != nil || result == nil {
return nil, fmt.Errorf("existing player data is invalid; restore it before applying updates")
}
for key, value := range patch {
if key == "avatars" && !replace {
var incoming map[string]json.RawMessage
if err := json.Unmarshal(value, &incoming); err != nil || len(incoming) == 0 {
return nil, fmt.Errorf("avatars update must be a non-empty object")
}
current := make(map[string]json.RawMessage)
if raw, found := result[key]; found {
if err := json.Unmarshal(raw, &current); err != nil || current == nil {
return nil, fmt.Errorf("existing avatars data is invalid")
}
}
for id, avatar := range incoming {
current[id] = avatar
}
merged, err := json.Marshal(current)
if err != nil {
return nil, err
}
result[key] = merged
} else {
result[key] = value
}
}
out, err := json.MarshalIndent(result, "", " ")
if err != nil {
return nil, err
}
var data common.FreesrData
if err := json.Unmarshal(out, &data); err != nil {
return nil, fmt.Errorf("invalid player data: %w", err)
}
if err := common.ValidateAvatarRoster(data.Avatars); err != nil {
return nil, err
}
if saved, err := common.ReadPersistent(); err == nil {
if err := common.ValidateAdventureLineup(data.Avatars, saved.Lineups); err != nil {
return nil, err
}
} else if !os.IsNotExist(err) {
return nil, fmt.Errorf("cannot validate active lineup: %w", err)
}
return out, nil
}
func writePlayerDataAtomic(data []byte) error {
file, err := os.CreateTemp(".", ".gac-freesr-*.tmp")
if err != nil {
return err
}
name := file.Name()
defer os.Remove(name)
if _, err := file.Write(data); err != nil {
file.Close()
return err
}
if err := file.Sync(); err != nil {
file.Close()
return err
}
if err := file.Close(); err != nil {
return err
}
return os.Rename(name, "freesr-data.json")
}