package mail import ( "context" "errors" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/ultisuite/ulti-backend/internal/api/apiresponse" "github.com/ultisuite/ulti-backend/internal/api/apivalidate" "github.com/ultisuite/ulti-backend/internal/api/middleware" mailimap "github.com/ultisuite/ulti-backend/internal/mail/imap" ) // AccountSyncTrigger runs on-demand IMAP maintenance for owned mail accounts. type AccountSyncTrigger interface { SyncAccountForUser(ctx context.Context, externalID, accountID string) error ForceSyncAccountForUser(ctx context.Context, externalID, accountID string) error RefetchAccountBodiesForUser(ctx context.Context, externalID, accountID string) (mailimap.RefetchBodiesResult, error) ReindexMessageAttachments(ctx context.Context, externalID, messageID string) error } func (h *Handler) ResanitizeAccountBodies(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) accountID := chi.URLParam(r, "accountID") if d := validateAccountUUID(accountID); d != nil { apivalidate.WriteNotFound(w, r, "not found") return } if h.accountSync != nil { result, err := h.accountSync.RefetchAccountBodiesForUser(r.Context(), claims.Sub, accountID) if err != nil { if errors.Is(err, ErrAccountNotFound) || strings.Contains(err.Error(), "account not found") { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("refetch account bodies", "account_id", accountID, "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, map[string]int{ "scanned": result.Scanned, "updated": result.Updated, }) return } result, err := h.svc.ResanitizeAccountBodies(r.Context(), claims.Sub, accountID) if err != nil { if errors.Is(err, ErrAccountNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("resanitize account bodies", "account_id", accountID, "error", err) apivalidate.WriteInternal(w, r) return } apiresponse.WriteJSON(w, http.StatusOK, result) } func (h *Handler) SyncAccountNow(w http.ResponseWriter, r *http.Request) { claims := middleware.ClaimsFromContext(r.Context()) accountID := chi.URLParam(r, "accountID") if d := validateAccountUUID(accountID); d != nil { apivalidate.WriteNotFound(w, r, "not found") return } if h.accountSync == nil { apiresponse.WriteError(w, r, http.StatusServiceUnavailable, "sync_unavailable", "mail sync is not configured", nil) return } if _, err := h.svc.GetAccount(r.Context(), claims.Sub, accountID); err != nil { if errors.Is(err, ErrNotFound) { apivalidate.WriteNotFound(w, r, "not found") return } h.logger.Error("load account for sync", "account_id", accountID, "error", err) apivalidate.WriteInternal(w, r) return } force := r.URL.Query().Get("force") == "true" var syncErr error if force { syncErr = h.accountSync.ForceSyncAccountForUser(r.Context(), claims.Sub, accountID) } else { syncErr = h.accountSync.SyncAccountForUser(r.Context(), claims.Sub, accountID) } if syncErr != nil { h.logger.Error("sync account", "account_id", accountID, "force", force, "error", syncErr) apiresponse.WriteError(w, r, http.StatusBadGateway, "sync_failed", "imap sync failed", nil) return } apiresponse.WriteJSON(w, http.StatusOK, map[string]string{"status": "ok"}) }