Files
warmbly/internal/api/handler/admin_sync.go
T

122 lines
3.7 KiB
Go

package handler
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
"github.com/warmbly/warmbly/internal/repository"
)
const (
auditActionSyncClearThrottle models.AuditAction = "sync_clear_throttle"
auditActionSyncRestartBackfill models.AuditAction = "sync_restart_backfill"
)
// AdminSearchSync is GET /admin/sync: every mailbox's sync governor state.
func (h *Handler) AdminSearchSync(c *gin.Context) {
if h.AdminSyncRepo == nil {
errx.JSON(c, errx.New(errx.NotImplemented, "sync operations are not available on this instance"))
return
}
var search models.AdminSyncSearch
if err := c.ShouldBindQuery(&search); err != nil {
errx.JSON(c, errx.New(errx.BadRequest, "invalid query parameters"))
return
}
result, err := h.AdminSyncRepo.Search(c.Request.Context(), &search)
if err != nil {
switch {
case errors.Is(err, repository.ErrAdminSyncBadCursor):
errx.JSON(c, errx.New(errx.BadRequest, "invalid cursor"))
case errors.Is(err, repository.ErrAdminSyncBadState):
errx.JSON(c, errx.New(errx.BadRequest, "invalid state filter"))
default:
errx.JSON(c, errx.New(errx.Internal, "failed to load sync state"))
}
return
}
c.JSON(http.StatusOK, result)
}
// AdminSyncClearThrottle is POST /admin/sync/:id/clear-throttle.
func (h *Handler) AdminSyncClearThrottle(c *gin.Context) {
if h.AdminSyncRepo == nil {
errx.JSON(c, errx.New(errx.NotImplemented, "sync operations are not available on this instance"))
return
}
id, err := uuid.Parse(c.Param("id"))
if err != nil {
errx.JSON(c, errx.New(errx.BadRequest, "invalid mailbox ID"))
return
}
cleared, err := h.AdminSyncRepo.ClearThrottle(c.Request.Context(), id)
if err != nil {
errx.JSON(c, errx.New(errx.Internal, "failed to clear throttle"))
return
}
if !cleared {
errx.JSON(c, errx.New(errx.NotFound, "mailbox has no sync state or is not throttled"))
return
}
resp := gin.H{"email_id": id, "cleared": true}
h.reloadSyncedMailbox(c, id, resp)
h.audit(c, auditActionSyncClearThrottle, models.AuditEntityEmailAccount, &id, map[string]string{
"reloaded": boolString(resp["reloaded"].(bool)),
})
c.JSON(http.StatusOK, resp)
}
// AdminSyncRestartBackfill is POST /admin/sync/:id/restart-backfill.
func (h *Handler) AdminSyncRestartBackfill(c *gin.Context) {
if h.AdminSyncRepo == nil {
errx.JSON(c, errx.New(errx.NotImplemented, "sync operations are not available on this instance"))
return
}
id, err := uuid.Parse(c.Param("id"))
if err != nil {
errx.JSON(c, errx.New(errx.BadRequest, "invalid mailbox ID"))
return
}
reset, err := h.AdminSyncRepo.ResetBackfill(c.Request.Context(), id)
if err != nil {
errx.JSON(c, errx.New(errx.Internal, "failed to reset backfill"))
return
}
if !reset {
errx.JSON(c, errx.New(errx.NotFound, "mailbox has no sync state"))
return
}
resp := gin.H{"email_id": id, "reset": true}
h.reloadSyncedMailbox(c, id, resp)
h.audit(c, auditActionSyncRestartBackfill, models.AuditEntityEmailAccount, &id, map[string]string{
"reloaded": boolString(resp["reloaded"].(bool)),
})
c.JSON(http.StatusOK, resp)
}
// reloadSyncedMailbox re-ships the mailbox so the worker's live copy follows
// the platform copy; a failure is reported in the response, not as an error.
func (h *Handler) reloadSyncedMailbox(c *gin.Context, id uuid.UUID, resp gin.H) {
resp["reloaded"] = false
if h.EmailService == nil {
resp["reload_error"] = "email service not configured"
return
}
if err := h.EmailService.LoadAccountOntoWorker(c.Request.Context(), id); err != nil {
resp["reload_error"] = err.Error()
return
}
resp["reloaded"] = true
}
func boolString(b bool) string {
if b {
return "true"
}
return "false"
}