package api import ( "net/http" "github.com/gin-gonic/gin" "gitea.stevedudenhoeffer.com/steve/pansy/internal/domain" ) // Change history and undo (#48). Two endpoints: read a garden's change sets, and // revert one. Both encode domain types directly, like the rest of the package — // domain.ChangeSet already carries the actor name, revert linkage and per-op // counts the history list renders, and its Revisions field is omitempty so the // JSON snapshots never ride along on a list response. // historyResponse is the body of GET /gardens/:id/history. hasMore lets the // client page without a separate count query over a table that only grows. type historyResponse struct { ChangeSets []domain.ChangeSet `json:"changeSets"` HasMore bool `json:"hasMore"` } // revertResponse is the body of POST /change-sets/:id/revert on every path. // Carrying both fields regardless is what lets the UI say "2 of 3 changes undone; // the north bed was edited since and was left alone" instead of a generic // failure — a partial revert really did change things, and pretending otherwise // would be a lie. ChangeSet is null when nothing needed reverting. type revertResponse struct { ChangeSet *domain.ChangeSet `json:"changeSet"` Conflicts []domain.RevertConflict `json:"conflicts"` } func (h *handlers) getGardenHistory(c *gin.Context) { gardenID, ok := parseIDParam(c, "id") if !ok { return } // 0 means "the service's default"; it clamps the upper bound too. limit := intQuery(c, "limit", 0) offset := intQuery(c, "offset", 0) sets, hasMore, err := h.svc.GardenHistory(c.Request.Context(), mustActor(c).ID, gardenID, limit, offset) if err != nil { writeServiceError(c, err) return } c.JSON(http.StatusOK, historyResponse{ChangeSets: sets, HasMore: hasMore}) } func (h *handlers) revertChangeSet(c *gin.Context) { id, ok := parseIDParam(c, "id") if !ok { return } // A revert through the REST API is a person clicking undo. The agent reverts // its own work through the service directly and stamps SourceAgent, so the // history badge can tell the two apart. cs, conflicts, err := h.svc.RevertChangeSet(c.Request.Context(), mustActor(c).ID, id, domain.SourceUI) if err != nil { writeServiceError(c, err) return } if conflicts == nil { conflicts = []domain.RevertConflict{} } body := revertResponse{ChangeSet: cs, Conflicts: conflicts} switch { case len(conflicts) > 0: // 409 even when part of the revert applied: something the caller asked for // did not happen, and the body says exactly what. c.JSON(http.StatusConflict, body) case cs == nil: // Every revision resolved to a no-op (already undone by hand, say). Nothing // was created, so 200 rather than a 201 pointing at nothing. c.JSON(http.StatusOK, body) default: c.JSON(http.StatusCreated, body) } }