package controllers import ( "crypto/sha256" "encoding/hex" "encoding/json" "log" "net/mail" "strings" "time" "github.com/gofiber/fiber/v3" "gorm.io/gorm" ) type SubscribeController struct { DB *gorm.DB IsUniqueConstraint func(error) bool } type subscribeRequest struct { Email string `json:"email"` BrowserData map[string]any `json:"browserData"` } const subscriptionCookieName = "bag_exchange_subscribed" func (s *SubscribeController) Subscribe(c fiber.Ctx) error { var req subscribeRequest if err := json.Unmarshal(c.Body(), &req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(map[string]string{"error": "invalid payload"}) } email := strings.ToLower(strings.TrimSpace(req.Email)) if _, err := mail.ParseAddress(email); err != nil { return c.Status(fiber.StatusBadRequest).JSON(map[string]string{"error": "invalid email"}) } emailHash := hashEmail(email) if c.Cookies(subscriptionCookieName) == emailHash { return c.Status(fiber.StatusConflict).JSON(map[string]string{"status": "already_submitted"}) } browserData := "{}" if len(req.BrowserData) > 0 { payload, err := json.Marshal(req.BrowserData) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(map[string]string{"error": "invalid browser data"}) } browserData = string(payload) } err := s.DB.Table("subscribers").Create(map[string]any{ "email": email, "ip_address": c.IP(), "user_agent": c.Get("User-Agent"), "accept_language": c.Get("Accept-Language"), "browser_data": browserData, }).Error if err != nil { if s.IsUniqueConstraint != nil && s.IsUniqueConstraint(err) { setSubscriptionCookie(c, emailHash) return c.Status(fiber.StatusConflict).JSON(map[string]string{"status": "already_submitted"}) } log.Printf("unable to insert subscriber: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(map[string]string{"error": "unable to save subscription"}) } setSubscriptionCookie(c, emailHash) return c.Status(fiber.StatusCreated).JSON(map[string]string{"status": "subscribed"}) } func hashEmail(email string) string { sum := sha256.Sum256([]byte(strings.ToLower(strings.TrimSpace(email)))) return hex.EncodeToString(sum[:]) } func setSubscriptionCookie(c fiber.Ctx, value string) { c.Cookie(&fiber.Cookie{ Name: subscriptionCookieName, Value: value, Path: "/", HTTPOnly: false, Secure: false, Expires: time.Now().AddDate(1, 0, 0), MaxAge: 60 * 60 * 24 * 365, }) }