~evanj/cms

fb4989378cd725e9151a175bd08b8e63161e7369 — Evan M Jones 1 year, 8 months ago 7a89d26
WIP(cache): Improving cache code reuse.
M cms.go => cms.go +1 -0
@@ 105,6 105,7 @@ func init() {
		content: content.New(
			log.New(w, "[cms:content] ", 0),
			cacher,
			// db,
			fs,
		),
		contenttype: contenttype.New(

M internal/s/cache/cache.go => internal/s/cache/cache.go +31 -0
@@ 1,6 1,7 @@
package cache

import (
	"encoding/json"
	"log"

	"git.sr.ht/~evanj/cms/internal/s/db"


@@ 25,3 26,33 @@ func New(log *log.Logger, db *db.DB, key string, memcacheServer string) (*Cache,
	}
	return c, c.mc.Ping()
}

func (c *Cache) cache(breakCache bool, key string, v interface{}, getter func() (interface{}, error)) error {
	m, err := c.mc.Get(key)
	if err != nil || breakCache {
		thing, err := getter()
		if err != nil {
			return err
		}

		bytes, err := json.Marshal(thing)
		if err != nil {
			return err
		}

		if err := c.mc.Set(&memcache.Item{Key: key, Value: bytes}); err != nil {
			return err
		}

		// TODO: On put we transform to json AND decode to json. Fix this.
		c.log.Println("put", key)
		m = &memcache.Item{Value: bytes}
	}

	if err := json.Unmarshal(m.Value, &v); err != nil {
		return err
	}

	c.log.Println("got", key)
	return nil
}

M internal/s/cache/content.go => internal/s/cache/content.go +1 -25
@@ 1,43 1,19 @@
package cache

import (
	"encoding/json"
	"fmt"

	"git.sr.ht/~evanj/cms/internal/m/content"
	"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"
)

func (c *Cache) content(breakCache bool, key string, getter func() (content.Content, error)) (content.Content, error) {
	m, err := c.mc.Get(key)
	if err != nil || breakCache {
		thing, err := getter()
		if err != nil {
			return nil, err
		}

		bytes, err := json.Marshal(thing)
		if err != nil {
			return nil, err
		}

		if err := c.mc.Set(&memcache.Item{Key: key, Value: bytes}); err != nil {
			return nil, err
		}

		c.log.Println("put", key)
		return thing, nil
	}

	var v *db.Content
	if err := json.Unmarshal(m.Value, &v); err != nil {
	if err := c.cache(breakCache, key, &v, func() (interface{}, error) { return getter() }); err != nil {
		return nil, err
	}

	c.log.Println("got", key)
	return v, nil
}


M internal/s/cache/contenttype.go => internal/s/cache/contenttype.go +1 -25
@@ 1,13 1,11 @@
package cache

import (
	"encoding/json"
	"fmt"

	"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"
)

// TODO: ON DELETE CASCADE


@@ 16,32 14,10 @@ import (
// contenttype is deleted and remove from cache.

func (c *Cache) contenttype(breakCache bool, key string, getter func() (contenttype.ContentType, error)) (contenttype.ContentType, error) {
	m, err := c.mc.Get(key)
	if err != nil || breakCache {
		thing, err := getter()
		if err != nil {
			return nil, err
		}

		bytes, err := json.Marshal(thing)
		if err != nil {
			return nil, err
		}

		if err := c.mc.Set(&memcache.Item{Key: key, Value: bytes}); err != nil {
			return nil, err
		}

		c.log.Println("put", key)
		return thing, nil
	}

	var v *db.ContentType
	if err := json.Unmarshal(m.Value, &v); err != nil {
	if err := c.cache(breakCache, key, &v, func() (interface{}, error) { return getter() }); err != nil {
		return nil, err
	}

	c.log.Println("got", key)
	return v, nil
}


M internal/s/cache/space.go => internal/s/cache/space.go +1 -25
@@ 1,42 1,18 @@
package cache

import (
	"encoding/json"
	"fmt"

	"git.sr.ht/~evanj/cms/internal/m/space"
	"git.sr.ht/~evanj/cms/internal/m/user"
	"git.sr.ht/~evanj/cms/internal/s/db"
	"github.com/bradfitz/gomemcache/memcache"
)

func (c *Cache) space(breakCache bool, key string, getter func() (space.Space, error)) (space.Space, error) {
	m, err := c.mc.Get(key)
	if err != nil || breakCache {
		thing, err := getter()
		if err != nil {
			return nil, err
		}

		bytes, err := json.Marshal(thing)
		if err != nil {
			return nil, err
		}

		if err := c.mc.Set(&memcache.Item{Key: key, Value: bytes}); err != nil {
			return nil, err
		}

		c.log.Println("put", key)
		return thing, nil
	}

	var v *db.Space
	if err := json.Unmarshal(m.Value, &v); err != nil {
	if err := c.cache(breakCache, key, &v, func() (interface{}, error) { return getter() }); err != nil {
		return nil, err
	}

	c.log.Println("got", key)
	return v, nil
}


M internal/s/db/content.go => internal/s/db/content.go +19 -9
@@ 182,7 182,7 @@ var (
	`

	queryValueGetReferenceListByID = `
		SELECT cms_value.ID as ID, cms_valuetype.VALUE as TYPE, cms_contenttype_to_valuetype.NAME, cms_value_reference_list_values.CONTENT_ID as VALUE
		SELECT cms_value.ID as ID, cms_valuetype.VALUE as TYPE, cms_contenttype_to_valuetype.NAME
		FROM cms_value
		JOIN cms_contenttype_to_valuetype
		ON cms_contenttype_to_valuetype.ID = cms_value.CONTENTTYPE_TO_VALUETYPE_ID


@@ 190,14 190,21 @@ var (
		ON cms_valuetype.ID = cms_contenttype_to_valuetype.VALUETYPE_ID
		JOIN cms_value_reference_list
		ON cms_value_reference_list.ID = cms_value.VALUE_ID
		JOIN cms_value_reference_list_values
		ON cms_value_reference_list.ID = cms_value_reference_list_values.VALUE_ID
		WHERE cms_value.ID = ?;
	`

	queryValueGetReferenceListValuesByID = `
		SELECT CONTENT_ID FROM cms_value_reference_list_values 
		WHERE VALUE_ID = ?
		SELECT cms_value.ID as ID, cms_valuetype.VALUE as TYPE, cms_contenttype_to_valuetype.NAME, cms_value_reference_list_values.CONTENT_ID as VALUE
		FROM cms_value
		JOIN cms_contenttype_to_valuetype
		ON cms_contenttype_to_valuetype.ID = cms_value.CONTENTTYPE_TO_VALUETYPE_ID
		JOIN cms_valuetype
		ON cms_valuetype.ID = cms_contenttype_to_valuetype.VALUETYPE_ID
		JOIN cms_value_reference_list
		ON cms_value_reference_list.ID = cms_value.VALUE_ID
		JOIN cms_value_reference_list_values
		ON cms_value_reference_list.ID = cms_value_reference_list_values.VALUE_ID
		WHERE cms_value.ID = ?;
	`

	queryValueListByContent = `


@@ 305,13 312,14 @@ func (db *DB) valueReferenceListNew(s space.Space, ct contenttype.ContentType, c
		return fmt.Errorf("failed to attach field value of content")
	}

	_, err = res.LastInsertId()
	valueID, err := res.LastInsertId()
	if err != nil {
		return fmt.Errorf("failed to read new field value of content")
	}

	var value ContentValue
	if err := t.QueryRow(queryValueGetReferenceListByID, refListID).Scan(&value.FieldID, &value.FieldType, &value.FieldName); err != nil {
	value.FieldValue = strings.Join(IDs, "-") // Match what client should send us.
	if err := t.QueryRow(queryValueGetReferenceListByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName); err != nil {
		return err
	}



@@ 609,14 617,14 @@ func (db *DB) contentValueAttachRefList(t *sql.Tx, c *ContentValue, depth int) e
		return nil
	}

	rows, err := t.Query(queryValueGetReferenceListByID, c.ID())
	rows, err := t.Query(queryValueGetReferenceListValuesByID, c.ID())
	if err != nil {
		return err
	}
	defer rows.Close()

	var ids []string
	for rows.Next() {
		// NOTE: value.FieldValue here will actually be a content ID.
		var value ContentValue
		if err := rows.Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
			return err


@@ 628,8 636,10 @@ func (db *DB) contentValueAttachRefList(t *sql.Tx, c *ContentValue, depth int) e
		}

		c.FieldReferenceList = append(c.FieldReferenceList, ref)
		ids = append(ids, ref.ID())
	}

	c.FieldValue = strings.Join(ids, "-") // Match what client sends us.
	return nil
}


M internal/s/db/db.go => internal/s/db/db.go +5 -3
@@ 152,12 152,14 @@ func (db *DB) CreateTables() error {
		);
	`)

	// TODO: Reconsider these ON DELETE CASCADES after this point.

	// value Reference
	_, _ = db.Exec(`
		CREATE TABLE cms_value_reference (
			ID INTEGER PRIMARY KEY AUTO_INCREMENT,
			VALUE INTEGER NOT NULL,
			FOREIGN KEY(VALUE) REFERENCES cms_content(ID)
			FOREIGN KEY(VALUE) REFERENCES cms_content(ID) ON DELETE CASCADE
		);
	`)



@@ 167,8 169,8 @@ func (db *DB) CreateTables() error {
			ID INTEGER PRIMARY KEY AUTO_INCREMENT,
			VALUE_ID INTEGER NOT NULL,
			CONTENT_ID INTEGER NOT NULL,
			FOREIGN KEY(VALUE_ID) REFERENCES cms_value_reference_list(ID),
			FOREIGN KEY(CONTENT_ID) REFERENCES cms_content(ID)
			FOREIGN KEY(VALUE_ID) REFERENCES cms_value_reference_list(ID) ON DELETE CASCADE,
			FOREIGN KEY(CONTENT_ID) REFERENCES cms_content(ID) ON DELETE CASCADE
		);
	`)


M internal/s/tmpl/html/content.html => internal/s/tmpl/html/content.html +20 -0
@@ 68,6 68,26 @@
              </dialog>
            {{ end }}

            {{ if eq .Type "ReferenceList" }}
              <input class='output-ref' required type=hidden value="{{ .Value }}" name="{{ .Type }}-{{ .Name }}" />
              <input class='input-ref' type=button value=Open />
              <dialog>
                <menu>
                  <div>
                    <center>
                      <p>Search for content to use as reference.</p>
                    </center>
                    <input autofocus class='input-contenttype' type=text placeholder='Search by content type' />
                    <input disabled class='input-content' type=text placeholder='Search by content name' />
                    <div>
                      <input class=left type=button value=Clear />
                      <input class=right type=button value=Done />
                    </div>
                  </div>
                </menu>
              </dialog>
            {{ end }}

          {{ end }}

          <input type=submit value=Update />

M internal/s/tmpl/tmpls_embed.go => internal/s/tmpl/tmpls_embed.go +20 -0
@@ 596,6 596,26 @@ blockquote footer {
              </dialog>
            {{ end }}

            {{ if eq .Type "ReferenceList" }}
              <input class='output-ref' required type=hidden value="{{ .Value }}" name="{{ .Type }}-{{ .Name }}" />
              <input class='input-ref' type=button value=Open />
              <dialog>
                <menu>
                  <div>
                    <center>
                      <p>Search for content to use as reference.</p>
                    </center>
                    <input autofocus class='input-contenttype' type=text placeholder='Search by content type' />
                    <input disabled class='input-content' type=text placeholder='Search by content name' />
                    <div>
                      <input class=left type=button value=Clear />
                      <input class=right type=button value=Done />
                    </div>
                  </div>
                </menu>
              </dialog>
            {{ end }}

          {{ end }}

          <input type=submit value=Update />