package api import ( "encoding/json" "errors" "net/http" "strings" "git.fabledsword.com/bvandeusen/minstrel/internal/apierror" "git.fabledsword.com/bvandeusen/minstrel/internal/backup" ) // GET /api/admin/backups — the backup's verdict, settings and recent runs. func (h *handlers) handleGetBackups(w http.ResponseWriter, r *http.Request) { st, err := h.backups.Status(r.Context()) if err != nil { writeErrWithLog(w, h.logger, "admin backups: status", apierror.Internal(err)) return } writeJSON(w, http.StatusOK, st) } func decodeBackupUpdate(w http.ResponseWriter, r *http.Request) (backup.Update, bool) { var u backup.Update if err := json.NewDecoder(r.Body).Decode(&u); err != nil { writeErr(w, apierror.BadRequest("invalid_body", "")) return u, false } return u, true } // fieldErr writes a setting that can't be kept as a 400 whose message says // which setting and why, and reports whether err was one. func fieldErr(w http.ResponseWriter, err error) bool { var fe *backup.FieldError if !errors.As(err, &fe) { return false } writeErr(w, apierror.BadRequest("invalid_backup_setting", fe.Message)) return true } // PUT /api/admin/backups — save the settings. An empty secret_key keeps the // stored one. Returns the new status. func (h *handlers) handleUpdateBackups(w http.ResponseWriter, r *http.Request) { u, ok := decodeBackupUpdate(w, r) if !ok { return } if err := h.backups.Save(r.Context(), u); err != nil { if !fieldErr(w, err) { writeErrWithLog(w, h.logger, "admin backups: save", apierror.Internal(err)) } return } h.handleGetBackups(w, r) } // POST /api/admin/backups/test — check the destination in the body without // saving it, by listing the backups already there. Nothing is written. func (h *handlers) handleTestBackups(w http.ResponseWriter, r *http.Request) { u, ok := decodeBackupUpdate(w, r) if !ok { return } res, err := h.backups.Test(r.Context(), u) if err != nil { if fieldErr(w, err) { return } msg := strings.TrimPrefix(err.Error(), "backup: ") writeErr(w, &apierror.Error{Status: http.StatusBadGateway, Code: "destination_failed", Message: msg}) return } writeJSON(w, http.StatusOK, res) } // POST /api/admin/backups/run — start a backup now. 202 once it has started; // 409 while one runs or before the destination is set up. func (h *handlers) handleRunBackup(w http.ResponseWriter, r *http.Request) { err := h.backups.Start(r.Context(), backup.TriggerManual) switch { case err == nil: w.WriteHeader(http.StatusAccepted) case errors.Is(err, backup.ErrRunning): writeErr(w, apierror.Conflict("backup_running", "A backup is already running.")) case errors.Is(err, backup.ErrNotReady): writeErr(w, apierror.Conflict("backup_not_ready", "Set up where backups go first.")) case errors.Is(err, backup.ErrClosed): writeErr(w, apierror.Conflict("shutting_down", "The server is shutting down.")) default: writeErrWithLog(w, h.logger, "admin backups: start", apierror.Internal(err)) } }