- Added new API endpoints for sending, rescheduling, and canceling scheduled outbox messages. - Implemented outbox processing logic to handle attachments and manage message statuses. - Introduced a dead-letter strategy for failed outbox messages, enhancing reliability. - Updated database schema to support new outbox statuses and dead-letter entries. - Enhanced unit tests for outbox functionalities, ensuring robust error handling and validation. - Improved attachment handling in the outbox processor to support inline and regular attachments.
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package mail
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"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"
|
|
)
|
|
|
|
func (h *Handler) SendOutboxNow(w http.ResponseWriter, r *http.Request) {
|
|
claims := middleware.ClaimsFromContext(r.Context())
|
|
userID, err := h.svc.ResolveUserID(r.Context(), claims.Sub)
|
|
if err != nil {
|
|
h.writeUserResolveError(w, r, err)
|
|
return
|
|
}
|
|
|
|
outboxID := chi.URLParam(r, "outboxID")
|
|
status, err := h.svc.SendOutboxNow(r.Context(), userID, outboxID)
|
|
if err != nil {
|
|
if h.writeOutboxActionError(w, r, err) {
|
|
return
|
|
}
|
|
h.logger.Error("send outbox now", "error", err, "outbox_id", outboxID)
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusAccepted, map[string]string{"id": outboxID, "status": status})
|
|
}
|
|
|
|
func (h *Handler) RescheduleOutbox(w http.ResponseWriter, r *http.Request) {
|
|
claims := middleware.ClaimsFromContext(r.Context())
|
|
userID, err := h.svc.ResolveUserID(r.Context(), claims.Sub)
|
|
if err != nil {
|
|
h.writeUserResolveError(w, r, err)
|
|
return
|
|
}
|
|
|
|
var req rescheduleOutboxRequest
|
|
if err := apivalidate.DecodeJSON(w, r, maxOutboxScheduleBody, &req); err != nil {
|
|
return
|
|
}
|
|
scheduledAt, verr := validateRescheduleOutbox(&req)
|
|
if verr != nil {
|
|
apivalidate.WriteValidationError(w, r, verr)
|
|
return
|
|
}
|
|
|
|
outboxID := chi.URLParam(r, "outboxID")
|
|
status, err := h.svc.RescheduleOutbox(r.Context(), userID, outboxID, *scheduledAt)
|
|
if err != nil {
|
|
if h.writeOutboxActionError(w, r, err) {
|
|
return
|
|
}
|
|
h.logger.Error("reschedule outbox", "error", err, "outbox_id", outboxID)
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]string{"id": outboxID, "status": status})
|
|
}
|
|
|
|
func (h *Handler) CancelScheduledOutbox(w http.ResponseWriter, r *http.Request) {
|
|
claims := middleware.ClaimsFromContext(r.Context())
|
|
userID, err := h.svc.ResolveUserID(r.Context(), claims.Sub)
|
|
if err != nil {
|
|
h.writeUserResolveError(w, r, err)
|
|
return
|
|
}
|
|
|
|
outboxID := chi.URLParam(r, "outboxID")
|
|
status, err := h.svc.CancelScheduledOutbox(r.Context(), userID, outboxID)
|
|
if err != nil {
|
|
if h.writeOutboxActionError(w, r, err) {
|
|
return
|
|
}
|
|
h.logger.Error("cancel scheduled outbox", "error", err, "outbox_id", outboxID)
|
|
apivalidate.WriteInternal(w, r)
|
|
return
|
|
}
|
|
apiresponse.WriteJSON(w, http.StatusOK, map[string]string{"id": outboxID, "status": status})
|
|
}
|
|
|
|
func (h *Handler) writeOutboxActionError(w http.ResponseWriter, r *http.Request, err error) bool {
|
|
if errors.Is(err, ErrNotFound) {
|
|
apivalidate.WriteNotFound(w, r, "not found")
|
|
return true
|
|
}
|
|
if errors.Is(err, ErrInvalidOutboxStatus) {
|
|
apiresponse.WriteError(w, r, http.StatusConflict, apiresponse.CodeInvalidRequest, "invalid outbox status", nil)
|
|
return true
|
|
}
|
|
return false
|
|
}
|