Files
spsays-bff/pkg/revalidate/revalidate.go
T

79 lines
2.2 KiB
Go

// 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 <frontend>/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
}