// Package revalidate pings the frontend's on-demand cache revalidation // endpoint so sitemap-relevant changes (new signups, content re-seeds) surface // within seconds instead of waiting out the frontend's daily ISR window. package revalidate import ( "context" "fmt" "net/http" "net/url" "time" "go.uber.org/zap" ) // Client posts to /api/revalidate. Safe to use when nil or when the // frontend URL / secret are unset — Trigger becomes a no-op (so dev / misconfig // never breaks the calling flow). type Client struct { base string secret string logger *zap.Logger } func New(frontendURL, secret string, logger *zap.Logger) *Client { return &Client{ base: trimRightSlash(frontendURL), secret: secret, logger: logger, } } // Trigger fires a fire-and-forget POST to revalidate the given cache tag. // Never blocks the caller and never errors out of band — use from request // handlers (e.g. signup) on a long-lived server. func (c *Client) Trigger(tag string) { if c == nil || c.base == "" || c.secret == "" { return } go func() { if err := c.TriggerSync(tag); err != nil && c.logger != nil { c.logger.Warn("revalidate ping failed", zap.String("tag", tag), zap.Error(err)) } }() } // TriggerSync POSTs synchronously and returns any error. Use from short-lived // CLIs (e.g. the indexer) that would exit before a background goroutine ran. // No-op (nil) when the client / URL / secret is unset. func (c *Client) TriggerSync(tag string) error { if c == nil || c.base == "" || c.secret == "" { return nil } endpoint := fmt.Sprintf("%s/api/revalidate?tag=%s&secret=%s", c.base, url.QueryEscape(tag), url.QueryEscape(c.secret)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil) if err != nil { return err } resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { return fmt.Errorf("revalidate returned %d", resp.StatusCode) } return nil } func trimRightSlash(s string) string { for len(s) > 0 && s[len(s)-1] == '/' { s = s[:len(s)-1] } return s }