~evanj/cms

d8267364d7e704c2653e785c08fa0c081efc863b — Evan M Jones 10 months ago 6c96e30
Fix(cache break): When a contenttype is deleted break cache for all
content that was referring to content under deleted contenttype.
6 files changed, 46 insertions(+), 2 deletions(-)

M TODO
M go.mod
M go.sum
M internal/s/cache/contenttype.go
M internal/s/db/content.go
M internal/s/db/space.go
M TODO => TODO +1 -0
@@ 11,3 11,4 @@ Fullscreen takeover for html/markdown editors.
Sidebar nav for desktop
X Break cache for referrers when content is deleted
X Don't let content reference itself
X On contenttype delete invalid content that refers to content in the contenttype

M go.mod => go.mod +1 -0
@@ 11,6 11,7 @@ require (
	github.com/go-sql-driver/mysql v1.5.0
	github.com/golang/mock v1.4.3
	github.com/kr/pretty v0.2.0 // indirect
	github.com/pkg/errors v0.9.1
	golang.org/x/crypto v0.0.0-20200320181102-891825fb96df // indirect
	golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
	golang.org/x/text v0.3.2 // indirect

M go.sum => go.sum +2 -0
@@ 21,6 21,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=

M internal/s/cache/contenttype.go => internal/s/cache/contenttype.go +31 -0
@@ 6,6 6,8 @@ import (
	"git.sr.ht/~evanj/cms/internal/m/contenttype"
	"git.sr.ht/~evanj/cms/internal/m/space"
	"git.sr.ht/~evanj/cms/internal/s/db"
	"github.com/bradfitz/gomemcache/memcache"
	"github.com/pkg/errors"
)

// TODO: ON DELETE CASCADE


@@ 58,6 60,27 @@ func (c *Cache) ContentTypeGet(space space.Space, thingID string) (contenttype.C
func (c *Cache) ContentTypeDelete(space space.Space, ct contenttype.ContentType) error {
	key := fmt.Sprintf("%s::%s::%s", c.baseKey, space.ID(), ct.ID())

	// Copy all contents and their values.
	iter, err := c.db.ContentIter(space, ct, "name") // TODO: What is this sort type for?
	if err != nil {
		return errors.Wrap(err, "failed to iterate referring content for cache breaking")
	}

	var list []db.ContentRefSet
	for iter.Next() {
		prevC, err := iter.Scan()
		if err != nil {
			return err
		}

		subList, err := c.db.ContentRefererList(prevC.ID())
		if err != nil {
			return errors.Wrap(err, "failed to find referring content for cache breaking")
		}

		list = append(list, subList...)
	}

	var deleteErr error
	_, _ = c.contenttype(
		true,


@@ 72,6 95,14 @@ func (c *Cache) ContentTypeDelete(space space.Space, ct contenttype.ContentType)
		return deleteErr
	}

	// Remove content from cache that referenced this.
	for _, ref := range list {
		err := c.mc.Delete(fmt.Sprintf("%s::%s::%s::%s", c.baseKey, space.ID(), ref.ContentTypeID, ref.ContentID))
		if !errors.Is(err, memcache.ErrCacheMiss) { // Don't care about cache miss.
			return errors.Wrap(err, "failed to cache break referring content")
		}
	}

	return c.mc.Delete(key)
}


M internal/s/db/content.go => internal/s/db/content.go +10 -1
@@ 1121,12 1121,21 @@ type contentIter struct {
	err  error
}

func (db *DB) ContentIter(t *sql.Tx, space space.Space, ct contenttype.ContentType, sortField string) *contentIter {
func (db *DB) contentIter(t *sql.Tx, space space.Space, ct contenttype.ContentType, sortField string) *contentIter {
	iter := &contentIter{db, t, space, ct, sortField, 0, nil, nil}
	iter.pump()
	return iter
}

func (db *DB) ContentIter(space space.Space, ct contenttype.ContentType, sortField string) (*contentIter, error) {
	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()
	return db.contentIter(t, space, ct, sortField), t.Commit()
}

func (iter *contentIter) pump() {
	list, err := iter.db.contentPerContentType(iter.t, iter.space, iter.ct, iter.page, OrderAsc, iter.sortField, defaultDepth)
	iter.list = list

M internal/s/db/space.go => internal/s/db/space.go +1 -1
@@ 181,7 181,7 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, next, prevS space.Space) error {
		}

		// Copy all contents and their values.
		iter := db.ContentIter(t, prevS, prevCT, "name") // TODO: What is this sort type for?
		iter := db.contentIter(t, prevS, prevCT, "name") // TODO: What is this sort type for?
		for iter.Next() {
			prevC, err := iter.Scan()
			if err != nil {