- PurgeSiteCacheAfterCommit queues the purge on a per-purger worker after commit: one request in flight, saves committed meanwhile collapse into one follow-up, so the admin request never waits for the site - new cooler.SiteCachePurger.Wait drains the queue; Handle.Close waits for it at shutdown (timeout plus one second) - Purge stays the synchronous send; exported names and signatures unchanged
130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
package cooler_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"time"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/cabana"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"git.golem15.com/golem15/summercms/modules/cooler"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
)
|
|
|
|
func ExampleFromApp() {
|
|
// serve publishes the Cache at start-up; a job or console command asks
|
|
// FromApp, which builds the configured driver (memory here) once.
|
|
app := backpack.New(nil)
|
|
cache, err := cooler.FromApp(app)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
ctx := context.Background()
|
|
loads := 0
|
|
popular := func(context.Context) ([]byte, error) {
|
|
loads++
|
|
return json.Marshal([]string{"Hello", "Summer"})
|
|
}
|
|
var titles []string
|
|
for range 2 {
|
|
raw, err := cache.Remember(ctx, "blog:popular", 10*time.Minute, popular)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
if err := json.Unmarshal(raw, &titles); err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
}
|
|
fmt.Println(titles, loads)
|
|
// Output: [Hello Summer] 1
|
|
}
|
|
|
|
// Article is a blog's article model.
|
|
type Article struct {
|
|
Slug string
|
|
}
|
|
|
|
// articlesController is a blog's admin controller (its pact.AdminController
|
|
// methods are left out here).
|
|
type articlesController struct {
|
|
app func() *backpack.App
|
|
}
|
|
|
|
// purge queues one site-cache purge for the saved article once the save
|
|
// transaction commits (at once outside one); the save never waits for the
|
|
// site. Without a purge configured the purger is nil and nothing is sent.
|
|
func (c articlesController) purge(ctx context.Context, model any) error {
|
|
article, ok := model.(*Article)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
tx, _ := cabana.TxFromContext(ctx)
|
|
cooler.PurgeSiteCacheAfterCommit(ctx, tx, cooler.SiteCacheFromApp(c.app()), "article", article.Slug)
|
|
return nil
|
|
}
|
|
|
|
// FormAfterCreate runs inside the create's transaction.
|
|
func (c articlesController) FormAfterCreate(ctx context.Context, model any) error {
|
|
return c.purge(ctx, model)
|
|
}
|
|
|
|
// FormAfterUpdate runs inside the update's transaction.
|
|
func (c articlesController) FormAfterUpdate(ctx context.Context, model any) error {
|
|
return c.purge(ctx, model)
|
|
}
|
|
|
|
// FormAfterDelete runs inside the delete's transaction.
|
|
func (c articlesController) FormAfterDelete(ctx context.Context, model any) error {
|
|
return c.purge(ctx, model)
|
|
}
|
|
|
|
var (
|
|
_ pact.FormAfterCreate = articlesController{}
|
|
_ pact.FormAfterUpdate = articlesController{}
|
|
_ pact.FormAfterDelete = articlesController{}
|
|
)
|
|
|
|
func ExampleSiteCacheFromApp() {
|
|
// The host site's purge route, standing in on loopback.
|
|
site := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
fmt.Println(r.Method, r.URL.Path, string(body))
|
|
_, _ = w.Write([]byte(`{"purged":3}`))
|
|
}))
|
|
defer site.Close()
|
|
|
|
dir, err := os.MkdirTemp("", "cooler-example")
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
cfg, err := compass.Open(compass.Options{Dir: dir, Environ: []string{
|
|
"SUMMER_SITE_CACHE__PURGE_URL=" + site.URL + "/api/__site__/cache/purge",
|
|
"SUMMER_SITE_CACHE__PURGE_TOKEN=example-purge-token-0123456789abcdef",
|
|
}})
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return
|
|
}
|
|
app := backpack.New(cfg)
|
|
c := articlesController{app: func() *backpack.App { return app }}
|
|
if err := c.FormAfterUpdate(context.Background(), &Article{Slug: "hello-summer"}); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
// The purge is queued; Wait drains the queue (serve does it at shutdown).
|
|
if err := cooler.SiteCacheFromApp(app).Wait(context.Background()); err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
// Output: POST /api/__site__/cache/purge {"kind":"article","slug":"hello-summer"}
|
|
}
|