mirror of
https://github.com/warmbly/warmbly.git
synced 2026-10-03 16:02:02 +00:00
636 lines
17 KiB
Go
636 lines
17 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/warmbly/warmbly/internal/api/middleware"
|
|
"github.com/warmbly/warmbly/internal/app/placement"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
func (h *Handler) placementReady(c *gin.Context) bool {
|
|
if h.PlacementService == nil {
|
|
errx.JSON(c, errx.New(errx.NotImplemented, "placement testing is not configured"))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// placementLimit parses ?limit into [1, 100], 400 on anything else.
|
|
func placementLimit(c *gin.Context) (int, bool) {
|
|
raw := c.Query("limit")
|
|
if raw == "" {
|
|
return 25, true
|
|
}
|
|
n, err := strconv.Atoi(raw)
|
|
if err != nil || n < 1 || n > 100 {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid limit"))
|
|
return 0, false
|
|
}
|
|
return n, true
|
|
}
|
|
|
|
func optionalUUID(c *gin.Context, raw, field string) (*uuid.UUID, bool) {
|
|
if raw == "" {
|
|
return nil, true
|
|
}
|
|
id, err := uuid.Parse(raw)
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid "+field))
|
|
return nil, false
|
|
}
|
|
return &id, true
|
|
}
|
|
|
|
// --- Workspace ----------------------------------------------------------
|
|
|
|
// GetPlacementOverview lists the seed panels this workspace can test on and
|
|
// its monthly allowance.
|
|
func (h *Handler) GetPlacementOverview(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
out, xerr := h.PlacementService.Overview(c.Request.Context(), *orgID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": out})
|
|
}
|
|
|
|
type createPlacementTestRequest struct {
|
|
SenderAccountID string `json:"sender_account_id"`
|
|
CampaignID string `json:"campaign_id"`
|
|
SequenceID string `json:"sequence_id"`
|
|
ContactID string `json:"contact_id"`
|
|
Subject string `json:"subject"`
|
|
BodyHTML string `json:"body_html"`
|
|
BodyPlain string `json:"body_plain"`
|
|
Tracking string `json:"tracking"`
|
|
Panel string `json:"panel"`
|
|
SeedIDs []uuid.UUID `json:"seed_ids"`
|
|
Families []string `json:"families"`
|
|
Pace string `json:"pace"`
|
|
MaxCredits int `json:"max_credits"`
|
|
}
|
|
|
|
// CreatePlacementTest starts a test: one test, or two for a tracking
|
|
// comparison.
|
|
func (h *Handler) CreatePlacementTest(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
var req createPlacementTestRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
senderID, err := uuid.Parse(req.SenderAccountID)
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid sender_account_id"))
|
|
return
|
|
}
|
|
if !middleware.APIKeyAllowsEmailAccount(c, senderID) {
|
|
errx.JSON(c, errx.New(errx.Forbidden, "this API key cannot send from that mailbox"))
|
|
return
|
|
}
|
|
campaignID, ok := optionalUUID(c, req.CampaignID, "campaign_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
sequenceID, ok := optionalUUID(c, req.SequenceID, "sequence_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
contactID, ok := optionalUUID(c, req.ContactID, "contact_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
var userID *uuid.UUID
|
|
if id, err := middleware.GetUserUUID(c); err == nil && id != uuid.Nil {
|
|
userID = &id
|
|
}
|
|
|
|
tests, xerr := h.PlacementService.CreateTests(c.Request.Context(), placement.CreateInput{
|
|
OrgID: *orgID,
|
|
UserID: userID,
|
|
SenderAccountID: senderID,
|
|
CampaignID: campaignID,
|
|
SequenceID: sequenceID,
|
|
ContactID: contactID,
|
|
Subject: req.Subject,
|
|
BodyHTML: req.BodyHTML,
|
|
BodyPlain: req.BodyPlain,
|
|
Tracking: req.Tracking,
|
|
Panel: req.Panel,
|
|
SeedIDs: req.SeedIDs,
|
|
Families: req.Families,
|
|
Pace: req.Pace,
|
|
MaxCredits: req.MaxCredits,
|
|
Origin: models.PlacementOriginManual,
|
|
})
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
for _, t := range tests {
|
|
id := t.ID
|
|
h.auditOrg(c, models.AuditActionCreate, models.AuditEntityPlacementTest, &id, nil, map[string]string{
|
|
"sender": t.SenderEmail,
|
|
"panel": t.Panel,
|
|
})
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"data": tests})
|
|
}
|
|
|
|
// ListPlacementTests lists the workspace's tests, newest first.
|
|
func (h *Handler) ListPlacementTests(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
offset, ok := decodeOffsetCursor(c.Query("cursor"))
|
|
if !ok {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid cursor"))
|
|
return
|
|
}
|
|
limit, ok := placementLimit(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
campaignID, ok := optionalUUID(c, c.Query("campaign_id"), "campaign_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
tests, total, xerr := h.PlacementService.ListTests(c.Request.Context(), orgID, campaignID, limit, offset)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": tests, "pagination": pageMetaFor(offset, limit, len(tests), total)})
|
|
}
|
|
|
|
// GetPlacementTest returns one test with every copy, the content check and,
|
|
// for a tracking comparison, the other half.
|
|
func (h *Handler) GetPlacementTest(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
detail, xerr := h.PlacementService.GetTest(c.Request.Context(), orgID, id)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": detail})
|
|
}
|
|
|
|
// CancelPlacementTest stops the copies that have not been sent yet.
|
|
func (h *Handler) CancelPlacementTest(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
view, xerr := h.PlacementService.CancelTest(c.Request.Context(), *orgID, id)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.auditOrg(c, models.AuditActionStop, models.AuditEntityPlacementTest, &id, nil, nil)
|
|
c.JSON(http.StatusOK, gin.H{"data": view})
|
|
}
|
|
|
|
// ListPlacementSeeds lists the workspace's mailboxes and which are its seed
|
|
// inboxes.
|
|
func (h *Handler) ListPlacementSeeds(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
seeds, xerr := h.PlacementService.ListWorkspaceSeeds(c.Request.Context(), *orgID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
allowed := seeds[:0]
|
|
for _, s := range seeds {
|
|
if middleware.APIKeyAllowsEmailAccount(c, s.EmailAccountID) {
|
|
allowed = append(allowed, s)
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": allowed})
|
|
}
|
|
|
|
type setPlacementSeedRequest struct {
|
|
Seed *bool `json:"seed"`
|
|
}
|
|
|
|
// SetPlacementSeed makes a workspace mailbox a seed inbox, or not.
|
|
func (h *Handler) SetPlacementSeed(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
var req setPlacementSeedRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
if req.Seed == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "seed is required"))
|
|
return
|
|
}
|
|
seed, xerr := h.PlacementService.SetWorkspaceSeed(c.Request.Context(), *orgID, id, *req.Seed)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.auditOrg(c, models.AuditActionUpdate, models.AuditEntityEmailAccount, &id, map[string]string{
|
|
"placement_seed": strconv.FormatBool(*req.Seed),
|
|
}, map[string]string{"email": seed.Email})
|
|
c.JSON(http.StatusOK, gin.H{"data": seed})
|
|
}
|
|
|
|
// GetPlacementMonitor returns a campaign's scheduled placement test, null when
|
|
// it has none.
|
|
func (h *Handler) GetPlacementMonitor(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
campaignID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
m, xerr := h.PlacementService.GetMonitor(c.Request.Context(), *orgID, campaignID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": m})
|
|
}
|
|
|
|
// PutPlacementMonitor creates or updates a campaign's scheduled test. A PUT of
|
|
// the same body lands on the same state, so it needs no Idempotency-Key.
|
|
func (h *Handler) PutPlacementMonitor(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
campaignID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
var in placement.MonitorInput
|
|
if err := c.ShouldBindJSON(&in); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
userID, _ := middleware.GetUserUUID(c)
|
|
m, xerr := h.PlacementService.PutMonitor(c.Request.Context(), *orgID, userID, campaignID, in)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
id := m.ID
|
|
h.auditOrg(c, models.AuditActionUpdate, models.AuditEntityPlacementMonitor, &id, nil, map[string]string{
|
|
"campaign_id": campaignID.String(),
|
|
})
|
|
c.JSON(http.StatusOK, gin.H{"data": m})
|
|
}
|
|
|
|
// DeletePlacementMonitor removes a campaign's scheduled test.
|
|
func (h *Handler) DeletePlacementMonitor(c *gin.Context) {
|
|
orgID := middleware.GetOrganizationID(c)
|
|
if orgID == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "no organization selected"))
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
campaignID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
if xerr := h.PlacementService.DeleteMonitor(c.Request.Context(), *orgID, campaignID); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.auditOrg(c, models.AuditActionDelete, models.AuditEntityPlacementMonitor, nil, nil, map[string]string{
|
|
"campaign_id": campaignID.String(),
|
|
})
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// --- Admin ----------------------------------------------------------------
|
|
|
|
type adminCreatePlacementTestRequest struct {
|
|
SenderAccountID string `json:"sender_account_id"`
|
|
Subject string `json:"subject"`
|
|
BodyPlain string `json:"body_plain"`
|
|
BodyHTML string `json:"body_html"`
|
|
Tracking string `json:"tracking"`
|
|
}
|
|
|
|
// AdminCreatePlacementTest runs a test from any mailbox on the instance panel,
|
|
// outside every workspace allowance.
|
|
func (h *Handler) AdminCreatePlacementTest(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
var req adminCreatePlacementTestRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
senderID, err := uuid.Parse(req.SenderAccountID)
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid sender_account_id"))
|
|
return
|
|
}
|
|
if h.PlacementRepo == nil {
|
|
errx.JSON(c, errx.New(errx.NotImplemented, "placement testing is not configured"))
|
|
return
|
|
}
|
|
sender, err := h.PlacementRepo.GetSeedAccount(c.Request.Context(), senderID)
|
|
if err != nil {
|
|
errx.JSON(c, errx.InternalError())
|
|
return
|
|
}
|
|
if sender == nil || sender.OrganizationID == nil {
|
|
errx.JSON(c, errx.New(errx.NotFound, "sending mailbox not found"))
|
|
return
|
|
}
|
|
tests, xerr := h.PlacementService.CreateTests(c.Request.Context(), placement.CreateInput{
|
|
OrgID: *sender.OrganizationID,
|
|
SenderAccountID: senderID,
|
|
Subject: req.Subject,
|
|
BodyHTML: req.BodyHTML,
|
|
BodyPlain: req.BodyPlain,
|
|
Tracking: req.Tracking,
|
|
Panel: models.PlacementPanelInstance,
|
|
Origin: models.PlacementOriginAdmin,
|
|
})
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
for _, t := range tests {
|
|
id := t.ID
|
|
h.audit(c, models.AuditActionCreate, models.AuditEntityPlacementTest, &id, map[string]string{
|
|
"sender_account_id": senderID.String(),
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": tests})
|
|
}
|
|
|
|
// AdminListPlacementTests lists every workspace's tests, newest first.
|
|
func (h *Handler) AdminListPlacementTests(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
offset, ok := decodeOffsetCursor(c.Query("cursor"))
|
|
if !ok {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid cursor"))
|
|
return
|
|
}
|
|
limit := parseLimit(c.Query("limit"), 25)
|
|
tests, total, xerr := h.PlacementService.ListTests(c.Request.Context(), nil, nil, limit, offset)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": tests, "pagination": pageMetaFor(offset, limit, len(tests), total)})
|
|
}
|
|
|
|
// AdminGetPlacementTest returns any test in full.
|
|
func (h *Handler) AdminGetPlacementTest(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
detail, xerr := h.PlacementService.GetTest(c.Request.Context(), nil, id)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": detail})
|
|
}
|
|
|
|
// AdminListSeedMailboxes lists the instance panel.
|
|
func (h *Handler) AdminListSeedMailboxes(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
seeds, xerr := h.PlacementService.AdminListSeeds(c.Request.Context())
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": seeds})
|
|
}
|
|
|
|
// AdminListSeedCandidates searches connected mailboxes to add to the panel.
|
|
func (h *Handler) AdminListSeedCandidates(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
seeds, xerr := h.PlacementService.AdminSeedCandidates(c.Request.Context(), c.Query("search"), parseLimit(c.Query("limit"), 50))
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": seeds})
|
|
}
|
|
|
|
// AdminSetSeedMailbox adds a mailbox to the instance panel or takes it off.
|
|
func (h *Handler) AdminSetSeedMailbox(c *gin.Context) {
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid id"))
|
|
return
|
|
}
|
|
var req setPlacementSeedRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
if req.Seed == nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "seed is required"))
|
|
return
|
|
}
|
|
seed, xerr := h.PlacementService.AdminSetSeed(c.Request.Context(), id, *req.Seed)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
h.audit(c, models.AuditActionUpdate, models.AuditEntityEmailAccount, &id, map[string]string{
|
|
"seed": strconv.FormatBool(*req.Seed),
|
|
"email": seed.Email,
|
|
})
|
|
c.JSON(http.StatusOK, gin.H{"data": seed})
|
|
}
|
|
|
|
// --- Warmbly Cloud, for linked instances -----------------------------------
|
|
|
|
// PoolLinkPlacementPanel is the cloud seed panel as a linked instance sees it.
|
|
func (h *Handler) PoolLinkPlacementPanel(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
out, xerr := h.PlacementService.RemotePanel(c.Request.Context(), inst)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, out)
|
|
}
|
|
|
|
// PoolLinkStartPlacement hands a linked instance the seeds for a test.
|
|
func (h *Handler) PoolLinkStartPlacement(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
var req models.PlacementCloudStartRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
out, xerr := h.PlacementService.RemoteStart(c.Request.Context(), inst, req)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, out)
|
|
}
|
|
|
|
// PoolLinkPlacementSends records the Message-IDs of the copies an instance
|
|
// sent.
|
|
func (h *Handler) PoolLinkPlacementSends(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
testID, err := uuid.Parse(c.Param("testId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid test id"))
|
|
return
|
|
}
|
|
var req models.PlacementCloudSends
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
errx.JSON(c, errx.InvalidBody(err))
|
|
return
|
|
}
|
|
if xerr := h.PlacementService.RemoteSends(c.Request.Context(), inst, testID, req.Sends); xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// PoolLinkPlacementVerdicts reports where each copy landed so far.
|
|
func (h *Handler) PoolLinkPlacementVerdicts(c *gin.Context) {
|
|
inst := middleware.GetPoolLinkInstance(c)
|
|
if inst == nil {
|
|
errx.JSON(c, errx.ErrUnauthorized)
|
|
return
|
|
}
|
|
if !h.placementReady(c) {
|
|
return
|
|
}
|
|
testID, err := uuid.Parse(c.Param("testId"))
|
|
if err != nil {
|
|
errx.JSON(c, errx.New(errx.BadRequest, "invalid test id"))
|
|
return
|
|
}
|
|
out, xerr := h.PlacementService.RemoteGet(c.Request.Context(), inst, testID)
|
|
if xerr != nil {
|
|
errx.JSON(c, xerr)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, out)
|
|
}
|