108 lines
3.5 KiB
Go
108 lines
3.5 KiB
Go
package order
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ProfilePreferencesClient is the subset of the profile service's UCP customer
|
|
// API that returns email preferences for a given email address.
|
|
type ProfilePreferencesClient struct {
|
|
baseURL string
|
|
httpClient *http.Client
|
|
timeout time.Duration
|
|
}
|
|
|
|
// NewProfilePreferencesClient creates a client that talks to the profile
|
|
// service's UCP customer endpoint at baseURL (e.g. "http://profile:8080/ucp/v1/customers").
|
|
func NewProfilePreferencesClient(baseURL string) *ProfilePreferencesClient {
|
|
return &ProfilePreferencesClient{
|
|
baseURL: strings.TrimRight(baseURL, "/"),
|
|
httpClient: &http.Client{Timeout: 5 * time.Second},
|
|
timeout: 5 * time.Second,
|
|
}
|
|
}
|
|
|
|
// emailPreferencesResponse mirrors ucp.EmailPreferencesResponse for decoding.
|
|
type emailPreferencesResponse struct {
|
|
OrderEmails *bool `json:"orderEmails,omitempty"`
|
|
MarketingEmails *bool `json:"marketingEmails,omitempty"`
|
|
}
|
|
|
|
// customerLookupResponse mirrors the UCP customer response, but we only need
|
|
// email preferences.
|
|
type customerLookupResponse struct {
|
|
EmailPreferences *emailPreferencesResponse `json:"emailPreferences,omitempty"`
|
|
}
|
|
|
|
// fetchPreferences calls GET /by-email/{email} on the profile service and
|
|
// returns the parsed email preferences. Returns nil, nil when the profile
|
|
// is not found (unknown email -> allow all).
|
|
func (c *ProfilePreferencesClient) fetchPreferences(ctx context.Context, email string) (*emailPreferencesResponse, error) {
|
|
url := fmt.Sprintf("%s/by-email/%s", c.baseURL, url.PathEscape(email))
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("profile prefs: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
return nil, nil // unknown email -> no preferences
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("profile prefs: unexpected status %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var data customerLookupResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return nil, fmt.Errorf("profile prefs: decode: %w", err)
|
|
}
|
|
return data.EmailPreferences, nil
|
|
}
|
|
|
|
// PreferencesChecker wraps ProfilePreferencesClient to implement
|
|
// EmailPreferenceChecker.
|
|
type PreferencesChecker struct {
|
|
client *ProfilePreferencesClient
|
|
}
|
|
|
|
// NewPreferencesChecker creates an EmailPreferenceChecker that looks up
|
|
// preferences from the profile service at the given base URL.
|
|
func NewPreferencesChecker(profileServiceURL string) *PreferencesChecker {
|
|
return &PreferencesChecker{
|
|
client: NewProfilePreferencesClient(profileServiceURL),
|
|
}
|
|
}
|
|
|
|
// Allowed returns true when the customer has not opted out of the given
|
|
// category. Categories are "order" (order confirmations, shipping updates,
|
|
// receipts) and "marketing" (promotional offers, newsletters). Returns true
|
|
// when preferences are unknown (profile not found) — the safe default.
|
|
func (c *PreferencesChecker) Allowed(ctx context.Context, email string, category string) bool {
|
|
prefs, err := c.client.fetchPreferences(ctx, email)
|
|
if err != nil || prefs == nil {
|
|
return true // safe default: allow when unknown
|
|
}
|
|
|
|
switch category {
|
|
case "order":
|
|
return prefs.OrderEmails == nil || *prefs.OrderEmails
|
|
case "marketing":
|
|
return prefs.MarketingEmails == nil || *prefs.MarketingEmails
|
|
default:
|
|
return true // unknown category -> allow
|
|
}
|
|
}
|