ultisuite-backend/internal/api/mail/handlers_outbox.go
R3D347HR4Y 65fc9e517a Implement outbox management features with scheduling and attachment support
- 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.
2026-05-22 17:46:30 +02:00

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
}