@@ -37,10 +37,11 @@ import (
3737 3838 3939 type viewPayload struct {40- Event event.Event `json:"event"`41- Attempt int `json:"attempt"`42- Run string `json:"run,omitempty"`43- Refresh bool `json:"refresh,omitempty"`40+ Event event.Event `json:"event"`41+ Attempt int `json:"attempt"`42+ Run string `json:"run,omitempty"`43+ Refresh bool `json:"refresh,omitempty"`44+ Generation string `json:"generation,omitempty"`4445 }4546 4647 @@ -95,18 +96,18 @@ func viewSourceTag(source string) []string {
9596 9697 9798 98-func (t *Tenant) viewHTTPClient() *http.Client {99+func (t *customViewService) viewHTTPClient() *http.Client {99100 if t.viewClient != nil {100101 return t.viewClient101102 }102- if t.loopbackRelay() {103+ if t.loopback != nil && t.loopback() {103104 return &http.Client{Timeout: viewTimeout, Transport: &http.Transport{TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}}, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}104105 }105106 return replication.NewPinnedClient(viewTimeout)106107 }107108 108109 109-func (t *Tenant) viewLock(name string) *sync.Mutex {110+func (t *customViewService) viewLock(name string) *sync.Mutex {110111 t.viewMu.Lock()111112 defer t.viewMu.Unlock()112113 if t.viewLocks == nil {@@ -122,7 +123,7 @@ func (t *Tenant) viewLock(name string) *sync.Mutex {
122123 123124 124125 125-func (t *Tenant) sourceBlocks(ctx context.Context, e event.Event) ([]views.Block, error) {126+func (t *customViewService) sourceBlocks(ctx context.Context, e event.Event) ([]views.Block, error) {126127 if e.Kind != event.KIND_REPO_STATE {127128 return views.Blocks(e.Content), nil128129 }@@ -135,7 +136,7 @@ func (t *Tenant) sourceBlocks(ctx context.Context, e event.Event) ([]views.Block
135136 136137 137138 138-func (t *Tenant) stateReadme(ctx context.Context, e event.Event) (string, error) {139+func (t *customViewService) stateReadme(ctx context.Context, e event.Event) (string, error) {139140 if t.git == nil {140141 return "", nil141142 }@@ -168,7 +169,7 @@ func (t *Tenant) stateReadme(ctx context.Context, e event.Event) (string, error)
168169 169170 170171 171-func (t *Tenant) sourceCurrent(ctx context.Context, e event.Event) (bool, error) {172+func (t *customViewService) sourceCurrent(ctx context.Context, e event.Event) (bool, error) {172173 var id string173174 var err error174175 if e.Kind == event.KIND_REPO_STATE {@@ -186,8 +187,8 @@ func (t *Tenant) sourceCurrent(ctx context.Context, e event.Event) (bool, error)
186187 187188 188189 189-func (t *Tenant) handleViewTransform(ctx context.Context, intent work.Intent) (err error) {190- ctx, finish := t.app.telemetry.Start(ctx, "view")190+func (t *customViewService) handleViewTransform(ctx context.Context, intent work.Intent) (err error) {191+ ctx, finish := t.telemetry.Start(ctx, "view")191192 outcome := "error"192193 defer func() { finish(outcome) }()193194 var payload viewPayload@@ -213,6 +214,10 @@ func (t *Tenant) handleViewTransform(ctx context.Context, intent work.Intent) (e
213214 outcome = "paused"214215 return nil215216 }217+ if payload.Generation != "" && payload.Generation != view.generation {218+ outcome = "invalid"219+ return nil220+ }216221 current, err := t.sourceCurrent(ctx, payload.Event)217222 if err != nil {218223 return err@@ -286,7 +291,7 @@ func (t *Tenant) handleViewTransform(ctx context.Context, intent work.Intent) (e
286291 return err287292 }288293 289- t.app.telemetry.Logger().Info("view transform delivered", "tenant", t.meta.Name, "attempt", payload.Attempt, "status", status, "blocks", len(pending), "artifacts", stored, "refused", refused, "errors", len(response.Errors))294+ t.telemetry.Logger().Info("view transform delivered", "tenant", t.tenantName, "attempt", payload.Attempt, "status", status, "blocks", len(pending), "artifacts", stored, "refused", refused, "errors", len(response.Errors))290295 return t.pruneViewArtifacts(ctx, view.Name, intent.ID)291296 }292297 reason := postErr.Error()@@ -300,7 +305,7 @@ func (t *Tenant) handleViewTransform(ctx context.Context, intent work.Intent) (e
300305 } else if _, err := t.store.DB().ExecContext(ctx, `UPDATE custom_views SET last_run_at=?, failures=?, last_status=? WHERE name=?`, now, failures, reason, view.Name); err != nil {301306 return err302307 }303- t.app.telemetry.Logger().Info("view transform failed", "tenant", t.meta.Name, "attempt", payload.Attempt, "status", status, "failures", failures, "paused", paused)308+ t.telemetry.Logger().Info("view transform failed", "tenant", t.tenantName, "attempt", payload.Attempt, "status", status, "failures", failures, "paused", paused)304309 if paused || payload.Attempt >= viewMaxAttempts {305310 return nil306311 }@@ -309,7 +314,7 @@ func (t *Tenant) handleViewTransform(ctx context.Context, intent work.Intent) (e
309314 310315 311316 312-func (t *Tenant) pauseCustomView(ctx context.Context, name string, failures int, status string) error {317+func (t *customViewService) pauseCustomView(ctx context.Context, name string, failures int, status string) error {313318 if _, err := t.store.DB().ExecContext(ctx, `UPDATE custom_views SET enabled=0, failures=?, last_status=? WHERE name=?`, failures, status, name); err != nil {314319 return err315320 }@@ -319,7 +324,7 @@ func (t *Tenant) pauseCustomView(ctx context.Context, name string, failures int,
319324 320325 321326 322-func (t *Tenant) retryViewTransform(ctx context.Context, intent work.Intent, payload viewPayload) error {327+func (t *customViewService) retryViewTransform(ctx context.Context, intent work.Intent, payload viewPayload) error {323328 delay := viewBackoff[len(viewBackoff)-1]324329 if payload.Attempt-1 < len(viewBackoff) {325330 delay = viewBackoff[payload.Attempt-1]@@ -342,7 +347,7 @@ func (t *Tenant) retryViewTransform(ctx context.Context, intent work.Intent, pay
342347 343348 344349 345-func (t *Tenant) postViewTransform(ctx context.Context, view customView, e event.Event, blocks []views.Block) (int, viewResponse, error) {350+func (t *customViewService) postViewTransform(ctx context.Context, view customView, e event.Event, blocks []views.Block) (int, viewResponse, error) {346351 body, err := json.Marshal(viewRequest{Relay: t.publicURL, View: view.Name, Source: viewSource{ID: viewSourceKey(e), Kind: e.Kind}, Blocks: blocks})347352 if err != nil {348353 return 0, viewResponse{}, errors.New("encode request")@@ -455,7 +460,7 @@ func checkSVG(body string) error {
455460 456461 457462 458-func (t *Tenant) storeArtifact(ctx context.Context, view customView, block views.Block, artifact viewArtifact, source string, expires int64, now int64) error {463+func (t *customViewService) storeArtifact(ctx context.Context, view customView, block views.Block, artifact viewArtifact, source string, expires int64, now int64) error {459464 content, err := checkArtifact(artifact, view.MaxBytes)460465 if err != nil {461466 return err@@ -480,7 +485,7 @@ func (t *Tenant) storeArtifact(ctx context.Context, view customView, block views
480485 481486 482487 483-func (t *Tenant) attachArtifact(ctx context.Context, view customView, hash, source string, block int, expires int64) (bool, error) {488+func (t *customViewService) attachArtifact(ctx context.Context, view customView, hash, source string, block int, expires int64) (bool, error) {484489 var exists int485490 err := t.store.DB().QueryRowContext(ctx, `SELECT 1 FROM custom_view_artifacts WHERE view=? AND hash=?`, view.Name, hash).Scan(&exists)486491 if errors.Is(err, sql.ErrNoRows) {@@ -507,7 +512,7 @@ func (t *Tenant) attachArtifact(ctx context.Context, view customView, hash, sour
507512 508513 509514 510-func (t *Tenant) signArtifact(ctx context.Context, view customView, hash string, now int64) error {515+func (t *customViewService) signArtifact(ctx context.Context, view customView, hash string, now int64) error {511516 var typ, engine, content string512517 if err := t.store.DB().QueryRowContext(ctx, `SELECT type,engine,content FROM custom_view_artifacts WHERE view=? AND hash=?`, view.Name, hash).Scan(&typ, &engine, &content); err != nil {513518 return err@@ -566,7 +571,7 @@ func (t *Tenant) signArtifact(ctx context.Context, view customView, hash string,
566571 567572 568573 569-func (t *Tenant) detachStaleArtifacts(ctx context.Context, name, source string, hashes []string) error {574+func (t *customViewService) detachStaleArtifacts(ctx context.Context, name, source string, hashes []string) error {570575 rows, err := t.store.DB().QueryContext(ctx, `SELECT hash FROM custom_view_sources WHERE view=? AND source=?`, name, source)571576 if err != nil {572577 return err@@ -600,7 +605,7 @@ func (t *Tenant) detachStaleArtifacts(ctx context.Context, name, source string,
600605 601606 602607 603-func (t *Tenant) pruneViewArtifacts(ctx context.Context, name, except string) error {608+func (t *customViewService) pruneViewArtifacts(ctx context.Context, name, except string) error {604609 var pending int605610 if err := t.store.DB().QueryRowContext(ctx, `SELECT count(*) FROM work_intents WHERE kind=? AND target=? AND state IN ('pending','running') AND id<>?`, viewTransform, name, except).Scan(&pending); err != nil {606611 return err@@ -646,7 +651,7 @@ func (t *Tenant) pruneViewArtifacts(ctx context.Context, name, except string) er
646651 647652 648653 649-func (t *Tenant) pruneAllViewArtifacts(ctx context.Context) error {654+func (t *customViewService) pruneAllViewArtifacts(ctx context.Context) error {650655 rows, err := t.customViewRows(ctx)651656 if err != nil {652657 return err@@ -659,7 +664,7 @@ func (t *Tenant) pruneAllViewArtifacts(ctx context.Context) error {
659664 return nil660665 }661666 662-func (t *Tenant) sourcePresent(ctx context.Context, source string) (bool, error) {667+func (t *customViewService) sourcePresent(ctx context.Context, source string) (bool, error) {663668 var one int664669 var err error665670 if tag := viewSourceTag(source); tag[0] == "a" {@@ -679,7 +684,7 @@ func (t *Tenant) sourcePresent(ctx context.Context, source string) (bool, error)
679684 680685 681686 682-func (t *Tenant) dropOrphanArtifacts(ctx context.Context, name string) error {687+func (t *customViewService) dropOrphanArtifacts(ctx context.Context, name string) error {683688 ids, err := t.customViewArtifactIDs(ctx, `SELECT event_id FROM custom_view_artifacts WHERE view=? AND NOT EXISTS(SELECT 1 FROM custom_view_sources WHERE custom_view_sources.view=custom_view_artifacts.view AND custom_view_sources.hash=custom_view_artifacts.hash)`, name)684689 if err != nil {685690 return err@@ -691,7 +696,7 @@ func (t *Tenant) dropOrphanArtifacts(ctx context.Context, name string) error {
691696 return err692697 }693698 if len(ids) > 0 {694- t.app.telemetry.Logger().Info("view artifacts removed", "tenant", t.meta.Name, "removed", len(ids))699+ t.telemetry.Logger().Info("view artifacts removed", "tenant", t.tenantName, "removed", len(ids))695700 }696701 697702 @@ -744,7 +749,7 @@ func (t *Tenant) dropOrphanArtifacts(ctx context.Context, name string) error {
744749 return nil745750 }746751 747-func (t *Tenant) artifactSources(ctx context.Context, name, hash string) ([]string, error) {752+func (t *customViewService) artifactSources(ctx context.Context, name, hash string) ([]string, error) {748753 rows, err := t.store.DB().QueryContext(ctx, `SELECT source FROM custom_view_sources WHERE view=? AND hash=?`, name, hash)749754 if err != nil {750755 return nil, err@@ -761,7 +766,7 @@ func (t *Tenant) artifactSources(ctx context.Context, name, hash string) ([]stri
761766 return out, rows.Err()762767 }763768 764-func (t *Tenant) customViewArtifactIDs(ctx context.Context, query string, args ...any) ([]string, error) {769+func (t *customViewService) customViewArtifactIDs(ctx context.Context, query string, args ...any) ([]string, error) {765770 rows, err := t.store.DB().QueryContext(ctx, query, args...)766771 if err != nil {767772 return nil, err@@ -781,7 +786,7 @@ func (t *Tenant) customViewArtifactIDs(ctx context.Context, query string, args .
781786 }782787 783788 784-func (t *Tenant) deleteStoredArtifacts(ctx context.Context, ids []string) error {789+func (t *customViewService) deleteStoredArtifacts(ctx context.Context, ids []string) error {785790 for _, id := range ids {786791 if _, err := t.store.DeleteEvent(ctx, id); err != nil {787792 return err@@ -792,7 +797,7 @@ func (t *Tenant) deleteStoredArtifacts(ctx context.Context, ids []string) error
792797 793798 794799 795-func (t *Tenant) cascadeViewDeletion(ctx context.Context, e event.Event) {800+func (t *customViewService) cascadeViewDeletion(ctx context.Context, e event.Event) {796801 if e.Kind != event.KIND_DELETION {797802 return798803 }@@ -802,7 +807,7 @@ func (t *Tenant) cascadeViewDeletion(ctx context.Context, e event.Event) {
802807 }803808 for _, view := range rows {804809 if err := t.pruneViewArtifacts(ctx, view.Name, ""); err != nil {805- t.app.telemetry.Logger().Error("view artifacts not pruned", "tenant", t.meta.Name, "error", err)810+ t.telemetry.Logger().Error("view artifacts not pruned", "tenant", t.tenantName, "error", err)806811 }807812 }808813 }@@ -815,7 +820,7 @@ type artifactRecord struct {
815820 EventID string816821 }817822 818-func (t *Tenant) customViewArtifact(ctx context.Context, name, hash string) (artifactRecord, error) {823+func (t *customViewService) customViewArtifact(ctx context.Context, name, hash string) (artifactRecord, error) {819824 var record artifactRecord820825 err := t.store.DB().QueryRowContext(ctx, `SELECT type,audience,content,event_id FROM custom_view_artifacts WHERE view=? AND hash=?`, name, hash).Scan(&record.Type, &record.Audience, &record.Content, &record.EventID)821826 if errors.Is(err, sql.ErrNoRows) {