package backupapi import ( "context" "encoding/json" "errors" "net/http" "time" "github.com/itworx/pulse/internal/auth" "github.com/itworx/pulse/internal/backup" ) type Handler struct { Manager *backup.Manager Audit func(context.Context, string, string) error // OnCreated invalidates derived status caches after the archive and its // checksum have both been written successfully. OnCreated func() } type publicResult struct { BackupID string `json:"backupId"` SHA256 string `json:"sha256"` Bytes int64 `json:"bytes"` Rows int64 `json:"rows"` Created time.Time `json:"createdAt"` } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h.Manager == nil { writeError(w, http.StatusServiceUnavailable, "BACKUP_UNAVAILABLE", "Backup is not configured") return } switch r.Method { case http.MethodGet: if r.URL.Path != "/api/v1/system/backups" { writeError(w, http.StatusNotFound, "NOT_FOUND", "Not found") return } result, err := h.Manager.List(r.Context()) if err != nil { writeError(w, http.StatusServiceUnavailable, "BACKUP_UNAVAILABLE", "Backups are not available") return } public := make([]publicResult, 0, len(result)) for _, item := range result { public = append(public, toPublic(item)) } writeJSON(w, http.StatusOK, map[string]any{"backups": public}) case http.MethodPost: if r.URL.Path != "/api/v1/system/backups" { writeError(w, http.StatusNotFound, "NOT_FOUND", "Not found") return } result, err := h.Manager.Create(r.Context()) principal, _ := auth.PrincipalFromContext(r.Context()) if err != nil { if h.Audit != nil { _ = h.Audit(r.Context(), principal.Subject, "failure") } status := http.StatusInternalServerError code := "BACKUP_FAILED" if errors.Is(err, backup.ErrNotConfigured) { status = http.StatusServiceUnavailable code = "BACKUP_UNAVAILABLE" } writeError(w, status, code, "Backup could not be created") return } if h.Audit != nil { _ = h.Audit(r.Context(), principal.Subject, "success") } if h.OnCreated != nil { h.OnCreated() } writeJSON(w, http.StatusCreated, toPublic(result)) default: writeError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "Method not allowed") } } func toPublic(result backup.Result) publicResult { return publicResult{BackupID: result.BackupID, SHA256: result.SHA256, Bytes: result.Bytes, Rows: result.Rows, Created: result.Created} } func writeJSON(w http.ResponseWriter, status int, value any) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "private, no-store") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(value) } func writeError(w http.ResponseWriter, status int, code, detail string) { writeJSON(w, status, map[string]string{"code": code, "detail": detail}) } var _ http.Handler = Handler{}