package handlers import ( "encoding/base64" "net/http" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" "github.com/treytartt/casera-api/internal/services" ) // TrackingHandler handles email tracking endpoints type TrackingHandler struct { onboardingService *services.OnboardingEmailService } // NewTrackingHandler creates a new tracking handler func NewTrackingHandler(onboardingService *services.OnboardingEmailService) *TrackingHandler { return &TrackingHandler{ onboardingService: onboardingService, } } // 1x1 transparent GIF (43 bytes) var transparentGIF, _ = base64.StdEncoding.DecodeString("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") // TrackEmailOpen handles email open tracking via tracking pixel // GET /api/track/open/:trackingID func (h *TrackingHandler) TrackEmailOpen(c echo.Context) error { trackingID := c.Param("trackingID") if trackingID != "" && h.onboardingService != nil { // Record the open (async, don't block response) go func() { defer func() { if r := recover(); r != nil { log.Error().Interface("panic", r).Str("tracking_id", trackingID).Msg("Panic in email open tracking goroutine") } }() if err := h.onboardingService.RecordEmailOpened(trackingID); err != nil { log.Error().Err(err).Str("tracking_id", trackingID).Msg("Failed to record email open") } }() } // Return 1x1 transparent GIF c.Response().Header().Set("Content-Type", "image/gif") c.Response().Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate") c.Response().Header().Set("Pragma", "no-cache") c.Response().Header().Set("Expires", "0") return c.Blob(http.StatusOK, "image/gif", transparentGIF) }