mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 16:02:02 +00:00
152 lines
4.2 KiB
Go
152 lines
4.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/api/middleware"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
func (h *Handler) CreateSequence(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.Handle(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
|
|
id := c.Param("id")
|
|
|
|
// The body is optional: the same fields as PATCH, applied to the new step.
|
|
var data models.UpdateSequence
|
|
if err := c.ShouldBindJSON(&data); err != nil && !errors.Is(err, io.EOF) {
|
|
errx.Handle(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
|
|
resp, err := h.SequenceService.Create(c.Request.Context(), orgID.String(), id)
|
|
if err != nil {
|
|
errx.Handle(c, err)
|
|
return
|
|
}
|
|
if data != (models.UpdateSequence{}) {
|
|
updated, uerr := h.SequenceService.Update(c.Request.Context(), orgID.String(), id, resp.ID.String(), &data)
|
|
if uerr != nil {
|
|
// A refused body leaves no blank step behind.
|
|
_ = h.SequenceService.Delete(c.Request.Context(), orgID.String(), id, resp.ID.String())
|
|
errx.Handle(c, uerr)
|
|
return
|
|
}
|
|
resp = updated
|
|
}
|
|
|
|
h.auditOrg(c, models.AuditActionCreate, models.AuditEntitySequence, &resp.ID, nil, map[string]string{"campaign_id": id})
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
func (h *Handler) GetSequences(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.Handle(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
|
|
id := c.Param("id")
|
|
|
|
resp, err := h.SequenceService.Get(c.Request.Context(), orgID.String(), id)
|
|
if err != nil {
|
|
errx.Handle(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
func (h *Handler) UpdateSequence(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.Handle(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
|
|
id := c.Param("id")
|
|
sequenceID := c.Param("sid")
|
|
|
|
var data models.UpdateSequence
|
|
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
errx.Handle(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
|
|
resp, err := h.SequenceService.Update(c.Request.Context(), orgID.String(), id, sequenceID, &data)
|
|
if err != nil {
|
|
errx.Handle(c, err)
|
|
return
|
|
}
|
|
|
|
h.auditOrg(c, models.AuditActionUpdate, models.AuditEntitySequence, &resp.ID, nil, map[string]string{"campaign_id": id})
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// PatchSequenceLayout persists only the canvas coordinates of a campaign's
|
|
// steps so a teammate's arrangement "sticks" across visits. Like the automation
|
|
// layout endpoint it is written continuously as steps are dragged (the live
|
|
// cursor/drag stream is the realtime half), so it is deliberately NOT audited
|
|
// and does not bump updated_at: a reposition is cosmetic and must not spam the
|
|
// audit log or read as a content change. Retries are naturally safe (positions
|
|
// are last-write-wins), so no Idempotency-Key is required.
|
|
func (h *Handler) PatchSequenceLayout(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.Handle(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
id := c.Param("id")
|
|
|
|
var data models.SequenceLayout
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
errx.Handle(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
if len(data.Positions) > 1000 {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "too many positions"))
|
|
return
|
|
}
|
|
if err := h.SequenceService.UpdateLayout(c.Request.Context(), orgID.String(), id, data.Positions); err != nil {
|
|
errx.Handle(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
func (h *Handler) DeleteSequence(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.Handle(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
|
|
id := c.Param("id")
|
|
sequenceID := c.Param("sid")
|
|
|
|
if err := h.SequenceService.Delete(c.Request.Context(), orgID.String(), id, sequenceID); err != nil {
|
|
errx.Handle(c, err)
|
|
return
|
|
}
|
|
|
|
if sid, perr := uuid.Parse(sequenceID); perr == nil {
|
|
h.auditOrg(c, models.AuditActionDelete, models.AuditEntitySequence, &sid, nil, map[string]string{"campaign_id": id})
|
|
} else {
|
|
h.auditOrg(c, models.AuditActionDelete, models.AuditEntitySequence, nil, nil, map[string]string{"campaign_id": id})
|
|
}
|
|
|
|
c.Status(http.StatusOK)
|
|
}
|