mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-09 08:03:38 +00:00
122 lines
3.7 KiB
Go
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"
|
|
}
|