feat(seo): add sitemap generator
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user