zlh-agent/internal/handlers/backups.go
2026-04-16 18:53:08 +00:00

149 lines
3.5 KiB
Go

package handlers
import (
"encoding/json"
"errors"
"net/http"
"os"
"strings"
agentbackup "zlh-agent/internal/backup"
"zlh-agent/internal/state"
)
var loadBackupConfig = state.LoadConfig
var restoreBackup = agentbackup.Restore
func HandleGameBackups(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
handleGameBackupsList(w, r)
case http.MethodPost:
handleGameBackupCreate(w, r)
default:
writeJSONError(w, http.StatusMethodNotAllowed, "GET or POST only")
}
}
func HandleGameBackupByID(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
writeJSONError(w, http.StatusMethodNotAllowed, "DELETE only")
return
}
endOp, ok := beginHandlerOperation(w, "backup_delete", true, "deleting backup")
if !ok {
return
}
defer endOp()
if _, ok := requireBackupConfig(w); !ok {
return
}
id := strings.TrimPrefix(r.URL.Path, "/game/backups/")
id = strings.TrimSpace(strings.Trim(id, "/"))
if id == "" || strings.Contains(id, "/") {
writeJSONError(w, http.StatusBadRequest, "backup id required")
return
}
if err := agentbackup.Delete(id); err != nil {
if errors.Is(err, os.ErrNotExist) {
writeJSONError(w, http.StatusNotFound, "backup not found")
return
}
writeJSONError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{"deleted": true, "id": id})
}
func handleGameBackupsList(w http.ResponseWriter, r *http.Request) {
if _, ok := requireBackupConfig(w); !ok {
return
}
backups, err := agentbackup.List()
if err != nil {
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{"backups": backups})
}
func handleGameBackupCreate(w http.ResponseWriter, r *http.Request) {
endOp, ok := beginHandlerOperation(w, "backup_create", true, "creating backup")
if !ok {
return
}
defer endOp()
cfg, ok := requireBackupConfig(w)
if !ok {
return
}
manifest, err := agentbackup.Create(cfg)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, manifest)
}
func HandleGameBackupRestore(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
writeJSONError(w, http.StatusMethodNotAllowed, "POST only")
return
}
endOp, ok := beginHandlerOperation(w, "backup_restore", true, "restoring backup")
if !ok {
return
}
defer endOp()
cfg, ok := requireBackupConfig(w)
if !ok {
return
}
id := strings.TrimSpace(r.URL.Query().Get("id"))
if id == "" {
var req struct {
ID string `json:"id"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
id = strings.TrimSpace(req.ID)
}
if id == "" {
writeJSONError(w, http.StatusBadRequest, "backup id required")
return
}
result, err := restoreBackup(cfg, id)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]any{
"restored": true,
"backup": result.Backup,
"checkpoint": result.Checkpoint,
})
}
func requireBackupConfig(w http.ResponseWriter) (*state.Config, bool) {
cfg, err := loadBackupConfig()
if err != nil {
writeJSONError(w, http.StatusBadRequest, "no config loaded")
return nil, false
}
if !strings.EqualFold(cfg.ContainerType, "game") {
writeJSONError(w, http.StatusBadRequest, "not a game container")
return nil, false
}
if !strings.EqualFold(cfg.Game, "minecraft") {
writeJSONError(w, http.StatusNotImplemented, "backups are only implemented for minecraft")
return nil, false
}
return cfg, true
}