~evanj/cms

ed17a2199584ba94d1ca44e9002b52c48b2c02fa — Evan J 2 months ago afc228c
Feat(context): Thread context throughout data layer. TODO: Update
tests.
M cms.go => cms.go +6 -1
@@ 2,9 2,11 @@
package main

import (
	"context"
	"log"
	"net/http"
	"strings"
	"time"
)

type App struct {


@@ 33,5 35,8 @@ func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
		return
	}

	h.ServeHTTP(w, r)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	h.ServeHTTP(w, r.WithContext(ctx))
}

M internal/c/c.go => internal/c/c.go +5 -4
@@ 2,6 2,7 @@ package c

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"


@@ 34,8 35,8 @@ type Controller struct {
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
}

func New(log *log.Logger, db dber, analytics bool, buildID string) *Controller {


@@ 61,7 62,7 @@ func (c *Controller) GetCookieUser2(w http.ResponseWriter, r *http.Request) (use
		return nil, wrapUserErr(err)
	}

	user, err := c.db.UserGetFromToken(cookie.Value)
	user, err := c.db.UserGetFromToken(r.Context(), cookie.Value)
	if err != nil {
		return nil, wrapUserErr(err)
	}


@@ 82,7 83,7 @@ func (c *Controller) GetCookieUser(w http.ResponseWriter, r *http.Request) (user
			return nil, ErrNoLogin
		}

		user, err = c.db.UserGet(u, p)
		user, err = c.db.UserGet(r.Context(), u, p)
		if err != nil {
			return nil, err
		}

M internal/c/content/content.go => internal/c/content/content.go +30 -30
@@ 34,22 34,22 @@ var (
type Content struct {
	*c.Controller
	log     *log.Logger
	db      DBer
	db      dber
	e3      E3er
	hook    Hooker
	baseURL string
}

type DBer interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	ContentTypeGet(u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error)
	ContentNew(u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error)
	ContentGet(u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error)
	ContentUpdate(u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error)
	ContentDelete(u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error
	ContentSearch(u user.User, space space.Space, ct contenttype.ContentType, name, query string, before int) (content.ContentList, error)
type dber interface {
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error)
	ContentTypeGet(ctx context.Context, u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error)
	ContentNew(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error)
	ContentGet(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error)
	ContentUpdate(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error)
	ContentDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error
	ContentSearch(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, name, query string, before int) (content.ContentList, error)
}

type E3er interface {


@@ 57,10 57,10 @@ type E3er interface {
}

type Hooker interface {
	Do(user user.User, space space.Space, content content.Content, ht webhook.HookType)
	Do(ctx context.Context, user user.User, space space.Space, content content.Content, ht webhook.HookType)
}

func New(c *c.Controller, log *log.Logger, db DBer, e3 E3er, hook Hooker, baseURL string) *Content {
func New(c *c.Controller, log *log.Logger, db dber, e3 E3er, hook Hooker, baseURL string) *Content {
	return &Content{
		c,
		log,


@@ 91,17 91,17 @@ func (c *Content) tree(w http.ResponseWriter, r *http.Request, spaceID, contentt
		return nil, nil, nil, nil, err
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		return nil, nil, nil, nil, ErrNoSpace
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		return nil, nil, nil, nil, ErrNoCT
	}

	content, err := c.db.ContentGet(user, space, ct, contentID)
	content, err := c.db.ContentGet(r.Context(), user, space, ct, contentID)
	if err != nil {
		return nil, nil, nil, nil, ErrNoC
	}


@@ 119,13 119,13 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoSpace.Error()))
		return
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoCT.Error()))
		return


@@ 187,13 187,13 @@ func (c *Content) create(w http.ResponseWriter, r *http.Request) {
		}
	}

	content, err := c.db.ContentNew(user, space, ct, params)
	content, err := c.db.ContentNew(r.Context(), user, space, ct, params)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, err)
		return
	}

	go c.hook.Do(user, space, content, webhook.HookNew)
	go c.hook.Do(r.Context(), user, space, content, webhook.HookNew)

	url := fmt.Sprintf("/content/%s/%s/%s", space.ID(), ct.ID(), content.ID())
	c.Redirect(w, r, url)


@@ 217,19 217,19 @@ func (c *Content) serve(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoSpace.Error()))
		return
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoCT.Error()))
		return
	}

	content, err := c.db.ContentGet(user, space, ct, contentID)
	content, err := c.db.ContentGet(r.Context(), user, space, ct, contentID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoC.Error()))
		return


@@ 361,13 361,13 @@ func (c *Content) update(w http.ResponseWriter, r *http.Request) {
		}
	}

	content, err = c.db.ContentUpdate(user, space, ct, content, newParams, updateParams)
	content, err = c.db.ContentUpdate(r.Context(), user, space, ct, content, newParams, updateParams)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to update content: %w", err))
		return
	}

	go c.hook.Do(user, space, content, webhook.HookUpdate)
	go c.hook.Do(r.Context(), user, space, content, webhook.HookUpdate)

	url := fmt.Sprintf("/content/%s/%s/%s", space.ID(), ct.ID(), content.ID())
	c.Redirect(w, r, url)


@@ 384,12 384,12 @@ func (c *Content) delete(w http.ResponseWriter, r *http.Request) {
		return
	}

	if err := c.db.ContentDelete(user, space, ct, content); err != nil {
	if err := c.db.ContentDelete(r.Context(), user, space, ct, content); err != nil {
		c.Error(w, r, http.StatusInternalServerError, fmt.Errorf("failed to delete content: %w", err))
		return
	}

	go c.hook.Do(user, space, content, webhook.HookDelete)
	go c.hook.Do(r.Context(), user, space, content, webhook.HookDelete)

	url := fmt.Sprintf("/contenttype/%s/%s", space.ID(), ct.ID())
	c.Redirect(w, r, url)


@@ 411,20 411,20 @@ func (c *Content) search(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoSpace.Error()))
		return
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusBadRequest, errors.Wrap(err, ErrNoCT.Error()))
		return
	}

	before, _ := strconv.Atoi(r.URL.Query().Get("before"))
	list, err := c.db.ContentSearch(user, space, ct, field, query, before)
	list, err := c.db.ContentSearch(r.Context(), user, space, ct, field, query, before)
	if err != nil {
		return
	}

M internal/c/contenttype/contenttype.go => internal/c/contenttype/contenttype.go +23 -22
@@ 1,6 1,7 @@
package contenttype

import (
	"context"
	"errors"
	"fmt"
	"log"


@@ 40,15 41,15 @@ type ContentType struct {
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	ContentTypeNew(user user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error)
	ContentTypeGet(user user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error)
	ContentTypeUpdate(user user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error)
	ContentTypeDelete(user user.User, space space.Space, ct contenttype.ContentType) error
	ContentTypeSearch(user user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error)
	ContentPerContentType(user user.User, space space.Space, ct contenttype.ContentType, before int, order db.OrderType, sortField string) (content.ContentList, error)
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error)
	ContentTypeNew(ctx context.Context, user user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error)
	ContentTypeGet(ctx context.Context, user user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error)
	ContentTypeUpdate(ctx context.Context, user user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error)
	ContentTypeDelete(ctx context.Context, user user.User, space space.Space, ct contenttype.ContentType) error
	ContentTypeSearch(ctx context.Context, user user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error)
	ContentPerContentType(ctx context.Context, user user.User, space space.Space, ct contenttype.ContentType, before int, order db.OrderType, sortField string) (content.ContentList, error)
}

func New(c *c.Controller, log *log.Logger, db dber) *ContentType {


@@ 62,7 63,7 @@ func (c *ContentType) tree(w http.ResponseWriter, r *http.Request) (user.User, s
	}

	spaceID := r.FormValue("space")
	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		return nil, nil, ErrNoSpace
	}


@@ 121,7 122,7 @@ func (ct *ContentType) create(w http.ResponseWriter, r *http.Request) {
		return
	}

	ctype, err := ct.db.ContentTypeNew(user, space, name, params)
	ctype, err := ct.db.ContentTypeNew(r.Context(), user, space, name, params)
	if err != nil {
		ct.Error(w, r, http.StatusInternalServerError, fmt.Errorf("%s: %w", ErrFailedCreate.Error(), err))
		return


@@ 141,7 142,7 @@ func (ct *ContentType) update(w http.ResponseWriter, r *http.Request) {
		return
	}

	old, err := ct.db.ContentTypeGet(user, space, ctID)
	old, err := ct.db.ContentTypeGet(r.Context(), user, space, ctID)
	if err != nil {
		ct.Error(w, r, http.StatusInternalServerError, ErrNoCT)
		return


@@ 223,13 224,13 @@ func (ct *ContentType) update(w http.ResponseWriter, r *http.Request) {
		return
	}

	ctype, err := ct.db.ContentTypeGet(user, space, ctID)
	ctype, err := ct.db.ContentTypeGet(r.Context(), user, space, ctID)
	if err != nil {
		ct.Error(w, r, http.StatusInternalServerError, ErrNoCT)
		return
	}

	ctype, err = ct.db.ContentTypeUpdate(user, space, ctype, name, newParams, updateParams)
	ctype, err = ct.db.ContentTypeUpdate(r.Context(), user, space, ctype, name, newParams, updateParams)
	if err != nil {
		ct.Error(w, r, http.StatusInternalServerError, fmt.Errorf("%s: %w", ErrFailedUpdate.Error(), err))
		return


@@ 255,13 256,13 @@ func (c *ContentType) serve(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoSpace)
		return
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoCT)
		return


@@ 288,7 289,7 @@ func (c *ContentType) serve(w http.ResponseWriter, r *http.Request) {
	}

	before, _ := strconv.Atoi(r.URL.Query().Get("before"))
	list, err := c.db.ContentPerContentType(user, space, ct, before, o, f)
	list, err := c.db.ContentPerContentType(r.Context(), user, space, ct, before, o, f)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoC)
		return


@@ 312,19 313,19 @@ func (c *ContentType) delete(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoSpace)
		return
	}

	ct, err := c.db.ContentTypeGet(user, space, contenttypeID)
	ct, err := c.db.ContentTypeGet(r.Context(), user, space, contenttypeID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoCT)
		return
	}

	if err := c.db.ContentTypeDelete(user, space, ct); err != nil {
	if err := c.db.ContentTypeDelete(r.Context(), user, space, ct); err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrFailedDelete)
		return
	}


@@ 343,14 344,14 @@ func (c *ContentType) search(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := c.db.SpaceGet(user, spaceID)
	space, err := c.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoSpace)
		return
	}

	before, _ := strconv.Atoi(r.URL.Query().Get("before"))
	list, err := c.db.ContentTypeSearch(user, space, query, before)
	list, err := c.db.ContentTypeSearch(r.Context(), user, space, query, before)
	if err != nil {
		c.Error(w, r, http.StatusInternalServerError, ErrNoCT)
		return

M internal/c/doc/doc.go => internal/c/doc/doc.go +2 -11
@@ 7,7 7,6 @@ import (

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/m/tier"
	"git.sr.ht/~evanj/cms/internal/m/user"
	"git.sr.ht/~evanj/cms/internal/v"
)



@@ 27,16 26,8 @@ type Doc struct {
	log *log.Logger
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
}

func New(c *c.Controller, log *log.Logger, db dber) *Doc {
	return &Doc{
		c,
		log,
	}
func New(c *c.Controller, log *log.Logger) *Doc {
	return &Doc{c, log}
}

func (d *Doc) ServeHTTP(w http.ResponseWriter, r *http.Request) {

M internal/c/file/file.go => internal/c/file/file.go +7 -7
@@ 13,15 13,15 @@ import (
type File struct {
	*c.Controller
	log     *log.Logger
	db      DBer
	db      dber
	e3      E3er
	baseURL string
}

type DBer interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	FileExists(URL string) (bool, error)
type dber interface {
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	FileExists(ctx context.Context, URL string) (bool, error)
}

type E3er interface {


@@ 29,12 29,12 @@ type E3er interface {
	URL() string
}

func New(c *c.Controller, log *log.Logger, db DBer, e3 E3er, baseURL string) *File {
func New(c *c.Controller, log *log.Logger, db dber, e3 E3er, baseURL string) *File {
	return &File{c, log, db, e3, baseURL}
}

func (f *File) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ok, err := f.db.FileExists(f.baseURL + r.URL.Path)
	ok, err := f.db.FileExists(r.Context(), f.baseURL+r.URL.Path)
	if !ok || err != nil {
		f.ErrorString(w, r, http.StatusNotFound, "file does not exist")
		return

M internal/c/hook/hook.go => internal/c/hook/hook.go +14 -13
@@ 1,6 1,7 @@
package hook

import (
	"context"
	"errors"
	"fmt"
	"log"


@@ 30,12 31,12 @@ type Content struct {
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	HookNew(user user.User, space space.Space, url string) (hook.Hook, error)
	HookGet(user user.User, space space.Space, id string) (hook.Hook, error)
	HookDelete(user user.User, space space.Space, hook hook.Hook) error
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error)
	HookNew(ctx context.Context, user user.User, space space.Space, url string) (hook.Hook, error)
	HookGet(ctx context.Context, user user.User, space space.Space, id string) (hook.Hook, error)
	HookDelete(ctx context.Context, user user.User, space space.Space, hook hook.Hook) error
}

func New(c *c.Controller, log *log.Logger, db dber) *Content {


@@ 56,13 57,13 @@ func (h *Content) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch h.Method(r) {

	case "POST":
		space, err := h.db.SpaceGet(user, r.FormValue("space"))
		space, err := h.db.SpaceGet(r.Context(), user, r.FormValue("space"))
		if err != nil {
			h.Error(w, r, http.StatusBadRequest, ErrNoSpace)
			return
		}

		hook, err := h.db.HookNew(user, space, r.FormValue("url"))
		hook, err := h.db.HookNew(r.Context(), user, space, r.FormValue("url"))
		if err != nil {
			h.Error(w, r, http.StatusInternalServerError, ErrFailedCreate)
			return


@@ 72,19 73,19 @@ func (h *Content) ServeHTTP(w http.ResponseWriter, r *http.Request) {
		return

	case "DELETE":
		space, err := h.db.SpaceGet(user, r.FormValue("space"))
		space, err := h.db.SpaceGet(r.Context(), user, r.FormValue("space"))
		if err != nil {
			h.Error(w, r, http.StatusBadRequest, ErrNoSpace)
			return
		}

		hook, err := h.db.HookGet(user, space, r.FormValue("hook"))
		hook, err := h.db.HookGet(r.Context(), user, space, r.FormValue("hook"))
		if err != nil {
			h.Error(w, r, http.StatusBadRequest, ErrNoHook)
			return
		}

		if err := h.db.HookDelete(user, space, hook); err != nil {
		if err := h.db.HookDelete(r.Context(), user, space, hook); err != nil {
			h.Error(w, r, http.StatusInternalServerError, ErrFailedDelete)
			return
		}


@@ 102,13 103,13 @@ func (h *Content) ServeHTTP(w http.ResponseWriter, r *http.Request) {
			hookID = parts[3]
		}

		space, err := h.db.SpaceGet(user, spaceID)
		space, err := h.db.SpaceGet(r.Context(), user, spaceID)
		if err != nil {
			h.Error(w, r, http.StatusBadRequest, ErrNoSpace)
			return
		}

		hook, err := h.db.HookGet(user, space, hookID)
		hook, err := h.db.HookGet(r.Context(), user, space, hookID)
		if err != nil {
			h.Error(w, r, http.StatusBadRequest, ErrNoHook)
			return

M internal/c/invite/invite.go => internal/c/invite/invite.go +9 -8
@@ 1,6 1,7 @@
package invite

import (
	"context"
	"errors"
	"fmt"
	"log"


@@ 29,10 30,10 @@ type Invite struct {
}

type dber interface {
	InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, error)
	InviteGetByToken(tok string) (invite.Invite, error)
	InviteAccept(i invite.Invite, u, p, v string) (user.User, invite.Invite, error)
	InviteList(u user.User, o org.Org) (r []invite.Invite, err error)
	InviteNew(ctx context.Context, u user.User, o org.Org, r role.Role) (invite.Invite, error)
	InviteGetByToken(ctx context.Context, tok string) (invite.Invite, error)
	InviteAccept(ctx context.Context, i invite.Invite, u, p, v string) (user.User, invite.Invite, error)
	InviteList(ctx context.Context, u user.User, o org.Org) (r []invite.Invite, err error)
}

func New(c *c.Controller, log *log.Logger, db dber) Invite {


@@ 54,7 55,7 @@ func (i Invite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
			return
		}

		invites, err := i.db.InviteList(user, user.Org())
		invites, err := i.db.InviteList(r.Context(), user, user.Org())
		if err != nil {
			i.Error(w, r, http.StatusInternalServerError, err)
			return


@@ 80,7 81,7 @@ func (i Invite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
			return
		}

		_, err = i.db.InviteNew(user, user.Org(), role)
		_, err = i.db.InviteNew(r.Context(), user, user.Org(), role)
		if errors.Is(err, invite.ErrExpired) || errors.Is(err, invite.ErrUsed) {
			i.Error(w, r, http.StatusBadRequest, err)
			return


@@ 101,14 102,14 @@ func (i Invite) ServeHTTP(w http.ResponseWriter, r *http.Request) {
			return
		}

		inv, err := i.db.InviteGetByToken(r.FormValue("invite"))
		inv, err := i.db.InviteGetByToken(r.Context(), r.FormValue("invite"))
		if err != nil {
			i.Error(w, r, http.StatusBadRequest, err)
			return
		}

		// Accept invite and create new user.
		user, _, err := i.db.InviteAccept(inv, r.FormValue("username"), r.FormValue("password"), r.FormValue("verify"))
		user, _, err := i.db.InviteAccept(r.Context(), inv, r.FormValue("username"), r.FormValue("password"), r.FormValue("verify"))
		if errors.Is(err, invite.ErrExpired) || errors.Is(err, invite.ErrUsed) {
			i.Error(w, r, http.StatusBadRequest, err)
			return

M internal/c/redirect/redirect.go => internal/c/redirect/redirect.go +3 -2
@@ 1,6 1,7 @@
package redirect

import (
	"context"
	"log"
	"net/http"



@@ 17,8 18,8 @@ type Redirect struct {
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
}

func New(c *c.Controller, log *log.Logger, db dber) *Redirect {

M internal/c/space/space.go => internal/c/space/space.go +20 -19
@@ 1,6 1,7 @@
package space

import (
	"context"
	"fmt"
	"log"
	"net/http"


@@ 30,15 31,15 @@ type Space struct {
}

type dber interface {
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	SpaceNew(user user.User, name, desc string) (space.Space, error)
	SpaceGet(user user.User, spaceID string) (space.Space, error)
	SpaceUpdate(user user.User, space space.Space, name, desc string) (space.Space, error)
	SpaceCopy(user user.User, space space.Space, name, desc string) (space.Space, error)
	SpaceDelete(user user.User, space space.Space) error
	ContentTypesPerSpace(user user.User, space space.Space, before int) (contenttype.ContentTypeList, error)
	HooksPerSpace(user user.User, space space.Space, before int) (hook.HookList, error)
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	SpaceNew(ctx context.Context, user user.User, name, desc string) (space.Space, error)
	SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error)
	SpaceUpdate(ctx context.Context, user user.User, space space.Space, name, desc string) (space.Space, error)
	SpaceCopy(ctx context.Context, user user.User, space space.Space, name, desc string) (space.Space, error)
	SpaceDelete(ctx context.Context, user user.User, space space.Space) error
	ContentTypesPerSpace(ctx context.Context, user user.User, space space.Space, before int) (contenttype.ContentTypeList, error)
	HooksPerSpace(ctx context.Context, user user.User, space space.Space, before int) (hook.HookList, error)
}

func New(c *c.Controller, log *log.Logger, db dber) *Space {


@@ 62,21 63,21 @@ func (s *Space) serve(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := s.db.SpaceGet(user, spaceID)
	space, err := s.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		s.Error(w, r, http.StatusNotFound, ErrNoSpace)
		return
	}

	beforect, _ := strconv.Atoi(r.URL.Query().Get("beforect"))
	cts, err := s.db.ContentTypesPerSpace(user, space, beforect)
	cts, err := s.db.ContentTypesPerSpace(r.Context(), user, space, beforect)
	if err != nil {
		s.ErrorString(w, r, http.StatusInternalServerError, "failed to find contenttypes for space")
		return
	}

	beforehook, _ := strconv.Atoi(r.URL.Query().Get("beforehook"))
	hooks, err := s.db.HooksPerSpace(user, space, beforehook)
	hooks, err := s.db.HooksPerSpace(r.Context(), user, space, beforehook)
	if err != nil {
		s.ErrorString(w, r, http.StatusInternalServerError, "failed to find webhooks for space")
		return


@@ 100,7 101,7 @@ func (s *Space) create(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := s.db.SpaceNew(user, name, desc)
	space, err := s.db.SpaceNew(r.Context(), user, name, desc)
	if err != nil {
		s.Error(w, r, http.StatusBadRequest, err)
		return


@@ 121,13 122,13 @@ func (s *Space) copy(w http.ResponseWriter, r *http.Request) {
		return
	}

	spacePrev, err := s.db.SpaceGet(user, spaceID)
	spacePrev, err := s.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		s.Error(w, r, http.StatusNotFound, ErrNoSpace)
		return
	}

	spaceNext, err := s.db.SpaceCopy(user, spacePrev, name, desc)
	spaceNext, err := s.db.SpaceCopy(r.Context(), user, spacePrev, name, desc)
	if err != nil {
		s.Error(w, r, http.StatusBadRequest, err)
		return


@@ 148,13 149,13 @@ func (s *Space) update(w http.ResponseWriter, r *http.Request) {
		return
	}

	prev, err := s.db.SpaceGet(user, spaceID)
	prev, err := s.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		s.Error(w, r, http.StatusNotFound, ErrNoSpace)
		return
	}

	next, err := s.db.SpaceUpdate(user, prev, name, desc)
	next, err := s.db.SpaceUpdate(r.Context(), user, prev, name, desc)
	if err != nil {
		s.ErrorString(w, r, http.StatusInternalServerError, "failed to update space")
		return


@@ 173,13 174,13 @@ func (s *Space) delete(w http.ResponseWriter, r *http.Request) {
		return
	}

	space, err := s.db.SpaceGet(user, spaceID)
	space, err := s.db.SpaceGet(r.Context(), user, spaceID)
	if err != nil {
		s.Error(w, r, http.StatusNotFound, ErrNoSpace)
		return
	}

	if err := s.db.SpaceDelete(user, space); err != nil {
	if err := s.db.SpaceDelete(r.Context(), user, space); err != nil {
		s.ErrorString(w, r, http.StatusInternalServerError, "failed to delete space")
		return
	}

M internal/c/stripe/stripe.go => internal/c/stripe/stripe.go +5 -7
@@ 1,6 1,7 @@
package stripe

import (
	"context"
	"log"
	"net/http"



@@ 10,24 11,21 @@ import (
type StripeEndpoint struct {
	*c.Controller
	log    *log.Logger
	db     DBer
	stripe Striper
}

type DBer interface{}

type Striper interface {
	CompleteCheckout(sessionID string) error
	CompleteCheckout(ctx context.Context, sessionID string) error
}

func New(c *c.Controller, l *log.Logger, db DBer, striper Striper) StripeEndpoint {
	return StripeEndpoint{c, l, db, striper}
func New(c *c.Controller, l *log.Logger, striper Striper) StripeEndpoint {
	return StripeEndpoint{c, l, striper}
}

func (s StripeEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/success":
		err := s.stripe.CompleteCheckout(r.FormValue("session_id"))
		err := s.stripe.CompleteCheckout(r.Context(), r.FormValue("session_id"))
		if err != nil {
			s.Error(w, r, http.StatusInternalServerError, err)
			return

M internal/c/user/user.go => internal/c/user/user.go +17 -16
@@ 1,6 1,7 @@
package user

import (
	"context"
	"errors"
	"log"
	"net/http"


@@ 29,17 30,17 @@ type User struct {
}

type dber interface {
	UserNew(username, password, verifyPassword string) (user.User, error)
	UserGet(username, password string) (user.User, error)
	UserGetFromToken(token string) (user.User, error)
	UserSetEmail(u user.User, email string) (user.User, error)
	UserSetPassword(u user.User, current, password, verify string) (user.User, error)
	SpacesPerUser(user user.User, before int) (space.SpaceList, error)
	UserNew(ctx context.Context, username, password, verifyPassword string) (user.User, error)
	UserGet(ctx context.Context, username, password string) (user.User, error)
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	UserSetEmail(ctx context.Context, u user.User, email string) (user.User, error)
	UserSetPassword(ctx context.Context, u user.User, current, password, verify string) (user.User, error)
	SpacesPerUser(ctx context.Context, user user.User, before int) (space.SpaceList, error)
}

type Striper interface {
	StartCheckout(user user.User, t tier.Tier) (string, string, error)
	CancelSubscription(user user.User) error
	StartCheckout(ctx context.Context, user user.User, t tier.Tier) (string, string, error)
	CancelSubscription(ctx context.Context, user user.User) error
}

func New(c *c.Controller, log *log.Logger, db dber, signupEnabled bool, s Striper) *User {


@@ 61,7 62,7 @@ func (l *User) login(w http.ResponseWriter, r *http.Request) {
	username := r.FormValue("username")
	password := r.FormValue("password")

	user, err := l.db.UserGet(username, password)
	user, err := l.db.UserGet(r.Context(), username, password)
	if err != nil {
		l.Error(w, r, http.StatusBadRequest, err)
		return


@@ 90,7 91,7 @@ func (l *User) signup(w http.ResponseWriter, r *http.Request) {
		return
	}

	user, err := l.db.UserNew(username, password, verify)
	user, err := l.db.UserNew(r.Context(), username, password, verify)
	if err != nil {
		l.Error(w, r, http.StatusBadRequest, err)
		return


@@ 102,7 103,7 @@ func (l *User) signup(w http.ResponseWriter, r *http.Request) {
		return
	}

	stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(user, t)
	stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(r.Context(), user, t)
	if err != nil {
		l.Error(w, r, http.StatusInternalServerError, err)
		return


@@ 129,7 130,7 @@ func (l *User) home(w http.ResponseWriter, r *http.Request) {
	// value.
	before, _ := strconv.Atoi(r.URL.Query().Get("before"))

	spaces, err := l.db.SpacesPerUser(user, before)
	spaces, err := l.db.SpacesPerUser(r.Context(), user, before)
	if err != nil {
		l.Error(w, r, http.StatusInternalServerError, errors.New("failed to find spaces for user"))
		return


@@ 149,7 150,7 @@ func (l *User) updateEmail(w http.ResponseWriter, r *http.Request) {
		return
	}

	if _, err := l.db.UserSetEmail(u, r.FormValue("email")); err != nil {
	if _, err := l.db.UserSetEmail(r.Context(), u, r.FormValue("email")); err != nil {
		l.Error(w, r, http.StatusInternalServerError, err)
		return
	}


@@ 164,7 165,7 @@ func (l *User) updatePassword(w http.ResponseWriter, r *http.Request) {
		return
	}

	if _, err := l.db.UserSetPassword(u, r.FormValue("current"), r.FormValue("password"), r.FormValue("verify")); err != nil {
	if _, err := l.db.UserSetPassword(r.Context(), u, r.FormValue("current"), r.FormValue("password"), r.FormValue("verify")); err != nil {
		l.Error(w, r, http.StatusInternalServerError, err)
		return
	}


@@ 190,7 191,7 @@ func (l *User) updateBilling(w http.ResponseWriter, r *http.Request) {
		return
	}

	stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(u, t)
	stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(r.Context(), u, t)
	if err != nil {
		l.Error(w, r, http.StatusInternalServerError, err)
		return


@@ 211,7 212,7 @@ func (l *User) cancelBilling(w http.ResponseWriter, r *http.Request) {
		return
	}

	if err := l.stripe.CancelSubscription(u); err != nil {
	if err := l.stripe.CancelSubscription(r.Context(), u); err != nil {
		l.Error(w, r, http.StatusInternalServerError, err)
		return
	}

D internal/s/cache/cache_generic.go => internal/s/cache/cache_generic.go +0 -23
@@ 1,23 0,0 @@
// Code generated by go2go; DO NOT EDIT.


//line cache_generic.go2:1
package cache

//line cache_generic.go2:1
import (
//line cache_generic.go2:1
 "encoding/json"
//line cache_generic.go2:1
 "github.com/bradfitz/gomemcache/memcache"
//line cache_generic.go2:1
)

//line cache_generic.go2:1
type Importableā­Ķ int

//line cache_generic.go2:1
var _ = json.Compact

//line cache_generic.go2:1
type _ memcache.Client

D internal/s/cache/cache_generic.go2 => internal/s/cache/cache_generic.go2 +0 -31
@@ 1,31 0,0 @@
package cache

import (
	"encoding/json"
	"github.com/bradfitz/gomemcache/memcache"
)

func StoreAndLoad[type T](mc *memcache.Client, breakCache bool, key string, val T, getter func() (T, error)) (it T, err error) { 
  cached, err := mc.Get(key)
  if err != nil || breakCache { 
    it, err = getter()
    if err != nil { 
      return it, err
    }

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

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

    return it, nil
  }

	err = json.Unmarshal(cached.Value, &it)
  return it, err
}


M internal/s/cache/content.go => internal/s/cache/content.go +11 -10
@@ 1,6 1,7 @@
package cache

import (
	"context"
	"errors"
	"fmt"



@@ 20,8 21,8 @@ func (c *Cache) content(breakCache bool, key string, getter func() (content.Cont
	return v, nil
}

func (c *Cache) ContentNew(u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error) {
	thing, err := c.db.ContentNew(u, space, ct, params)
func (c *Cache) ContentNew(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error) {
	thing, err := c.db.ContentNew(ctx, u, space, ct, params)
	if err != nil {
		return nil, err
	}


@@ 33,27 34,27 @@ func (c *Cache) ContentNew(u user.User, space space.Space, ct contenttype.Conten
	)
}

func (c *Cache) ContentGet(u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
func (c *Cache) ContentGet(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
	return c.content(
		false,
		fmt.Sprintf("content::%s::%s::%s::%s", c.baseKey, space.ID(), ct.ID(), contentID),
		func() (content.Content, error) { return c.db.ContentGet(u, space, ct, contentID) },
		func() (content.Content, error) { return c.db.ContentGet(ctx, u, space, ct, contentID) },
	)
}

func (c *Cache) ContentUpdate(u user.User, space space.Space, ct contenttype.ContentType, item content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error) {
func (c *Cache) ContentUpdate(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, item content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error) {
	content, err := c.content(
		true,
		fmt.Sprintf("content::%s::%s::%s::%s", c.baseKey, space.ID(), ct.ID(), item.ID()),
		func() (content.Content, error) {
			return c.db.ContentUpdate(u, space, ct, item, newParams, updateParams)
			return c.db.ContentUpdate(ctx, u, space, ct, item, newParams, updateParams)
		},
	)
	if err != nil {
		return nil, err
	}

	list, err := c.db.ContentRefererList(item.ID())
	list, err := c.db.ContentRefererList(ctx, item.ID())
	if err != nil {
		return nil, err
	}


@@ 69,10 70,10 @@ func (c *Cache) ContentUpdate(u user.User, space space.Space, ct contenttype.Con
	return content, nil
}

func (c *Cache) ContentDelete(u user.User, space space.Space, ct contenttype.ContentType, item content.Content) error {
func (c *Cache) ContentDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, item content.Content) error {
	key := fmt.Sprintf("content::%s::%s::%s::%s", c.baseKey, space.ID(), ct.ID(), item.ID())

	list, err := c.db.ContentRefererList(item.ID())
	list, err := c.db.ContentRefererList(ctx, item.ID())
	if err != nil {
		return err
	}


@@ 82,7 83,7 @@ func (c *Cache) ContentDelete(u user.User, space space.Space, ct contenttype.Con
		true,
		key,
		func() (content.Content, error) {
			deleteErr = c.db.ContentDelete(u, space, ct, item)
			deleteErr = c.db.ContentDelete(ctx, u, space, ct, item)
			return nil, deleteErr
		},
	)

M internal/s/cache/contenttype.go => internal/s/cache/contenttype.go +12 -11
@@ 1,6 1,7 @@
package cache

import (
	"context"
	"fmt"

	"git.sr.ht/~evanj/cms/internal/m/contenttype"


@@ 24,8 25,8 @@ func (c *Cache) contenttype(breakCache bool, key string, getter func() (contentt
	return v, nil
}

func (c *Cache) ContentTypeNew(u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
	thing, err := c.db.ContentTypeNew(u, space, name, params)
func (c *Cache) ContentTypeNew(ctx context.Context, u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
	thing, err := c.db.ContentTypeNew(ctx, u, space, name, params)
	if err != nil {
		return nil, err
	}


@@ 37,13 38,13 @@ func (c *Cache) ContentTypeNew(u user.User, space space.Space, name string, para
	)
}

func (c *Cache) ContentTypeUpdate(u user.User, space space.Space, prev contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
	next, err := c.db.ContentTypeUpdate(u, space, prev, name, newParams, updateParams)
func (c *Cache) ContentTypeUpdate(ctx context.Context, u user.User, space space.Space, prev contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
	next, err := c.db.ContentTypeUpdate(ctx, u, space, prev, name, newParams, updateParams)
	if err != nil {
		return nil, err
	}

	iter, t, err := c.db.ContentIter(u, space, next, "name") // TODO: What is this sort type for?
	iter, t, err := c.db.ContentIter(ctx, u, space, next, "name") // TODO: What is this sort type for?
	if err != nil {
		return nil, errors.Wrap(err, "failed to iterate content for cache breaking")
	}


@@ 73,19 74,19 @@ func (c *Cache) ContentTypeUpdate(u user.User, space space.Space, prev contentty
	)
}

func (c *Cache) ContentTypeGet(u user.User, space space.Space, thingID string) (contenttype.ContentType, error) {
func (c *Cache) ContentTypeGet(ctx context.Context, u user.User, space space.Space, thingID string) (contenttype.ContentType, error) {
	return c.contenttype(
		false,
		fmt.Sprintf("contenttype::%s::%s::%s", c.baseKey, space.ID(), thingID),
		func() (contenttype.ContentType, error) { return c.db.ContentTypeGet(u, space, thingID) },
		func() (contenttype.ContentType, error) { return c.db.ContentTypeGet(ctx, u, space, thingID) },
	)
}

func (c *Cache) ContentTypeDelete(u user.User, space space.Space, ct contenttype.ContentType) error {
func (c *Cache) ContentTypeDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType) error {
	key := fmt.Sprintf("contenttype::%s::%s::%s", c.baseKey, space.ID(), ct.ID())

	// Copy all contents and their values.
	iter, t, err := c.db.ContentIter(u, space, ct, "name") // TODO: What is this sort type for?
	iter, t, err := c.db.ContentIter(ctx, u, 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")
	}


@@ 98,7 99,7 @@ func (c *Cache) ContentTypeDelete(u user.User, space space.Space, ct contenttype
			return err
		}

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


@@ 115,7 116,7 @@ func (c *Cache) ContentTypeDelete(u user.User, space space.Space, ct contenttype
		true,
		key,
		func() (contenttype.ContentType, error) {
			deleteErr = c.db.ContentTypeDelete(u, space, ct)
			deleteErr = c.db.ContentTypeDelete(ctx, u, space, ct)
			return nil, deleteErr
		},
	)

M internal/s/cache/space.go => internal/s/cache/space.go +9 -8
@@ 1,6 1,7 @@
package cache

import (
	"context"
	"fmt"

	"git.sr.ht/~evanj/cms/internal/m/space"


@@ 16,8 17,8 @@ func (c *Cache) space(breakCache bool, key string, getter func() (space.Space, e
	return v, nil
}

func (c *Cache) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	thing, err := c.db.SpaceNew(user, name, desc)
func (c *Cache) SpaceNew(ctx context.Context, user user.User, name, desc string) (space.Space, error) {
	thing, err := c.db.SpaceNew(ctx, user, name, desc)
	if err != nil {
		return nil, err
	}


@@ 29,16 30,16 @@ func (c *Cache) SpaceNew(user user.User, name, desc string) (space.Space, error)
	)
}

func (c *Cache) SpaceGet(user user.User, thingID string) (space.Space, error) {
func (c *Cache) SpaceGet(ctx context.Context, user user.User, thingID string) (space.Space, error) {
	return c.space(
		false,
		fmt.Sprintf("space::%s::%s::%s", c.baseKey, user.ID(), thingID),
		func() (space.Space, error) { return c.db.SpaceGet(user, thingID) },
		func() (space.Space, error) { return c.db.SpaceGet(ctx, user, thingID) },
	)
}

func (c *Cache) SpaceUpdate(user user.User, prev space.Space, name, desc string) (space.Space, error) {
	next, err := c.db.SpaceUpdate(user, prev, name, desc)
func (c *Cache) SpaceUpdate(ctx context.Context, user user.User, prev space.Space, name, desc string) (space.Space, error) {
	next, err := c.db.SpaceUpdate(ctx, user, prev, name, desc)
	if err != nil {
		return nil, err
	}


@@ 50,7 51,7 @@ func (c *Cache) SpaceUpdate(user user.User, prev space.Space, name, desc string)
	)
}

func (c *Cache) SpaceDelete(u user.User, s space.Space) error {
func (c *Cache) SpaceDelete(ctx context.Context, u user.User, s space.Space) error {
	key := fmt.Sprintf("space::%s::%s::%s", c.baseKey, u.ID(), s.ID())

	var deleteErr error


@@ 58,7 59,7 @@ func (c *Cache) SpaceDelete(u user.User, s space.Space) error {
		true,
		key,
		func() (space.Space, error) {
			deleteErr = c.db.SpaceDelete(u, s)
			deleteErr = c.db.SpaceDelete(ctx, u, s)
			return nil, deleteErr
		},
	)

M internal/s/db/action.go => internal/s/db/action.go +11 -10
@@ 1,39 1,40 @@
package db

import (
	"context"
	"database/sql"
	"time"

	"git.sr.ht/~evanj/cms/internal/m/org"
)

func (db *DB) ActionNew(o org.Org, at time.Time) error {
	t, err := db.Begin()
func (db *DB) ActionNew(ctx context.Context, o org.Org, at time.Time) error {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	if err := db.actionNew(t, o, at); err != nil {
	if err := db.actionNew(ctx, t, o, at); err != nil {
		return err
	}

	return t.Commit()
}

func (db *DB) actionNew(t *sql.Tx, o org.Org, at time.Time) error {
	_, err := t.Exec("INSERT INTO cms_action (ORG_ID, AT) VALUES (?, ?)", o.ID(), at.Format("2006-01-02 03:04:05"))
func (db *DB) actionNew(ctx context.Context, t *sql.Tx, o org.Org, at time.Time) error {
	_, err := t.ExecContext(ctx, "INSERT INTO cms_action (ORG_ID, AT) VALUES (?, ?)", o.ID(), at.Format("2006-01-02 03:04:05"))
	return err
}

func (db *DB) ActionGetCount(o org.Org, from, to time.Time) (int, error) {
	t, err := db.Begin()
func (db *DB) ActionGetCount(ctx context.Context, o org.Org, from, to time.Time) (int, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}
	defer t.Rollback()

	i, err := db.actionGetCount(t, o, from, to)
	i, err := db.actionGetCount(ctx, t, o, from, to)
	if err != nil {
		return 0, err
	}


@@ 41,7 42,7 @@ func (db *DB) ActionGetCount(o org.Org, from, to time.Time) (int, error) {
	return i, t.Commit()
}

func (db *DB) actionGetCount(t *sql.Tx, o org.Org, from, to time.Time) (int, error) {
func (db *DB) actionGetCount(ctx context.Context, t *sql.Tx, o org.Org, from, to time.Time) (int, error) {
	var (
		count int
		q     = "SELECT COUNT(*) FROM cms_action WHERE cms_action.ORG_ID=? AND AT>? AND AT<?"


@@ 49,7 50,7 @@ func (db *DB) actionGetCount(t *sql.Tx, o org.Org, from, to time.Time) (int, err

	a := from.Format("2006-01-02 03:04:05")
	b := to.Format("2006-01-02 03:04:05")
	if err := t.QueryRow(q, o.ID(), a, b).Scan(&count); err != nil {
	if err := t.QueryRowContext(ctx, q, o.ID(), a, b).Scan(&count); err != nil {
		return 0, err
	}


M internal/s/db/content.go => internal/s/db/content.go +86 -84
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"


@@ 355,7 356,7 @@ var (
	`
)

func (db *DB) valueReferenceListUpdate(s space.Space, ct contenttype.ContentType, c *Content, t *sql.Tx, valueID string, IDs []string, depth int) error {
func (db *DB) valueReferenceListUpdate(ctx context.Context, s space.Space, ct contenttype.ContentType, c *Content, t *sql.Tx, valueID string, IDs []string, depth int) error {
	if len(IDs) < 1 {
		return fmt.Errorf("reference list type has no values")
	}


@@ 364,38 365,38 @@ func (db *DB) valueReferenceListUpdate(s space.Space, ct contenttype.ContentType
	// create new list values
	// TODO: Remove these inline queries, please.
	var refListID string
	if err := t.QueryRow("SELECT cms_value_reference_list.ID FROM cms_value JOIN cms_value_reference_list ON cms_value_reference_list.ID = cms_value.VALUE_ID WHERE cms_value.ID = ?", valueID).Scan(&refListID); err != nil {
	if err := t.QueryRowContext(ctx, "SELECT cms_value_reference_list.ID FROM cms_value JOIN cms_value_reference_list ON cms_value_reference_list.ID = cms_value.VALUE_ID WHERE cms_value.ID = ?", valueID).Scan(&refListID); err != nil {
		return fmt.Errorf("failed to query for reference list identifier before reference list update")
	}

	if _, err := t.Exec("DELETE FROM cms_value_reference_list_values WHERE VALUE_ID = ?", refListID); err != nil {
	if _, err := t.ExecContext(ctx, "DELETE FROM cms_value_reference_list_values WHERE VALUE_ID = ?", refListID); err != nil {
		return fmt.Errorf("failed to remove old reference list values before update")
	}

	var value ContentValue
	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 {
	if err := t.QueryRowContext(ctx, queryValueGetReferenceListByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName); err != nil {
		return err
	}

	for _, cid := range IDs {
		_, err := t.Exec("INSERT INTO cms_value_reference_list_values (VALUE_ID, CONTENT_ID) VALUES (?, ?);", refListID, cid)
		_, err := t.ExecContext(ctx, "INSERT INTO cms_value_reference_list_values (VALUE_ID, CONTENT_ID) VALUES (?, ?);", refListID, cid)
		if err != nil {
			return err
		}
	}

	db.contentValueAttachRefList(t, &value, depth)
	db.contentValueAttachRefList(ctx, t, &value, depth)
	c.ContentValues = append(c.ContentValues, value)
	return nil
}

func (db *DB) valueReferenceListNew(s space.Space, ct contenttype.ContentType, c *Content, t *sql.Tx, fieldName string, IDs []string, depth int) error {
func (db *DB) valueReferenceListNew(ctx context.Context, s space.Space, ct contenttype.ContentType, c *Content, t *sql.Tx, fieldName string, IDs []string, depth int) error {
	if len(IDs) < 1 {
		return fmt.Errorf("reference list type has no values")
	}

	res, err := t.Exec("INSERT INTO cms_value_reference_list () VALUES ();")
	res, err := t.ExecContext(ctx, "INSERT INTO cms_value_reference_list () VALUES ();")
	if err != nil {
		return fmt.Errorf("failed to attach field value of content")
	}


@@ 405,7 406,7 @@ func (db *DB) valueReferenceListNew(s space.Space, ct contenttype.ContentType, c
		return fmt.Errorf("failed to read new field value of content")
	}

	res, err = t.Exec(queryValueNew, c.ID(), ct.ID(), fieldName, refListID)
	res, err = t.ExecContext(ctx, queryValueNew, c.ID(), ct.ID(), fieldName, refListID)
	if err != nil {
		return fmt.Errorf("failed to attach field value of content")
	}


@@ 417,24 418,24 @@ func (db *DB) valueReferenceListNew(s space.Space, ct contenttype.ContentType, c

	var value ContentValue
	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 {
	if err := t.QueryRowContext(ctx, queryValueGetReferenceListByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName); err != nil {
		return err
	}

	for _, cid := range IDs {
		_, err := t.Exec("INSERT INTO cms_value_reference_list_values (VALUE_ID, CONTENT_ID) VALUES (?, ?);", refListID, cid)
		_, err := t.ExecContext(ctx, "INSERT INTO cms_value_reference_list_values (VALUE_ID, CONTENT_ID) VALUES (?, ?);", refListID, cid)
		if err != nil {
			return err
		}
	}

	db.contentValueAttachRefList(t, &value, depth)
	db.contentValueAttachRefList(ctx, t, &value, depth)
	c.ContentValues = append(c.ContentValues, value)
	return nil
}

func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentType, params []ContentNewParam, depth int) (content.Content, error) {
	res, err := t.Exec(queryContentNew, ct.ID())
func (db *DB) contentNew(ctx context.Context, t *sql.Tx, space space.Space, ct contenttype.ContentType, params []ContentNewParam, depth int) (content.Content, error) {
	res, err := t.ExecContext(ctx, queryContentNew, ct.ID())
	if err != nil {
		return nil, fmt.Errorf("failed to create new content attached to contenttype of '%s'", ct.Name())
	}


@@ 445,7 446,7 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
	}

	var content Content
	if err := t.QueryRow(queryContentGetByID, contentID).Scan(&content.ContentID, &content.ContentParentTypeID); err != nil {
	if err := t.QueryRowContext(ctx, queryContentGetByID, contentID).Scan(&content.ContentID, &content.ContentParentTypeID); err != nil {
		return nil, fmt.Errorf("failed to find content created")
	}



@@ 457,7 458,7 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
				continue
			}

			if err := db.valueReferenceListNew(space, ct, &content, t, item.Name, strings.Split(item.Value, "-"), depth); err != nil {
			if err := db.valueReferenceListNew(ctx, space, ct, &content, t, item.Name, strings.Split(item.Value, "-"), depth); err != nil {
				return nil, err
			}



@@ 474,7 475,7 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
			return nil, err
		}

		res, err := t.Exec(queryValueNewType, item.Value)
		res, err := t.ExecContext(ctx, queryValueNewType, item.Value)
		if err != nil {
			return nil, fmt.Errorf("failed to attach field value of content")
		}


@@ 484,7 485,7 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
			return nil, fmt.Errorf("failed to read new field value of content")
		}

		res, err = t.Exec(queryValueNew, contentID, ct.ID(), item.Name, valueID)
		res, err = t.ExecContext(ctx, queryValueNew, contentID, ct.ID(), item.Name, valueID)
		if err != nil {
			return nil, fmt.Errorf("failed to attach field value of content")
		}


@@ 495,11 496,11 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
		}

		var value ContentValue
		if err := t.QueryRow(queryValueGetTypeByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
		if err := t.QueryRowContext(ctx, queryValueGetTypeByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
			return nil, fmt.Errorf("failed to find value created")
		}

		if err := db.contentValueAttachRef(t, &value, depth); err != nil {
		if err := db.contentValueAttachRef(ctx, t, &value, depth); err != nil {
			return nil, err
		}



@@ 513,14 514,14 @@ func (db *DB) contentNew(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
	return &content, nil
}

func (db *DB) ContentNew(u user.User, space space.Space, ct contenttype.ContentType, params []ContentNewParam) (content.Content, error) {
	t, err := db.Begin()
func (db *DB) ContentNew(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, params []ContentNewParam) (content.Content, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	c, err := db.contentNew(t, space, ct, params, defaultDepth)
	c, err := db.contentNew(ctx, t, space, ct, params, defaultDepth)
	if err != nil {
		return nil, err
	}


@@ 532,7 533,7 @@ type ContentRefSet struct {
	ContentTypeID, ContentID string
}

func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []ContentRefSet, err error) {
func (db *DB) contentRefererList(ctx context.Context, t *sql.Tx, contentID string, depth int) (ret []ContentRefSet, err error) {
	// Cap total recursion to defaultDepth
	if depth < 1 {
		return ret, nil


@@ 559,7 560,7 @@ func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []
		AND cms_value_reference_list_values.CONTENT_ID = ?
	`

	rows, err := t.Query(refQ, valuetype.Reference, contentID)
	rows, err := t.QueryContext(ctx, refQ, valuetype.Reference, contentID)
	if err != nil {
		return ret, err
	}


@@ 579,7 580,7 @@ func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []
	}

	for _, ref := range refs {
		nested, err := db.contentRefererList(t, ref.ContentID, depth)
		nested, err := db.contentRefererList(ctx, t, ref.ContentID, depth)
		if err != nil {
			return nil, err
		}


@@ 588,7 589,7 @@ func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []
		ret = append(ret, nested...)
	}

	rows, err = t.Query(refListQ, valuetype.ReferenceList, contentID)
	rows, err = t.QueryContext(ctx, refListQ, valuetype.ReferenceList, contentID)
	if err != nil {
		return ret, err
	}


@@ 608,7 609,7 @@ func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []
	}

	for _, ref := range refs {
		nested, err := db.contentRefererList(t, ref.ContentID, depth)
		nested, err := db.contentRefererList(ctx, t, ref.ContentID, depth)
		if err != nil {
			return nil, err
		}


@@ 621,14 622,14 @@ func (db *DB) contentRefererList(t *sql.Tx, contentID string, depth int) (ret []
}

// ContentRefererList will retreive all content IDs that references a given piece of content.
func (db *DB) ContentRefererList(contentID string) (ret []ContentRefSet, err error) {
	t, err := db.Begin()
func (db *DB) ContentRefererList(ctx context.Context, contentID string) (ret []ContentRefSet, err error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return ret, err
	}
	defer t.Rollback()

	list, err := db.contentRefererList(t, contentID, defaultDepth)
	list, err := db.contentRefererList(ctx, t, contentID, defaultDepth)
	if err != nil {
		return ret, err
	}


@@ 636,7 637,7 @@ func (db *DB) ContentRefererList(contentID string) (ret []ContentRefSet, err err
	return list, t.Commit()
}

func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) error {
func (db *DB) contentUpdate(ctx context.Context, t *sql.Tx, space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) error {
	depth := defaultDepth

	// TODO: Do we have to do this w/ types? Can you not find a better way?


@@ 654,7 655,7 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
				continue
			}

			if err := db.valueReferenceListUpdate(space, ct, c, t, item.ID, strings.Split(item.Value, "-"), depth); err != nil {
			if err := db.valueReferenceListUpdate(ctx, space, ct, c, t, item.ID, strings.Split(item.Value, "-"), depth); err != nil {
				return err
			}
			continue


@@ 670,7 671,7 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
			return err
		}

		if _, err := t.Exec(queryValueUpdate, item.Value, item.ID); err != nil {
		if _, err := t.ExecContext(ctx, queryValueUpdate, item.Value, item.ID); err != nil {
			return fmt.Errorf("failed to create update content value '%s'", item.Value)
		}
	}


@@ 683,7 684,7 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
				continue
			}

			if err := db.valueReferenceListNew(space, ct, c, t, item.Name, strings.Split(item.Value, "-"), depth); err != nil {
			if err := db.valueReferenceListNew(ctx, space, ct, c, t, item.Name, strings.Split(item.Value, "-"), depth); err != nil {
				return err
			}
			continue


@@ 699,7 700,7 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
			return err
		}

		res, err := t.Exec(queryValueNewType, item.Value)
		res, err := t.ExecContext(ctx, queryValueNewType, item.Value)
		if err != nil {
			return fmt.Errorf("failed to attach field value of content")
		}


@@ 709,7 710,7 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
			return fmt.Errorf("failed to read new field value of content")
		}

		res, err = t.Exec(queryValueNew, content.ID(), ct.ID(), item.Name, valueID)
		res, err = t.ExecContext(ctx, queryValueNew, content.ID(), ct.ID(), item.Name, valueID)
		if err != nil {
			return fmt.Errorf("failed to attach field value of content")
		}


@@ 720,11 721,11 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
		}

		var value ContentValue
		if err := t.QueryRow(queryValueGetTypeByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
		if err := t.QueryRowContext(ctx, queryValueGetTypeByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
			return fmt.Errorf("failed to find value created")
		}

		if err := db.contentValueAttachRef(t, &value, depth); err != nil {
		if err := db.contentValueAttachRef(ctx, t, &value, depth); err != nil {
			return err
		}



@@ 734,14 735,14 @@ func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.Content
	return nil
}

func (db *DB) ContentUpdate(u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) (content.Content, error) {
	t, err := db.Begin()
func (db *DB) ContentUpdate(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) (content.Content, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	if err := db.contentUpdate(t, space, ct, content, newParams, updateParams); err != nil {
	if err := db.contentUpdate(ctx, t, space, ct, content, newParams, updateParams); err != nil {
		return nil, err
	}



@@ 749,18 750,18 @@ func (db *DB) ContentUpdate(u user.User, space space.Space, ct contenttype.Conte
		return nil, err
	}

	return db.ContentGet(u, space, ct, content.ID())
	return db.ContentGet(ctx, u, space, ct, content.ID())
}

func (db *DB) ContentDelete(u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error {
	t, err := db.Begin()
func (db *DB) ContentDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	for _, q := range queryContentDelete {
		_, err := t.Exec(q, content.ID())
		_, err := t.ExecContext(ctx, q, content.ID())
		if err != nil {
			return err
		}


@@ 769,7 770,7 @@ func (db *DB) ContentDelete(u user.User, space space.Space, ct contenttype.Conte
	return t.Commit()
}

func sortinfo(t *sql.Tx, ct contenttype.ContentType, sortField string) (string, string, error) {
func sortinfo(ctx context.Context, t *sql.Tx, ct contenttype.ContentType, sortField string) (string, string, error) {
	var (
		sortFieldValueType string
		sortFieldTableName string


@@ 780,7 781,7 @@ func sortinfo(t *sql.Tx, ct contenttype.ContentType, sortField string) (string, 
		JOIN cms_valuetype ON cms_valuetype.ID=cms_contenttype_to_valuetype.VALUETYPE_ID
		WHERE NAME=? AND CONTENTTYPE_ID=?
	`
	if err := t.QueryRow(q, sortField, ct.ID()).Scan(&sortFieldValueType); err != nil {
	if err := t.QueryRowContext(ctx, q, sortField, ct.ID()).Scan(&sortFieldValueType); err != nil {
		return "", "", err
	}



@@ 813,7 814,7 @@ func sortinfo(t *sql.Tx, ct contenttype.ContentType, sortField string) (string, 
	return sortFieldValueType, sortFieldTableName, nil
}

func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, ct contenttype.ContentType, before int, order OrderType, sortField string, depth int) (content.ContentList, error) {
func (db *DB) contentPerContentType(ctx context.Context, t *sql.Tx, u user.User, space space.Space, ct contenttype.ContentType, before int, order OrderType, sortField string, depth int) (content.ContentList, error) {
	var (
		tmpID        int
		tmpContentID string


@@ 829,7 830,7 @@ func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, c
	tbl := fmt.Sprintf("cms_tmp_cl_by_ct_%s", strings.ReplaceAll(uuid.New().String(), "-", "_"))

	// Get the table the sortField is part of.
	_, sortFieldTableName, err := sortinfo(t, ct, sortField)
	_, sortFieldTableName, err := sortinfo(ctx, t, ct, sortField)
	if err != nil {
		return nil, err
	}


@@ 841,7 842,7 @@ func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, c
			CONTENT_ID INTEGER NOT NULL
		)
	`, tbl)
	if _, err := t.Exec(q); err != nil {
	if _, err := t.ExecContext(ctx, q); err != nil {
		return nil, err
	}



@@ 864,13 865,13 @@ func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, c

		ORDER BY %s.VALUE %s
	`, tbl, sortFieldTableName, sortFieldTableName, sortFieldTableName, order.val)
	if _, err := t.Exec(q, ct.ID(), sortField); err != nil {
	if _, err := t.ExecContext(ctx, q, ct.ID(), sortField); err != nil {
		return nil, err
	}

	// Query the temporary table.
	q = fmt.Sprintf("SELECT ID, CONTENT_ID FROM %s WHERE ID < ? ORDER BY ID DESC LIMIT ?", tbl)
	rows, err := t.Query(q, before, perPage+1)
	rows, err := t.QueryContext(ctx, q, before, perPage+1)
	if err != nil {
		return nil, err
	}


@@ 894,7 895,7 @@ func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, c
	}

	for _, id := range ids {
		c, err := db.contentGet(t, space, ct, id, defaultDepth)
		c, err := db.contentGet(ctx, t, space, ct, id, defaultDepth)
		if err != nil {
			return nil, err
		}


@@ 905,14 906,14 @@ func (db *DB) contentPerContentType(t *sql.Tx, u user.User, space space.Space, c
	return newContentList(r, hasMore, tmpID), nil
}

func (db *DB) ContentPerContentType(u user.User, space space.Space, ct contenttype.ContentType, before int, order OrderType, sortField string) (content.ContentList, error) {
	t, err := db.Begin()
func (db *DB) ContentPerContentType(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, before int, order OrderType, sortField string) (content.ContentList, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	list, err := db.contentPerContentType(t, u, space, ct, before, order, sortField, defaultDepth)
	list, err := db.contentPerContentType(ctx, t, u, space, ct, before, order, sortField, defaultDepth)
	if err != nil {
		return nil, err
	}


@@ 920,7 921,7 @@ func (db *DB) ContentPerContentType(u user.User, space space.Space, ct contentty
	return list, t.Commit()
}

func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.ContentType, sortField, query string, before int) (content.ContentList, error) {
func (db *DB) ContentSearch(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, sortField, query string, before int) (content.ContentList, error) {
	var (
		tmpID        int
		tmpContentID string


@@ 932,7 933,7 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte

	before = beformat(before)

	t, err := db.Begin()
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}


@@ 942,7 943,7 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte
	tbl := fmt.Sprintf("cms_tmp_cl_search_%s", strings.ReplaceAll(uuid.New().String(), "-", "_"))

	// Get the table the sortField is part of.
	_, sortFieldTableName, err := sortinfo(t, ct, sortField)
	_, sortFieldTableName, err := sortinfo(ctx, t, ct, sortField)
	if err != nil {
		return nil, err
	}


@@ 954,7 955,7 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte
			CONTENT_ID INTEGER NOT NULL
		)
	`, tbl)
	if _, err := t.Exec(q); err != nil {
	if _, err := t.ExecContext(ctx, q); err != nil {
		return nil, err
	}



@@ 976,13 977,13 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte
	 	AND cms_contenttype_to_valuetype.NAME = ?
		AND %s.VALUE LIKE ?
	`, tbl, sortFieldTableName, sortFieldTableName, sortFieldTableName)
	if _, err := t.Exec(q, ct.ID(), sortField, s); err != nil {
	if _, err := t.ExecContext(ctx, q, ct.ID(), sortField, s); err != nil {
		return nil, err
	}

	// Query the temporary table.
	q = fmt.Sprintf("SELECT ID, CONTENT_ID FROM %s WHERE ID < ? ORDER BY ID DESC LIMIT ?", tbl)
	rows, err := t.Query(q, before, perPage+1)
	rows, err := t.QueryContext(ctx, q, before, perPage+1)
	if err != nil {
		return nil, err
	}


@@ 1006,7 1007,7 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte
	}

	for _, tmpContentID := range ids {
		c, err := db.contentGet(t, space, ct, tmpContentID, defaultDepth)
		c, err := db.contentGet(ctx, t, space, ct, tmpContentID, defaultDepth)
		if err != nil {
			return nil, err
		}


@@ 1017,15 1018,15 @@ func (db *DB) ContentSearch(u user.User, space space.Space, ct contenttype.Conte
	return newContentList(r, hasMore, tmpID), t.Commit()
}

func (db *DB) contentGet(t *sql.Tx, space space.Space, ct contenttype.ContentType, contentID string, depth int) (content.Content, error) {
func (db *DB) contentGet(ctx context.Context, t *sql.Tx, space space.Space, ct contenttype.ContentType, contentID string, depth int) (content.Content, error) {
	var content Content
	if err := t.QueryRow(queryContentGetByID, contentID).Scan(&content.ContentID, &content.ContentParentTypeID); err != nil {
	if err := t.QueryRowContext(ctx, queryContentGetByID, contentID).Scan(&content.ContentID, &content.ContentParentTypeID); err != nil {
		return nil, fmt.Errorf("failed to find content")
	}

	// TODO: For some reason t.Query(...) is causing errors here.
	// TODO: For some reason t.QueryContext(ctx, ...) is causing errors here.
	// See: https://github.com/go-sql-driver/mysql/issues/314
	rows, err := t.Query(queryValueListByContent, content.ID(), content.ID(), content.ID(), content.ID(), content.ID())
	rows, err := t.QueryContext(ctx, queryValueListByContent, content.ID(), content.ID(), content.ID(), content.ID(), content.ID())
	if err != nil {
		return nil, fmt.Errorf("failed to find value(s)")
	}


@@ 1044,11 1045,11 @@ func (db *DB) contentGet(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
	}

	for _, value := range values {
		if err := db.contentValueAttachRef(t, &value, depth); err != nil {
		if err := db.contentValueAttachRef(ctx, t, &value, depth); err != nil {
			return nil, err
		}

		if err := db.contentValueAttachRefList(t, &value, depth); err != nil {
		if err := db.contentValueAttachRefList(ctx, t, &value, depth); err != nil {
			return nil, err
		}



@@ 1058,7 1059,7 @@ func (db *DB) contentGet(t *sql.Tx, space space.Space, ct contenttype.ContentTyp
	return &content, nil
}

func (db *DB) ContentGet(u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
func (db *DB) ContentGet(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
	if space == nil {
		return nil, fmt.Errorf("must provide parent space")
	}


@@ 1067,13 1068,13 @@ func (db *DB) ContentGet(u user.User, space space.Space, ct contenttype.ContentT
		return nil, fmt.Errorf("must provide parent contenttype")
	}

	t, err := db.Begin()
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	c, err := db.contentGet(t, space, ct, contentID, defaultDepth)
	c, err := db.contentGet(ctx, t, space, ct, contentID, defaultDepth)
	if err != nil {
		return nil, err
	}


@@ 1107,13 1108,13 @@ func (db *DB) valueQuerySetByType(typ valuetype.ValueTypeEnum) (insert, get, upd
	return "", "", "", fmt.Errorf("%s is not a valid valuetype", typ)
}

func (db *DB) contentValueAttachRef(t *sql.Tx, c *ContentValue, depth int) error {
func (db *DB) contentValueAttachRef(ctx context.Context, t *sql.Tx, c *ContentValue, depth int) error {
	depth--
	if c.Type() != valuetype.Reference || depth < 1 {
		return nil
	}

	ref, err := db.contentGet(t, nil, nil, c.Value(), depth)
	ref, err := db.contentGet(ctx, t, nil, nil, c.Value(), depth)
	if err != nil {
		return err
	}


@@ 1122,13 1123,13 @@ func (db *DB) contentValueAttachRef(t *sql.Tx, c *ContentValue, depth int) error
	return nil
}

func (db *DB) contentValueAttachRefList(t *sql.Tx, c *ContentValue, depth int) error {
func (db *DB) contentValueAttachRefList(ctx context.Context, t *sql.Tx, c *ContentValue, depth int) error {
	depth--
	if c.Type() != valuetype.ReferenceList || depth < 1 {
		return nil
	}

	rows, err := t.Query(queryValueGetReferenceListValuesByID, c.ID())
	rows, err := t.QueryContext(ctx, queryValueGetReferenceListValuesByID, c.ID())
	if err != nil {
		return err
	}


@@ 1148,7 1149,7 @@ func (db *DB) contentValueAttachRefList(t *sql.Tx, c *ContentValue, depth int) e

	var ids []string
	for _, value := range values {
		ref, err := db.contentGet(t, nil, nil, value.FieldValue, depth)
		ref, err := db.contentGet(ctx, t, nil, nil, value.FieldValue, depth)
		if err != nil {
			return err
		}


@@ 1291,6 1292,7 @@ func (c *ContentValue) UnmarshalJSON(b []byte) error {

type contentIter struct {
	db        *DB
	ctx       context.Context
	t         *sql.Tx // Not used for the moment (we only use simple queries).
	user      user.User
	space     space.Space


@@ 1302,22 1304,22 @@ type contentIter struct {
	err  error
}

func (db *DB) contentIter(t *sql.Tx, user user.User, space space.Space, ct contenttype.ContentType, sortField string) *contentIter {
	iter := &contentIter{db, t, user, space, ct, sortField, newContentList(nil, false, 0), nil}
func (db *DB) contentIter(ctx context.Context, t *sql.Tx, user user.User, space space.Space, ct contenttype.ContentType, sortField string) *contentIter {
	iter := &contentIter{db, ctx, t, user, space, ct, sortField, newContentList(nil, false, 0), nil}
	iter.pump()
	return iter
}

func (db *DB) ContentIter(u user.User, space space.Space, ct contenttype.ContentType, sortField string) (*contentIter, *sql.Tx, error) {
	t, err := db.Begin()
func (db *DB) ContentIter(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, sortField string) (*contentIter, *sql.Tx, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, nil, err
	}
	return db.contentIter(t, u, space, ct, sortField), t, nil
	return db.contentIter(ctx, t, u, space, ct, sortField), t, nil
}

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

M internal/s/db/contenttype.go => internal/s/db/contenttype.go +39 -37
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"


@@ 68,14 69,14 @@ var (
	`
)

func (db *DB) ContentTypeNew(u user.User, space space.Space, name string, params []ContentTypeNewParam) (contenttype.ContentType, error) {
	t, err := db.Begin()
func (db *DB) ContentTypeNew(ctx context.Context, u user.User, space space.Space, name string, params []ContentTypeNewParam) (contenttype.ContentType, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	res, err := t.Exec(queryCreateContentType, name, space.ID())
	res, err := t.ExecContext(ctx, queryCreateContentType, name, space.ID())
	if err != nil {
		return nil, err
	}


@@ 87,17 88,17 @@ func (db *DB) ContentTypeNew(u user.User, space space.Space, name string, params

	for _, item := range params {
		db.log.Println(item, id)
		if _, err := t.Exec(queryCreateContentTypeConnection, item.Name, id, item.Type); err != nil {
		if _, err := t.ExecContext(ctx, queryCreateContentTypeConnection, item.Name, id, item.Type); err != nil {
			return nil, fmt.Errorf("failed to create field(s)")
		}
	}

	var ct ContentType
	if err := t.QueryRow(queryFindContentTypeByID, id).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
	if err := t.QueryRowContext(ctx, queryFindContentTypeByID, id).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
		return nil, fmt.Errorf("failed to find user created")
	}

	rows, err := t.Query(queryFindValueTypes, ct.ContentTypeID)
	rows, err := t.QueryContext(ctx, queryFindValueTypes, ct.ContentTypeID)
	if err != nil {
		return nil, fmt.Errorf("failed to find field(s)")
	}


@@ 123,8 124,8 @@ func (db *DB) ContentTypeNew(u user.User, space space.Space, name string, params
// ContentTypeUpdate will either remove fields are add fields to a contenttype.
// Note: field types cannot be changed (e.g. if a field is of type InputHTML it
// cannot become a StringSmall. Field type names can be changed.
func (db *DB) ContentTypeUpdate(u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []ContentTypeNewParam, updateParams []ContentTypeUpdateParam) (contenttype.ContentType, error) {
	t, err := db.Begin()
func (db *DB) ContentTypeUpdate(ctx context.Context, u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []ContentTypeNewParam, updateParams []ContentTypeUpdateParam) (contenttype.ContentType, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}


@@ 135,7 136,7 @@ func (db *DB) ContentTypeUpdate(u user.User, space space.Space, contenttype cont

	var fieldIDs []int
	for _, p := range updateParams {
		_, err := t.Exec(queryUpdateContentTypeFieldNames, p.Name, p.ID)
		_, err := t.ExecContext(ctx, queryUpdateContentTypeFieldNames, p.Name, p.ID)
		if err != nil {
			return nil, err
		}


@@ 148,20 149,20 @@ func (db *DB) ContentTypeUpdate(u user.User, space space.Space, contenttype cont
		fieldIDs = append(fieldIDs, id)
	}

	if _, err := t.Exec(queryDeleteContentTypeFieldsNotIn(fieldIDs), contenttype.ID()); err != nil {
	if _, err := t.ExecContext(ctx, queryDeleteContentTypeFieldsNotIn(fieldIDs), contenttype.ID()); err != nil {
		return nil, err
	}

	// Update name of CT itself.

	if _, err := t.Exec(queryUpdateContentTypeName, name, contenttype.ID()); err != nil {
	if _, err := t.ExecContext(ctx, queryUpdateContentTypeName, name, contenttype.ID()); err != nil {
		return nil, err
	}

	// Create new fields given.

	for _, item := range newParams {
		if _, err := t.Exec(queryCreateContentTypeConnection, item.Name, contenttype.ID(), item.Type); err != nil {
		if _, err := t.ExecContext(ctx, queryCreateContentTypeConnection, item.Name, contenttype.ID(), item.Type); err != nil {
			return nil, fmt.Errorf("failed to create field(s)")
		}
	}


@@ 170,10 171,10 @@ func (db *DB) ContentTypeUpdate(u user.User, space space.Space, contenttype cont
		return nil, err
	}

	return db.ContentTypeGet(u, space, contenttype.ID())
	return db.ContentTypeGet(ctx, u, space, contenttype.ID())
}

func (db *DB) contentTypesPerSpace(t *sql.Tx, space space.Space, before int) (contenttype.ContentTypeList, error) {
func (db *DB) contentTypesPerSpace(ctx context.Context, t *sql.Tx, space space.Space, before int) (contenttype.ContentTypeList, error) {
	var (
		r       []contenttype.ContentType
		id      int


@@ 189,7 190,7 @@ func (db *DB) contentTypesPerSpace(t *sql.Tx, space space.Space, before int) (co
		ORDER BY ID DESC LIMIT ?
	`

	rows, err := t.Query(q, space.ID(), before, perPage+1)
	rows, err := t.QueryContext(ctx, q, space.ID(), before, perPage+1)
	if err != nil {
		return nil, err
	}


@@ 213,7 214,7 @@ func (db *DB) contentTypesPerSpace(t *sql.Tx, space space.Space, before int) (co
	}

	for _, id := range ids {
		ct, err := db.contentTypeGet(t, space, strconv.Itoa(id))
		ct, err := db.contentTypeGet(ctx, t, space, strconv.Itoa(id))
		if err != nil {
			return nil, err
		}


@@ 224,14 225,14 @@ func (db *DB) contentTypesPerSpace(t *sql.Tx, space space.Space, before int) (co
	return newContentTypeList(r, hasMore, id), nil
}

func (db *DB) ContentTypesPerSpace(u user.User, space space.Space, before int) (contenttype.ContentTypeList, error) {
	t, err := db.Begin()
func (db *DB) ContentTypesPerSpace(ctx context.Context, u user.User, space space.Space, before int) (contenttype.ContentTypeList, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	list, err := db.contentTypesPerSpace(t, space, before)
	list, err := db.contentTypesPerSpace(ctx, t, space, before)
	if err != nil {
		return nil, err
	}


@@ 239,13 240,13 @@ func (db *DB) ContentTypesPerSpace(u user.User, space space.Space, before int) (
	return list, t.Commit()
}

func (db *DB) contentTypeGet(t *sql.Tx, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
func (db *DB) contentTypeGet(ctx context.Context, t *sql.Tx, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	var ct ContentType
	if err := t.QueryRow(queryFindContentTypeByIDAndSpace, contenttypeID, space.ID()).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
	if err := t.QueryRowContext(ctx, queryFindContentTypeByIDAndSpace, contenttypeID, space.ID()).Scan(&ct.ContentTypeID, &ct.ContentTypeName); err != nil {
		return nil, fmt.Errorf("failed to find contenttype for space")
	}

	rows, err := t.Query(queryFindValueTypes, ct.ContentTypeID)
	rows, err := t.QueryContext(ctx, queryFindValueTypes, ct.ContentTypeID)
	if err != nil {
		return nil, fmt.Errorf("failed to find field(s)")
	}


@@ 265,14 266,14 @@ func (db *DB) contentTypeGet(t *sql.Tx, space space.Space, contenttypeID string)
	return &ct, nil
}

func (db *DB) ContentTypeGet(u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	t, err := db.Begin()
func (db *DB) ContentTypeGet(ctx context.Context, u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	ct, err := db.contentTypeGet(t, space, contenttypeID)
	ct, err := db.contentTypeGet(ctx, t, space, contenttypeID)
	if err != nil {
		return nil, err
	}


@@ 282,14 283,14 @@ func (db *DB) ContentTypeGet(u user.User, space space.Space, contenttypeID strin

// TODO: Consolidate with other list function here. They are the same except for
// the query used.
func (db *DB) ContentTypeSearch(u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
	t, err := db.Begin()
func (db *DB) ContentTypeSearch(ctx context.Context, u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	list, err := db.contentTypeSearch(t, u, space, query, before)
	list, err := db.contentTypeSearch(ctx, t, u, space, query, before)
	if err != nil {
		return nil, err
	}


@@ 297,7 298,7 @@ func (db *DB) ContentTypeSearch(u user.User, space space.Space, query string, be
	return list, t.Commit()
}

func (db *DB) contentTypeSearch(t *sql.Tx, u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
func (db *DB) contentTypeSearch(ctx context.Context, t *sql.Tx, u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
	var (
		r       []contenttype.ContentType
		id      int


@@ 309,7 310,7 @@ func (db *DB) contentTypeSearch(t *sql.Tx, u user.User, space space.Space, query
	// TODO: May want to make a temp table for this query for proper ordering.
	q := `SELECT ID FROM cms_contenttype WHERE NAME LIKE ? AND SPACE_ID = ? AND ID < ? ORDER BY ID DESC LIMIT ?`

	rows, err := t.Query(q, fmt.Sprintf("%%%s%%", query), space.ID(), before, perPage)
	rows, err := t.QueryContext(ctx, q, fmt.Sprintf("%%%s%%", query), space.ID(), before, perPage)
	if err != nil {
		return nil, err
	}


@@ 328,7 329,7 @@ func (db *DB) contentTypeSearch(t *sql.Tx, u user.User, space space.Space, query
	}

	for _, id := range ids {
		ct, err := db.contentTypeGet(t, space, strconv.Itoa(id))
		ct, err := db.contentTypeGet(ctx, t, space, strconv.Itoa(id))
		if err != nil {
			return nil, err
		}


@@ 339,14 340,14 @@ func (db *DB) contentTypeSearch(t *sql.Tx, u user.User, space space.Space, query
	return newContentTypeList(r, hasMore, id), nil
}

func (db *DB) ContentTypeDelete(u user.User, space space.Space, ct contenttype.ContentType) error {
	t, err := db.Begin()
func (db *DB) ContentTypeDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType) error {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	if _, err := t.Exec(queryDeleteContentType, ct.ID()); err != nil {
	if _, err := t.ExecContext(ctx, queryDeleteContentType, ct.ID()); err != nil {
		return err
	}



@@ 399,6 400,7 @@ func (f *ContentTypeField) Type() string {

type contentTypeIter struct {
	db    *DB
	ctx   context.Context
	t     *sql.Tx
	space space.Space



@@ 407,14 409,14 @@ type contentTypeIter struct {
	err  error
}

func (db *DB) ContentTypeIter(t *sql.Tx, u user.User, space space.Space) *contentTypeIter {
	iter := &contentTypeIter{db, t, space, newContentTypeList(nil, false, 0), nil}
func (db *DB) ContentTypeIter(ctx context.Context, t *sql.Tx, u user.User, space space.Space) *contentTypeIter {
	iter := &contentTypeIter{db, ctx, t, space, newContentTypeList(nil, false, 0), nil}
	iter.pump()
	return iter
}

func (iter *contentTypeIter) pump() {
	list, err := iter.db.contentTypesPerSpace(iter.t, iter.space, iter.list.Before())
	list, err := iter.db.contentTypesPerSpace(iter.ctx, iter.t, iter.space, iter.list.Before())
	iter.list = list
	iter.err = err
}

M internal/s/db/db.go => internal/s/db/db.go +17 -16
@@ 2,6 2,7 @@ package db

import (
	"bytes"
	"context"
	"database/sql"
	"html/template"
	"log"


@@ 18,7 19,7 @@ import (

//go:generate embed -pattern */* -id migrations

func newMigrationSlice(m map[string]string) [][]string {
func newMigrationSlice(ctx context.Context, m map[string]string) [][]string {
	migrations := make([][]string, len(m))
	i := 0



@@ 93,7 94,7 @@ func New(log *log.Logger, typ, creds string, sec securer) *DB {
	return &DB{db, log, sec, err}
}

func (db *DB) Setup() error {
func (db *DB) Setup(ctx context.Context) error {
	if db.setupErr != nil {
		return db.setupErr
	}


@@ 102,7 103,7 @@ func (db *DB) Setup() error {
		return err
	}

	if err := db.migrate(); err != nil {
	if err := db.migrate(ctx); err != nil {
		return err
	}



@@ 119,24 120,24 @@ func (db *DB) Setup() error {
// migrate does our "migration" -migration in quotes as we just dummy
// attempt to create tables on every server startup and ignore "table already
// exists" errors.
func (db *DB) migrate() error {
func (db *DB) migrate(ctx context.Context) error {
	var (
		err error
		_   interface{}
	)

	t, err := db.Begin()
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	for _, migrationSet := range newMigrationSlice(migrations) {
	for _, migrationSet := range newMigrationSlice(ctx, migrations) {
		key := migrationSet[0]
		m := migrationSet[1]

		var count int
		if err := t.QueryRow("SELECT COUNT(*) FROM cms_migrate WHERE NAME=?", key).Scan(&count); err != nil {
		if err := t.QueryRowContext(ctx, "SELECT COUNT(*) FROM cms_migrate WHERE NAME=?", key).Scan(&count); err != nil {
			// Catch first error of DB setup.
			if !strings.Contains(err.Error(), "cms_migrate' doesn't exist") {
				return err


@@ 152,12 153,12 @@ func (db *DB) migrate() error {
			if q == "" {
				continue
			}
			if _, err := t.Exec(q); err != nil {
			if _, err := t.ExecContext(ctx, q); err != nil {
				return err
			}
		}

		if _, err := t.Exec("INSERT INTO cms_migrate (NAME) VALUES (?)", key); err != nil {
		if _, err := t.ExecContext(ctx, "INSERT INTO cms_migrate (NAME) VALUES (?)", key); err != nil {
			return err
		}
	}


@@ 175,7 176,7 @@ func (db *DB) migrate() error {

	for _, vt := range vtypes {
		var count int
		if err := t.QueryRow("SELECT COUNT(*) FROM cms_valuetype WHERE VALUE=?", vt).Scan(&count); err != nil {
		if err := t.QueryRowContext(ctx, "SELECT COUNT(*) FROM cms_valuetype WHERE VALUE=?", vt).Scan(&count); err != nil {
			return err
		}



@@ 183,7 184,7 @@ func (db *DB) migrate() error {
			continue
		}

		if _, err = t.Exec(`INSERT INTO cms_valuetype (VALUE) values (?);`, vt); err != nil {
		if _, err = t.ExecContext(ctx, `INSERT INTO cms_valuetype (VALUE) values (?);`, vt); err != nil {
			return err
		}
	}


@@ 193,13 194,13 @@ func (db *DB) migrate() error {

// FileExists makes sure SOME space and content owns the file. I.E. deleted
// spaces can't server files.
func (db *DB) FileExists(URL string) (bool, error) {
	t, err := db.Begin()
func (db *DB) FileExists(ctx context.Context, URL string) (bool, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return false, err
	}

	ok, err := db.fileExists(t, URL)
	ok, err := db.fileExists(ctx, t, URL)
	if err != nil {
		return false, err
	}


@@ 207,7 208,7 @@ func (db *DB) FileExists(URL string) (bool, error) {
	return ok, t.Commit()
}

func (db *DB) fileExists(t *sql.Tx, URL string) (bool, error) {
func (db *DB) fileExists(ctx context.Context, t *sql.Tx, URL string) (bool, error) {
	q := `
		SELECT cms_space.ID FROM cms_value_string_small 
		JOIN cms_value ON cms_value.VALUE_ID = cms_value_string_small.ID


@@ 220,7 221,7 @@ func (db *DB) fileExists(t *sql.Tx, URL string) (bool, error) {
	`

	var spaceID string
	if err := t.QueryRow(q, valuetype.File, URL).Scan(&spaceID); err != nil {
	if err := t.QueryRowContext(ctx, q, valuetype.File, URL).Scan(&spaceID); err != nil {
		return false, err
	}


M internal/s/db/hook.go => internal/s/db/hook.go +23 -21
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"strconv"



@@ 33,14 34,14 @@ type Hook struct {
	spaceID string
}

func (db *DB) HookNew(u user.User, space space.Space, url string) (hook.Hook, error) {
	t, err := db.Begin()
func (db *DB) HookNew(ctx context.Context, u user.User, space space.Space, url string) (hook.Hook, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	res, err := t.Exec(insert, space.ID(), url)
	res, err := t.ExecContext(ctx, insert, space.ID(), url)
	if err != nil {
		return nil, err
	}


@@ 51,30 52,30 @@ func (db *DB) HookNew(u user.User, space space.Space, url string) (hook.Hook, er
	}

	var hook Hook
	if err := t.QueryRow(query, id).Scan(&hook.HookID, &hook.HookURL, &hook.spaceID); err != nil {
	if err := t.QueryRowContext(ctx, query, id).Scan(&hook.HookID, &hook.HookURL, &hook.spaceID); err != nil {
		return nil, err
	}

	return &hook, t.Commit()
}

func (db *DB) hookGet(t *sql.Tx, space space.Space, id string) (hook.Hook, error) {
func (db *DB) hookGet(ctx context.Context, t *sql.Tx, space space.Space, id string) (hook.Hook, error) {
	var hook Hook
	if err := t.QueryRow(query, id).Scan(&hook.HookID, &hook.HookURL, &hook.spaceID); err != nil {
	if err := t.QueryRowContext(ctx, query, id).Scan(&hook.HookID, &hook.HookURL, &hook.spaceID); err != nil {
		return nil, err
	}

	return &hook, nil
}

func (db *DB) HookGet(u user.User, space space.Space, id string) (hook.Hook, error) {
	t, err := db.Begin()
func (db *DB) HookGet(ctx context.Context, u user.User, space space.Space, id string) (hook.Hook, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	h, err := db.hookGet(t, space, id)
	h, err := db.hookGet(ctx, t, space, id)
	if err != nil {
		return nil, err
	}


@@ 82,21 83,21 @@ func (db *DB) HookGet(u user.User, space space.Space, id string) (hook.Hook, err
	return h, t.Commit()
}

func (db *DB) HookDelete(u user.User, s space.Space, h hook.Hook) error {
	t, err := db.Begin()
func (db *DB) HookDelete(ctx context.Context, u user.User, s space.Space, h hook.Hook) error {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	if _, err := t.Exec(delete, h.ID()); err != nil {
	if _, err := t.ExecContext(ctx, delete, h.ID()); err != nil {
		return err
	}

	return t.Commit()
}

func (db *DB) hooksPerSpace(t *sql.Tx, space space.Space, before int) (hook.HookList, error) {
func (db *DB) hooksPerSpace(ctx context.Context, t *sql.Tx, space space.Space, before int) (hook.HookList, error) {
	var (
		r       []hook.Hook
		id      int


@@ 111,7 112,7 @@ func (db *DB) hooksPerSpace(t *sql.Tx, space space.Space, before int) (hook.Hook
		ORDER BY ID DESC LIMIT ?
	 `

	rows, err := t.Query(q, space.ID(), before, perPage+1)
	rows, err := t.QueryContext(ctx, q, space.ID(), before, perPage+1)
	if err != nil {
		return nil, err
	}


@@ 135,7 136,7 @@ func (db *DB) hooksPerSpace(t *sql.Tx, space space.Space, before int) (hook.Hook
	}

	for _, id := range ids {
		ct, err := db.hookGet(t, space, strconv.Itoa(id))
		ct, err := db.hookGet(ctx, t, space, strconv.Itoa(id))
		if err != nil {
			return nil, err
		}


@@ 146,14 147,14 @@ func (db *DB) hooksPerSpace(t *sql.Tx, space space.Space, before int) (hook.Hook
	return newHookList(r, hasMore, id), nil
}

func (db *DB) HooksPerSpace(u user.User, space space.Space, before int) (hook.HookList, error) {
	t, err := db.Begin()
func (db *DB) HooksPerSpace(ctx context.Context, u user.User, space space.Space, before int) (hook.HookList, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	list, err := db.hooksPerSpace(t, space, before)
	list, err := db.hooksPerSpace(ctx, t, space, before)
	if err != nil {
		return nil, err
	}


@@ 170,6 171,7 @@ func (h *Hook) URL() string { return h.HookURL }

type hookIter struct {
	db    *DB
	ctx   context.Context
	t     *sql.Tx
	space space.Space



@@ 178,14 180,14 @@ type hookIter struct {
	err  error
}

func (db *DB) HookIter(t *sql.Tx, u user.User, space space.Space) *hookIter {
	iter := &hookIter{db, t, space, newHookList(nil, false, 0), nil}
func (db *DB) HookIter(ctx context.Context, t *sql.Tx, u user.User, space space.Space) *hookIter {
	iter := &hookIter{db, ctx, t, space, newHookList(nil, false, 0), nil}
	iter.pump()
	return iter
}

func (iter *hookIter) pump() {
	list, err := iter.db.hooksPerSpace(iter.t, iter.space, iter.list.Before())
	list, err := iter.db.hooksPerSpace(iter.ctx, iter.t, iter.space, iter.list.Before())
	iter.list = list
	iter.err = err
}

M internal/s/db/invite.go => internal/s/db/invite.go +31 -30
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"


@@ 49,14 50,14 @@ var (
	mysqlTimeLayout = "2006-01-02 15:04:05"
)

func (db *DB) InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, error) {
	t, err := db.Begin()
func (db *DB) InviteNew(ctx context.Context, u user.User, o org.Org, r role.Role) (invite.Invite, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	i, err := db.inviteNew(t, o, r)
	i, err := db.inviteNew(ctx, t, o, r)
	if err != nil {
		return nil, err
	}


@@ 64,8 65,8 @@ func (db *DB) InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, err
	return i, t.Commit()
}

func (db *DB) inviteNew(t *sql.Tx, o org.Org, r role.Role) (invite.Invite, error) {
	res, err := t.Exec(queryCreate, time.Now().UTC().Add(1*time.Hour), o.ID(), r.Name)
func (db *DB) inviteNew(ctx context.Context, t *sql.Tx, o org.Org, r role.Role) (invite.Invite, error) {
	res, err := t.ExecContext(ctx, queryCreate, time.Now().UTC().Add(1*time.Hour), o.ID(), r.Name)
	if err != nil {
		return nil, err
	}


@@ 75,17 76,17 @@ func (db *DB) inviteNew(t *sql.Tx, o org.Org, r role.Role) (invite.Invite, error
		return nil, err
	}

	return db.inviteGet(t, strconv.FormatInt(id, 10))
	return db.inviteGet(ctx, t, strconv.FormatInt(id, 10))
}

func (db *DB) InviteGet(u user.User, id string) (invite.Invite, error) {
	t, err := db.Begin()
func (db *DB) InviteGet(ctx context.Context, u user.User, id string) (invite.Invite, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	i, err := db.inviteGet(t, id)
	i, err := db.inviteGet(ctx, t, id)
	if err != nil {
		return nil, err
	}


@@ 93,9 94,9 @@ func (db *DB) InviteGet(u user.User, id string) (invite.Invite, error) {
	return i, t.Commit()
}

func (db *DB) inviteGet(t *sql.Tx, id string) (invite.Invite, error) {
func (db *DB) inviteGet(ctx context.Context, t *sql.Tx, id string) (invite.Invite, error) {
	var i Invite
	if err := t.QueryRow(queryGet, id).Scan(
	if err := t.QueryRowContext(ctx, queryGet, id).Scan(
		&i.id, &i.createdStr, &i.expiresStr, &i.used,
		&i.InviteOrg.OrgID, &i.InviteOrg.OrgBillingTierName, &i.InviteOrg.OrgPaymentCustomer,
		&i.InviteRole.RoleID, &i.InviteRole.RoleName,


@@ 125,14 126,14 @@ func (db *DB) inviteGet(t *sql.Tx, id string) (invite.Invite, error) {
	return i, i.Validate()
}

func (db *DB) InviteAccept(i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
	t, err := db.Begin()
func (db *DB) InviteAccept(ctx context.Context, i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, nil, err
	}
	defer t.Rollback()

	user, invite, err := db.inviteAccept(t, i, u, p, v)
	user, invite, err := db.inviteAccept(ctx, t, i, u, p, v)
	if err != nil {
		return nil, nil, err
	}


@@ 140,22 141,22 @@ func (db *DB) InviteAccept(i invite.Invite, u, p, v string) (user.User, invite.I
	return user, invite, t.Commit()
}

func (db *DB) inviteAccept(t *sql.Tx, i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
	if _, err := t.Exec(queryUse, i.ID()); err != nil {
func (db *DB) inviteAccept(ctx context.Context, t *sql.Tx, i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
	if _, err := t.ExecContext(ctx, queryUse, i.ID()); err != nil {
		return nil, nil, err
	}

	// Create user.
	hash, err := db.userNewGeneratePassword(u, p, v)
	hash, err := db.userNewGeneratePassword(ctx, u, p, v)
	if err != nil {
		return nil, nil, err
	}

	if _, err := t.Exec(queryCreateNewUser, u, hash, i.Org().ID(), i.Role().Name); err != nil {
	if _, err := t.ExecContext(ctx, queryCreateNewUser, u, hash, i.Org().ID(), i.Role().Name); err != nil {
		return nil, nil, fmt.Errorf("user '%s' already exists", u)
	}

	user, err := db.userGet(t, u, p)
	user, err := db.userGet(ctx, t, u, p)
	if err != nil {
		return nil, nil, err
	}


@@ 163,11 164,11 @@ func (db *DB) inviteAccept(t *sql.Tx, i invite.Invite, u, p, v string) (user.Use
	return user, i, nil
}

func (db *DB) InviteList(u user.User, o org.Org) (r []invite.Invite, err error) {
	t, err := db.Begin()
func (db *DB) InviteList(ctx context.Context, u user.User, o org.Org) (r []invite.Invite, err error) {
	t, err := db.BeginTx(ctx, nil)
	defer t.Rollback()

	list, err := db.inviteList(t, u, o)
	list, err := db.inviteList(ctx, t, u, o)
	if err != nil {
		return nil, err
	}


@@ 175,13 176,13 @@ func (db *DB) InviteList(u user.User, o org.Org) (r []invite.Invite, err error) 
	return list, t.Commit()
}

func (db *DB) inviteList(t *sql.Tx, u user.User, o org.Org) (r []invite.Invite, err error) {
func (db *DB) inviteList(ctx context.Context, t *sql.Tx, u user.User, o org.Org) (r []invite.Invite, err error) {
	var (
		now  = time.Now().UTC()
		from = now.Format(mysqlTimeLayout)
	)

	rows, err := t.Query(queryList, o.ID(), from)
	rows, err := t.QueryContext(ctx, queryList, o.ID(), from)
	if err != nil {
		return nil, err
	}


@@ 201,7 202,7 @@ func (db *DB) inviteList(t *sql.Tx, u user.User, o org.Org) (r []invite.Invite, 
	}

	for _, id := range ids {
		i, err := db.inviteGet(t, id)
		i, err := db.inviteGet(ctx, t, id)
		if err != nil {
			return nil, err
		}


@@ 212,7 213,7 @@ func (db *DB) inviteList(t *sql.Tx, u user.User, o org.Org) (r []invite.Invite, 
	return r, nil
}

func (db *DB) inviteGetByToken(t *sql.Tx, tok string) (invite.Invite, error) {
func (db *DB) inviteGetByToken(ctx context.Context, t *sql.Tx, tok string) (invite.Invite, error) {
	tmap, err := db.sec.TokenFrom(tok)
	if err != nil {
		return nil, fmt.Errorf("failed to decode invite token")


@@ 228,7 229,7 @@ func (db *DB) inviteGetByToken(t *sql.Tx, tok string) (invite.Invite, error) {
		return nil, fmt.Errorf("corrupted invite token")
	}

	i, err := db.inviteGet(t, str)
	i, err := db.inviteGet(ctx, t, str)
	if err != nil {
		return nil, err
	}


@@ 240,14 241,14 @@ func (db *DB) inviteGetByToken(t *sql.Tx, tok string) (invite.Invite, error) {
	return i, nil
}

func (db *DB) InviteGetByToken(tok string) (invite.Invite, error) {
	t, err := db.Begin()
func (db *DB) InviteGetByToken(ctx context.Context, tok string) (invite.Invite, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	i, err := db.inviteGetByToken(t, tok)
	i, err := db.inviteGetByToken(ctx, t, tok)
	if err != nil {
		return nil, err
	}

M internal/s/db/org.go => internal/s/db/org.go +21 -20
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"

	"git.sr.ht/~evanj/cms/internal/m/org"


@@ 41,14 42,14 @@ var (
	`
)

func (db *DB) OrgNew() (org.Org, error) {
	tx, err := db.Begin()
func (db *DB) OrgNew(ctx context.Context) (org.Org, error) {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	org, err := db.orgNew(tx)
	org, err := db.orgNew(ctx, tx)
	if err != nil {
		return nil, err
	}


@@ 56,8 57,8 @@ func (db *DB) OrgNew() (org.Org, error) {
	return org, tx.Commit()
}

func (db *DB) orgNew(t *sql.Tx) (org.Org, error) {
	res, err := t.Exec("INSERT INTO cms_org () VALUES ()")
func (db *DB) orgNew(ctx context.Context, t *sql.Tx) (org.Org, error) {
	res, err := t.ExecContext(ctx, "INSERT INTO cms_org () VALUES ()")
	if err != nil {
		return nil, err
	}


@@ 68,39 69,39 @@ func (db *DB) orgNew(t *sql.Tx) (org.Org, error) {
	}

	var org Org
	if err := t.QueryRow(queryOrgByID, orgID).Scan(&org.OrgID, &org.OrgBillingTierName, &org.OrgPaymentCustomer); err != nil {
	if err := t.QueryRowContext(ctx, queryOrgByID, orgID).Scan(&org.OrgID, &org.OrgBillingTierName, &org.OrgPaymentCustomer); err != nil {
		return nil, err
	}

	return org, nil
}

func (db *DB) OrgUpdateTier(u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error {
	tx, err := db.Begin()
func (db *DB) OrgUpdateTier(ctx context.Context, u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	if _, err := tx.Exec("DELETE FROM cms_billing WHERE ORG_ID=?", o.ID()); err != nil {
	if _, err := tx.ExecContext(ctx, "DELETE FROM cms_billing WHERE ORG_ID=?", o.ID()); err != nil {
		return err
	}

	if _, err := tx.Exec("INSERT INTO cms_billing (PAYMENT_CUSTOMER, TIER_NAME, ORG_ID) values(?, ?, ?)", paymentCustomerID, t.Name, o.ID()); err != nil {
	if _, err := tx.ExecContext(ctx, "INSERT INTO cms_billing (PAYMENT_CUSTOMER, TIER_NAME, ORG_ID) values(?, ?, ?)", paymentCustomerID, t.Name, o.ID()); err != nil {
		return err
	}

	return tx.Commit()
}

func (db *DB) OrgGetSpaceCount(o org.Org) (int, error) {
	t, err := db.Begin()
func (db *DB) OrgGetSpaceCount(ctx context.Context, o org.Org) (int, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}
	defer t.Rollback()

	i, err := db.orgGetSpaceCount(t, o)
	i, err := db.orgGetSpaceCount(ctx, t, o)
	if err != nil {
		return 0, err
	}


@@ 108,27 109,27 @@ func (db *DB) OrgGetSpaceCount(o org.Org) (int, error) {
	return i, t.Commit()
}

func (db *DB) orgGetSpaceCount(t *sql.Tx, o org.Org) (int, error) {
func (db *DB) orgGetSpaceCount(ctx context.Context, t *sql.Tx, o org.Org) (int, error) {
	var (
		count int
		q     = "SELECT COUNT(*) FROM cms_space WHERE cms_space.ORG_ID=?"
	)

	if err := t.QueryRow(q, o.ID()).Scan(&count); err != nil {
	if err := t.QueryRowContext(ctx, q, o.ID()).Scan(&count); err != nil {
		return 0, err
	}

	return count, nil
}

func (db *DB) OrgGetUserCount(o org.Org) (int, error) {
	t, err := db.Begin()
func (db *DB) OrgGetUserCount(ctx context.Context, o org.Org) (int, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}
	defer t.Rollback()

	i, err := db.orgGetUserCount(t, o)
	i, err := db.orgGetUserCount(ctx, t, o)
	if err != nil {
		return 0, err
	}


@@ 136,13 137,13 @@ func (db *DB) OrgGetUserCount(o org.Org) (int, error) {
	return i, t.Commit()
}

func (db *DB) orgGetUserCount(t *sql.Tx, o org.Org) (int, error) {
func (db *DB) orgGetUserCount(ctx context.Context, t *sql.Tx, o org.Org) (int, error) {
	var (
		count int
		q     = "SELECT COUNT(*) FROM cms_user WHERE cms_user.ORG_ID=?"
	)

	if err := t.QueryRow(q, o.ID()).Scan(&count); err != nil {
	if err := t.QueryRowContext(ctx, q, o.ID()).Scan(&count); err != nil {
		return 0, err
	}


M internal/s/db/space.go => internal/s/db/space.go +43 -42
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"errors"
	"fmt"


@@ 66,8 67,8 @@ var (
	`
)

func (db *DB) spaceNew(t *sql.Tx, user user.User, name, desc string) (space.Space, error) {
	res, err := t.Exec(queryCreateNewSpace, name, desc, user.Org().ID())
func (db *DB) spaceNew(ctx context.Context, t *sql.Tx, user user.User, name, desc string) (space.Space, error) {
	res, err := t.ExecContext(ctx, queryCreateNewSpace, name, desc, user.Org().ID())
	if err != nil {
		return nil, fmt.Errorf("space '%s' already exists", name)
	}


@@ 77,17 78,17 @@ func (db *DB) spaceNew(t *sql.Tx, user user.User, name, desc string) (space.Spac
		return nil, fmt.Errorf("failed to create space")
	}

	return db.spaceGet(t, user, strconv.FormatInt(id, 10))
	return db.spaceGet(ctx, t, user, strconv.FormatInt(id, 10))
}

func (db *DB) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	t, err := db.Begin()
func (db *DB) SpaceNew(ctx context.Context, user user.User, name, desc string) (space.Space, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	space, err := db.spaceNew(t, user, name, desc)
	space, err := db.spaceNew(ctx, t, user, name, desc)
	if err != nil {
		return nil, err
	}


@@ 95,14 96,14 @@ func (db *DB) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	return space, t.Commit()
}

func (db *DB) SpaceUpdate(user user.User, space space.Space, name, desc string) (space.Space, error) {
	t, err := db.Begin()
func (db *DB) SpaceUpdate(ctx context.Context, user user.User, space space.Space, name, desc string) (space.Space, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	res, err := t.Exec(queryUpdateSpace, name, desc, space.ID())
	res, err := t.ExecContext(ctx, queryUpdateSpace, name, desc, space.ID())
	if err != nil {
		return nil, err
	}


@@ 117,7 118,7 @@ func (db *DB) SpaceUpdate(user user.User, space space.Space, name, desc string) 
		return nil, errors.New("space was not updated")
	}

	next, err := db.spaceGet(t, user, space.ID())
	next, err := db.spaceGet(ctx, t, user, space.ID())
	if err != nil {
		return nil, err
	}


@@ 125,14 126,14 @@ func (db *DB) SpaceUpdate(user user.User, space space.Space, name, desc string) 
	return next, t.Commit()
}

func (db *DB) SpaceCopy(user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	t, err := db.Begin()
func (db *DB) SpaceCopy(ctx context.Context, user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	res, err := t.Exec(queryCreateNewSpace, name, desc, user.Org().ID())
	res, err := t.ExecContext(ctx, queryCreateNewSpace, name, desc, user.Org().ID())
	if err != nil {
		return nil, err
	}


@@ 143,23 144,23 @@ func (db *DB) SpaceCopy(user user.User, prevS space.Space, name, desc string) (s
	}

	// Copy all webhooks.
	if _, err := t.Exec(copyHooksQuery, nextID, prevS.ID()); err != nil {
	if _, err := t.ExecContext(ctx, copyHooksQuery, nextID, prevS.ID()); err != nil {
		return nil, err
	}

	next, err := db.spaceGet(t, user, strconv.FormatInt(nextID, 10))
	next, err := db.spaceGet(ctx, t, user, strconv.FormatInt(nextID, 10))
	if err != nil {
		return nil, err
	}

	if err := db.spaceCopyContentTypes(t, user, next, prevS); err != nil {
	if err := db.spaceCopyContentTypes(ctx, t, user, next, prevS); err != nil {
		return nil, err
	}

	return next, t.Commit()
}

func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Space) error {
func (db *DB) spaceCopyContentTypes(ctx context.Context, t *sql.Tx, u user.User, next, prevS space.Space) error {
	type cct struct {
		ct contenttype.ContentType
		c  content.Content


@@ 179,7 180,7 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Sp

	// Copy all content types and their value types.
	// Copy all contents and their values.
	iter := db.ContentTypeIter(t, u, prevS)
	iter := db.ContentTypeIter(ctx, t, u, prevS)
	for iter.Next() {
		prevCT, err := iter.Scan()
		if err != nil {


@@ 187,7 188,7 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Sp
		}

		// Copy content type.
		res, err := t.Exec(copyContentTypeQuery, next.ID(), prevCT.ID())
		res, err := t.ExecContext(ctx, copyContentTypeQuery, next.ID(), prevCT.ID())
		if err != nil {
			return err
		}


@@ 198,24 199,24 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Sp
		}

		// Copy value type.
		if _, err := t.Exec(copyValueTypeQuery, ctID, prevCT.ID()); err != nil {
		if _, err := t.ExecContext(ctx, copyValueTypeQuery, ctID, prevCT.ID()); err != nil {
			return err
		}

		ct, err := db.contentTypeGet(t, next, strconv.FormatInt(ctID, 10))
		ct, err := db.contentTypeGet(ctx, t, next, strconv.FormatInt(ctID, 10))
		if err != nil {
			return err
		}

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

			ok, err := db.spaceCopyContent(t, next, ct, prevC, refmap)
			ok, err := db.spaceCopyContent(ctx, t, next, ct, prevC, refmap)
			if err != nil {
				return fmt.Errorf("failed to copy %s: %w", prevC.MustValueByName("name").Value(), err)
			}


@@ 229,7 230,7 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Sp

	// Update reference types that were skipped because content was not createt.
	for _, set := range todo {
		err := db.spaceUpdateContent(t, next, set.ct, set.c, refmap)
		err := db.spaceUpdateContent(ctx, t, next, set.ct, set.c, refmap)
		if err != nil {
			return fmt.Errorf("failed to copy for references %s: %w", set.c.MustValueByName("name").Value(), err)
		}


@@ 238,7 239,7 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, u user.User, next, prevS space.Sp
	return nil
}

func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) (bool, error) {
func (db *DB) spaceCopyContent(ctx context.Context, t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) (bool, error) {
	var (
		params []ContentNewParam
		skip   bool


@@ 287,7 288,7 @@ func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.Conte
		}
	}

	c, err := db.contentNew(t, next, ct, params, defaultDepth)
	c, err := db.contentNew(ctx, t, next, ct, params, defaultDepth)
	if err != nil {
		return false, err
	}


@@ 297,7 298,7 @@ func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.Conte
	return !skip, nil
}

func (db *DB) spaceUpdateContent(t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) error {
func (db *DB) spaceUpdateContent(ctx context.Context, t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) error {
	var params []ContentNewParam

	for _, prevF := range ct.Fields() {


@@ 338,12 339,12 @@ func (db *DB) spaceUpdateContent(t *sql.Tx, next space.Space, ct contenttype.Con
		}
	}

	return db.contentUpdate(t, next, ct, refmap[prevC.ID()], params, []ContentUpdateParam{})
	return db.contentUpdate(ctx, t, next, ct, refmap[prevC.ID()], params, []ContentUpdateParam{})
}

func (db *DB) spaceGet(t *sql.Tx, user user.User, spaceID string) (space.Space, error) {
func (db *DB) spaceGet(ctx context.Context, t *sql.Tx, user user.User, spaceID string) (space.Space, error) {
	var s Space
	err := t.QueryRow(queryFindSpaceByUserAndID, user.ID(), spaceID).Scan(
	err := t.QueryRowContext(ctx, queryFindSpaceByUserAndID, user.ID(), spaceID).Scan(
		&s.SpaceID, &s.SpaceName, &s.SpaceDesc,
		&s.SpaceOrg.OrgID, &s.SpaceOrg.OrgBillingTierName, &s.SpaceOrg.OrgPaymentCustomer,
	)


@@ 353,14 354,14 @@ func (db *DB) spaceGet(t *sql.Tx, user user.User, spaceID string) (space.Space, 
	return &s, nil
}

func (db *DB) SpaceGet(user user.User, spaceID string) (space.Space, error) {
	t, err := db.Begin()
func (db *DB) SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	space, err := db.spaceGet(t, user, spaceID)
	space, err := db.spaceGet(ctx, t, user, spaceID)
	if err != nil {
		return nil, err
	}


@@ 368,21 369,21 @@ func (db *DB) SpaceGet(user user.User, spaceID string) (space.Space, error) {
	return space, t.Commit()
}

func (db *DB) SpaceDelete(user user.User, space space.Space) error {
	t, err := db.Begin()
func (db *DB) SpaceDelete(ctx context.Context, user user.User, space space.Space) error {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer t.Rollback()

	if _, err := t.Exec(queryDeleteSpace, user.ID(), space.ID()); err != nil {
	if _, err := t.ExecContext(ctx, queryDeleteSpace, user.ID(), space.ID()); err != nil {
		return err
	}

	return t.Commit()
}

func (db *DB) spacesPerUser(t *sql.Tx, user user.User, before int) (space.SpaceList, error) {
func (db *DB) spacesPerUser(ctx context.Context, t *sql.Tx, user user.User, before int) (space.SpaceList, error) {
	var (
		r       []space.Space
		id      int


@@ 399,7 400,7 @@ func (db *DB) spacesPerUser(t *sql.Tx, user user.User, before int) (space.SpaceL
		ORDER BY cms_space.ID DESC LIMIT ?
	`

	rows, err := t.Query(q, user.ID(), before, perPage+1)
	rows, err := t.QueryContext(ctx, q, user.ID(), before, perPage+1)
	if err != nil {
		return nil, err
	}


@@ 423,7 424,7 @@ func (db *DB) spacesPerUser(t *sql.Tx, user user.User, before int) (space.SpaceL
	}

	for _, id := range ids {
		s, err := db.spaceGet(t, user, strconv.Itoa(id))
		s, err := db.spaceGet(ctx, t, user, strconv.Itoa(id))
		if err != nil {
			return nil, err
		}


@@ 434,14 435,14 @@ func (db *DB) spacesPerUser(t *sql.Tx, user user.User, before int) (space.SpaceL
	return newSpaceList(r, hasMore, id), nil
}

func (db *DB) SpacesPerUser(user user.User, before int) (space.SpaceList, error) {
	t, err := db.Begin()
func (db *DB) SpacesPerUser(ctx context.Context, user user.User, before int) (space.SpaceList, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	list, err := db.spacesPerUser(t, user, before)
	list, err := db.spacesPerUser(ctx, t, user, before)
	if err != nil {
		return nil, err
	}

M internal/s/db/user.go => internal/s/db/user.go +33 -32
@@ 1,6 1,7 @@
package db

import (
	"context"
	"database/sql"
	"fmt"



@@ 48,14 49,14 @@ var (
	`
)

func (db *DB) UserNew(username, password, verifyPassword string) (user.User, error) {
	t, err := db.Begin()
func (db *DB) UserNew(ctx context.Context, username, password, verifyPassword string) (user.User, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	user, err := db.userNew(t, username, password, verifyPassword)
	user, err := db.userNew(ctx, t, username, password, verifyPassword)
	if err != nil {
		return nil, err
	}


@@ 63,7 64,7 @@ func (db *DB) UserNew(username, password, verifyPassword string) (user.User, err
	return user, t.Commit()
}

func (db *DB) userNewGeneratePassword(username, password, verifyPassword string) (string, error) {
func (db *DB) userNewGeneratePassword(ctx context.Context, username, password, verifyPassword string) (string, error) {
	if password == "" {
		return "", fmt.Errorf("no password entered")
	}


@@ 80,32 81,32 @@ func (db *DB) userNewGeneratePassword(username, password, verifyPassword string)
	return hash, nil
}

func (db *DB) userNew(t *sql.Tx, username, password, verifyPassword string) (user.User, error) {
	hash, err := db.userNewGeneratePassword(username, password, verifyPassword)
func (db *DB) userNew(ctx context.Context, t *sql.Tx, username, password, verifyPassword string) (user.User, error) {
	hash, err := db.userNewGeneratePassword(ctx, username, password, verifyPassword)
	if err != nil {
		return nil, err
	}

	org, err := db.orgNew(t)
	org, err := db.orgNew(ctx, t)
	if err != nil {
		return nil, err
	}

	if _, err := t.Exec(queryCreateNewUser, username, hash, org.ID(), role.Admin.Name); err != nil {
	if _, err := t.ExecContext(ctx, queryCreateNewUser, username, hash, org.ID(), role.Admin.Name); err != nil {
		return nil, fmt.Errorf("user '%s' already exists", username)
	}

	return db.userGet(t, username, password)
	return db.userGet(ctx, t, username, password)
}

func (db *DB) UserGet(username, password string) (user.User, error) {
	t, err := db.Begin()
func (db *DB) UserGet(ctx context.Context, username, password string) (user.User, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	user, err := db.userGet(t, username, password)
	user, err := db.userGet(ctx, t, username, password)
	if err != nil {
		return nil, err
	}


@@ 113,9 114,9 @@ func (db *DB) UserGet(username, password string) (user.User, error) {
	return user, t.Commit()
}

func (db *DB) userGet(t *sql.Tx, username, password string) (user.User, error) {
func (db *DB) userGet(ctx context.Context, t *sql.Tx, username, password string) (user.User, error) {
	var user User
	if err := t.QueryRow(queryFindUserByName, username).Scan(
	if err := t.QueryRowContext(ctx, queryFindUserByName, username).Scan(
		&user.UserID, &user.UserName, &user.userHash, &user.userEmail,
		&user.UserOrg.OrgID, &user.UserOrg.OrgBillingTierName, &user.UserOrg.OrgPaymentCustomer,
		&user.UserRole.RoleID, &user.UserRole.RoleName,


@@ 136,14 137,14 @@ func (db *DB) userGet(t *sql.Tx, username, password string) (user.User, error) {
	return &user, nil
}

func (db *DB) UserGetFromToken(token string) (user.User, error) {
	t, err := db.Begin()
func (db *DB) UserGetFromToken(ctx context.Context, token string) (user.User, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	user, err := db.userGetFromToken(t, token)
	user, err := db.userGetFromToken(ctx, t, token)
	if err != nil {
		return nil, err
	}


@@ 151,7 152,7 @@ func (db *DB) UserGetFromToken(token string) (user.User, error) {
	return user, t.Commit()
}

func (db *DB) userGetFromToken(t *sql.Tx, token string) (user.User, error) {
func (db *DB) userGetFromToken(ctx context.Context, t *sql.Tx, token string) (user.User, error) {
	tmap, err := db.sec.TokenFrom(token)
	if err != nil {
		return nil, fmt.Errorf("failed to decode user token")


@@ 163,7 164,7 @@ func (db *DB) userGetFromToken(t *sql.Tx, token string) (user.User, error) {
	}

	var user User
	if err := t.QueryRow(queryFindUserByID, id).Scan(
	if err := t.QueryRowContext(ctx, queryFindUserByID, id).Scan(
		&user.UserID, &user.UserName, &user.userHash, &user.userEmail,
		&user.UserOrg.OrgID, &user.UserOrg.OrgBillingTierName, &user.UserOrg.OrgPaymentCustomer,
		&user.UserRole.RoleID, &user.UserRole.RoleName,


@@ 182,14 183,14 @@ func (db *DB) userGetFromToken(t *sql.Tx, token string) (user.User, error) {
	return &user, nil
}

func (db *DB) UserSetEmail(u user.User, email string) (user.User, error) {
	t, err := db.Begin()
func (db *DB) UserSetEmail(ctx context.Context, u user.User, email string) (user.User, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	user, err := db.userSetEmail(t, u, email)
	user, err := db.userSetEmail(ctx, t, u, email)
	if err != nil {
		return nil, err
	}


@@ 197,22 198,22 @@ func (db *DB) UserSetEmail(u user.User, email string) (user.User, error) {
	return user, t.Commit()
}

func (db *DB) userSetEmail(t *sql.Tx, u user.User, email string) (user.User, error) {
func (db *DB) userSetEmail(ctx context.Context, t *sql.Tx, u user.User, email string) (user.User, error) {
	q := "INSERT INTO cms_email (EMAIL, USER_ID) VALUES (?, ?)"
	if _, err := t.Exec(q, email, u.ID()); err != nil {
	if _, err := t.ExecContext(ctx, q, email, u.ID()); err != nil {
		return nil, err
	}
	return db.userGetFromToken(t, u.Token())
	return db.userGetFromToken(ctx, t, u.Token())
}

func (db *DB) UserSetPassword(u user.User, current, password, verifyPassword string) (user.User, error) {
	t, err := db.Begin()
func (db *DB) UserSetPassword(ctx context.Context, u user.User, current, password, verifyPassword string) (user.User, error) {
	t, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	user, err := db.userSetPassword(t, u, current, password, verifyPassword)
	user, err := db.userSetPassword(ctx, t, u, current, password, verifyPassword)
	if err != nil {
		return nil, err
	}


@@ 220,9 221,9 @@ func (db *DB) UserSetPassword(u user.User, current, password, verifyPassword str
	return user, t.Commit()
}

func (db *DB) userSetPassword(t *sql.Tx, u user.User, current, password, verifyPassword string) (user.User, error) {
func (db *DB) userSetPassword(ctx context.Context, t *sql.Tx, u user.User, current, password, verifyPassword string) (user.User, error) {
	var currentHash string
	if err := t.QueryRow("SELECT HASH FROM cms_user WHERE ID=?", u.ID()).Scan(&currentHash); err != nil {
	if err := t.QueryRowContext(ctx, "SELECT HASH FROM cms_user WHERE ID=?", u.ID()).Scan(&currentHash); err != nil {
		return nil, err
	}



@@ 245,11 246,11 @@ func (db *DB) userSetPassword(t *sql.Tx, u user.User, current, password, verifyP
		return nil, fmt.Errorf("failed to create password hash")
	}

	if _, err := t.Exec("UPDATE cms_user SET HASH=? WHERE ID=?", hash, u.ID()); err != nil {
	if _, err := t.ExecContext(ctx, "UPDATE cms_user SET HASH=? WHERE ID=?", hash, u.ID()); err != nil {
		return nil, err
	}

	return db.userGet(t, u.Name(), password)
	return db.userGet(ctx, t, u.Name(), password)
}

func (u *User) ID() string     { return u.UserID }

M internal/s/hook/hook.go => internal/s/hook/hook.go +7 -13
@@ 9,7 9,6 @@ import (
	"log"
	"net/http"
	"strings"
	"time"

	"git.sr.ht/~evanj/cms/internal/m/content"
	"git.sr.ht/~evanj/cms/internal/m/hook"


@@ 33,7 32,7 @@ type Hook struct {
}

type dber interface {
	HooksPerSpace(user user.User, space space.Space, before int) (hook.HookList, error)
	HooksPerSpace(ctx context.Context, user user.User, space space.Space, before int) (hook.HookList, error)
}

func New(log *log.Logger, db dber) *Hook {


@@ 78,21 77,16 @@ func (h *Hook) do(ctx context.Context, content content.Content, hook hook.Hook, 
	return nil
}

func (h *Hook) Do(user user.User, space space.Space, content content.Content, ht HookType) {
func (h *Hook) Do(ctx context.Context, user user.User, space space.Space, content content.Content, ht HookType) {
	var (
		hooks       hook.HookList
		before      int
		err         error
		eg          errgroup.Group
		ctx, cancel = context.WithTimeout(
			context.Background(),
			10*time.Second, // TODO: May want to lower?
		)
		hooks  hook.HookList
		before int
		err    error
		eg     errgroup.Group
	)
	defer cancel()

	for i := 0; i == 0 || hooks.More(); i++ {
		hooks, err = h.db.HooksPerSpace(user, space, before)
		hooks, err = h.db.HooksPerSpace(ctx, user, space, before)
		if err != nil {
			h.log.Println("failed to find webhooks for", space.ID(), space.Name(), err)
			return

M internal/s/rbac/rbac.go => internal/s/rbac/rbac.go +53 -52
@@ 3,6 3,7 @@
package rbac

import (
	"context"
	"log"

	"git.sr.ht/~evanj/cms/internal/m/content"


@@ 30,194 31,194 @@ func New(l *log.Logger, db rl.RL) RBAC {

// SPACE

func (rbac RBAC) SpaceNew(user user.User, name, desc string) (space.Space, error) {
func (rbac RBAC) SpaceNew(ctx context.Context, user user.User, name, desc string) (space.Space, error) {
	if user.Role().Can(role.SpaceCreate) {
		return rbac.db.SpaceNew(user, name, desc)
		return rbac.db.SpaceNew(ctx, user, name, desc)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) SpaceCopy(user user.User, prevS space.Space, name, desc string) (space.Space, error) {
func (rbac RBAC) SpaceCopy(ctx context.Context, user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	if user.Role().Can(role.SpaceCreate) {
		return rbac.db.SpaceCopy(user, prevS, name, desc)
		return rbac.db.SpaceCopy(ctx, user, prevS, name, desc)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) SpaceUpdate(user user.User, space space.Space, name, desc string) (space.Space, error) {
func (rbac RBAC) SpaceUpdate(ctx context.Context, user user.User, space space.Space, name, desc string) (space.Space, error) {
	if user.Role().Can(role.SpaceUpdate) {
		return rbac.db.SpaceUpdate(user, space, name, desc)
		return rbac.db.SpaceUpdate(ctx, user, space, name, desc)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) SpaceGet(user user.User, spaceID string) (space.Space, error) {
func (rbac RBAC) SpaceGet(ctx context.Context, user user.User, spaceID string) (space.Space, error) {
	if user.Role().Can(role.SpaceGet) {
		return rbac.db.SpaceGet(user, spaceID)
		return rbac.db.SpaceGet(ctx, user, spaceID)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) SpacesPerUser(user user.User, before int) (space.SpaceList, error) {
func (rbac RBAC) SpacesPerUser(ctx context.Context, user user.User, before int) (space.SpaceList, error) {
	if user.Role().Can(role.SpaceGet) {
		return rbac.db.SpacesPerUser(user, before)
		return rbac.db.SpacesPerUser(ctx, user, before)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) SpaceDelete(user user.User, space space.Space) error {
func (rbac RBAC) SpaceDelete(ctx context.Context, user user.User, space space.Space) error {
	if user.Role().Can(role.SpaceDelete) {
		return rbac.db.SpaceDelete(user, space)
		return rbac.db.SpaceDelete(ctx, user, space)
	}
	return role.ErrNoPermission
}

// INVITE

func (rbac RBAC) InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, error) {
func (rbac RBAC) InviteNew(ctx context.Context, u user.User, o org.Org, r role.Role) (invite.Invite, error) {
	if u.Role().Can(role.InviteCreate) {
		return rbac.db.InviteNew(u, o, r)
		return rbac.db.InviteNew(ctx, u, o, r)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) InviteGet(u user.User, id string) (invite.Invite, error) {
func (rbac RBAC) InviteGet(ctx context.Context, u user.User, id string) (invite.Invite, error) {
	if u.Role().Can(role.InviteGet) {
		return rbac.db.InviteGet(u, id)
		return rbac.db.InviteGet(ctx, u, id)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) InviteList(u user.User, o org.Org) (r []invite.Invite, err error) {
func (rbac RBAC) InviteList(ctx context.Context, u user.User, o org.Org) (r []invite.Invite, err error) {
	if u.Role().Can(role.InviteGet) {
		return rbac.db.InviteList(u, o)
		return rbac.db.InviteList(ctx, u, o)
	}
	return nil, role.ErrNoPermission
}

// CONTENTTYPE

func (rbac RBAC) ContentTypeNew(u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
func (rbac RBAC) ContentTypeNew(ctx context.Context, u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
	if u.Role().Can(role.ContentTypeCreate) {
		return rbac.db.ContentTypeNew(u, space, name, params)
		return rbac.db.ContentTypeNew(ctx, u, space, name, params)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentTypeUpdate(u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
func (rbac RBAC) ContentTypeUpdate(ctx context.Context, u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
	if u.Role().Can(role.ContentTypeUpdate) {
		return rbac.db.ContentTypeUpdate(u, space, contenttype, name, newParams, updateParams)
		return rbac.db.ContentTypeUpdate(ctx, u, space, contenttype, name, newParams, updateParams)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentTypesPerSpace(u user.User, space space.Space, before int) (contenttype.ContentTypeList, error) {
func (rbac RBAC) ContentTypesPerSpace(ctx context.Context, u user.User, space space.Space, before int) (contenttype.ContentTypeList, error) {
	if u.Role().Can(role.ContentTypeGet) {
		return rbac.db.ContentTypesPerSpace(u, space, before)
		return rbac.db.ContentTypesPerSpace(ctx, u, space, before)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentTypeGet(u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
func (rbac RBAC) ContentTypeGet(ctx context.Context, u user.User, space space.Space, contenttypeID string) (contenttype.ContentType, error) {
	if u.Role().Can(role.ContentTypeGet) {
		return rbac.db.ContentTypeGet(u, space, contenttypeID)
		return rbac.db.ContentTypeGet(ctx, u, space, contenttypeID)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentTypeSearch(u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
func (rbac RBAC) ContentTypeSearch(ctx context.Context, u user.User, space space.Space, query string, before int) (contenttype.ContentTypeList, error) {
	if u.Role().Can(role.ContentTypeGet) {
		return rbac.db.ContentTypeSearch(u, space, query, before)
		return rbac.db.ContentTypeSearch(ctx, u, space, query, before)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentTypeDelete(u user.User, space space.Space, ct contenttype.ContentType) error {
func (rbac RBAC) ContentTypeDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType) error {
	if u.Role().Can(role.ContentTypeDelete) {
		rbac.db.ContentTypeDelete(u, space, ct)
		rbac.db.ContentTypeDelete(ctx, u, space, ct)
	}
	return role.ErrNoPermission
}

// HOOK

func (rbac RBAC) HookNew(u user.User, space space.Space, url string) (hook.Hook, error) {
func (rbac RBAC) HookNew(ctx context.Context, u user.User, space space.Space, url string) (hook.Hook, error) {
	if u.Role().Can(role.HookCreate) {
		return rbac.db.HookNew(u, space, url)
		return rbac.db.HookNew(ctx, u, space, url)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) HookGet(u user.User, space space.Space, id string) (hook.Hook, error) {
func (rbac RBAC) HookGet(ctx context.Context, u user.User, space space.Space, id string) (hook.Hook, error) {
	if u.Role().Can(role.HookGet) {
		return rbac.db.HookGet(u, space, id)
		return rbac.db.HookGet(ctx, u, space, id)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) HookDelete(u user.User, s space.Space, h hook.Hook) error {
func (rbac RBAC) HookDelete(ctx context.Context, u user.User, s space.Space, h hook.Hook) error {
	if u.Role().Can(role.HookDelete) {
		return rbac.db.HookDelete(u, s, h)
		return rbac.db.HookDelete(ctx, u, s, h)
	}
	return role.ErrNoPermission
}

func (rbac RBAC) HooksPerSpace(u user.User, space space.Space, before int) (hook.HookList, error) {
func (rbac RBAC) HooksPerSpace(ctx context.Context, u user.User, space space.Space, before int) (hook.HookList, error) {
	if u.Role().Can(role.HookGet) {
		return rbac.db.HooksPerSpace(u, space, before)
		return rbac.db.HooksPerSpace(ctx, u, space, before)
	}
	return nil, role.ErrNoPermission
}

// CONTENT

func (rbac RBAC) ContentNew(u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error) {
func (rbac RBAC) ContentNew(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, params []db.ContentNewParam) (content.Content, error) {
	if u.Role().Can(role.ContentCreate) {
		return rbac.db.ContentNew(u, space, ct, params)
		return rbac.db.ContentNew(ctx, u, space, ct, params)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentUpdate(u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error) {
func (rbac RBAC) ContentUpdate(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content, newParams []db.ContentNewParam, updateParams []db.ContentUpdateParam) (content.Content, error) {
	if u.Role().Can(role.ContentUpdate) {
		return rbac.db.ContentUpdate(u, space, ct, content, newParams, updateParams)
		return rbac.db.ContentUpdate(ctx, u, space, ct, content, newParams, updateParams)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentDelete(u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error {
func (rbac RBAC) ContentDelete(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, content content.Content) error {
	if u.Role().Can(role.ContentDelete) {
		return rbac.db.ContentDelete(u, space, ct, content)
		return rbac.db.ContentDelete(ctx, u, space, ct, content)
	}
	return role.ErrNoPermission
}

func (rbac RBAC) ContentPerContentType(u user.User, space space.Space, ct contenttype.ContentType, before int, order db.OrderType, sortField string) (content.ContentList, error) {
func (rbac RBAC) ContentPerContentType(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, before int, order db.OrderType, sortField string) (content.ContentList, error) {
	if u.Role().Can(role.ContentGet) {
		return rbac.db.ContentPerContentType(u, space, ct, before, order, sortField)
		return rbac.db.ContentPerContentType(ctx, u, space, ct, before, order, sortField)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentSearch(u user.User, space space.Space, ct contenttype.ContentType, sortField, query string, before int) (content.ContentList, error) {
func (rbac RBAC) ContentSearch(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, sortField, query string, before int) (content.ContentList, error) {
	if u.Role().Can(role.ContentGet) {
		return rbac.db.ContentSearch(u, space, ct, sortField, query, before)
		return rbac.db.ContentSearch(ctx, u, space, ct, sortField, query, before)
	}
	return nil, role.ErrNoPermission
}

func (rbac RBAC) ContentGet(u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
func (rbac RBAC) ContentGet(ctx context.Context, u user.User, space space.Space, ct contenttype.ContentType, contentID string) (content.Content, error) {
	if u.Role().Can(role.ContentGet) {
		return rbac.db.ContentGet(u, space, ct, contentID)
		return rbac.db.ContentGet(ctx, u, space, ct, contentID)
	}
	return nil, role.ErrNoPermission
}

// ORG

func (rbac RBAC) OrgUpdateTier(u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error {
func (rbac RBAC) OrgUpdateTier(ctx context.Context, u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error {
	if u.Role().Can(role.OrgUpdate) {
		return rbac.db.OrgUpdateTier(u, o, t, paymentCustomerID)
		return rbac.db.OrgUpdateTier(ctx, u, o, t, paymentCustomerID)
	}
	return role.ErrNoPermission
}

M internal/s/rl/rl.go => internal/s/rl/rl.go +30 -30
@@ 60,16 60,16 @@ func New(l *log.Logger, db *cache.Cache, e3 e3.E3) RL {

// Limit requests made.

func (rl RL) requestLimit(o org.Org) error {
func (rl RL) requestLimit(ctx context.Context, o org.Org) error {
	now := time.Now().UTC()

	limit, ok := requestLimits[o.Tier().Name]
	if !ok {
		// If not in map, unlimited.
		return rl.db.ActionNew(o, now)
		return rl.db.ActionNew(ctx, o, now)
	}

	c, err := rl.db.ActionGetCount(o, now.Add(-1*time.Minute), now)
	c, err := rl.db.ActionGetCount(ctx, o, now.Add(-1*time.Minute), now)
	if err != nil {
		return err
	}


@@ 78,35 78,35 @@ func (rl RL) requestLimit(o org.Org) error {
		return ErrHitLimit
	}

	return rl.db.ActionNew(o, now)
	return rl.db.ActionNew(ctx, o, now)
}

func (rl RL) UserGet(username, password string) (user.User, error) {
	u, e := rl.db.UserGet(username, password)
func (rl RL) UserGet(ctx context.Context, username, password string) (user.User, error) {
	u, e := rl.db.UserGet(ctx, username, password)
	if e != nil {
		return nil, e
	}
	return u, rl.requestLimit(u.Org())
	return u, rl.requestLimit(ctx, u.Org())
}

func (rl RL) UserGetFromToken(token string) (user.User, error) {
	u, e := rl.db.UserGetFromToken(token)
func (rl RL) UserGetFromToken(ctx context.Context, token string) (user.User, error) {
	u, e := rl.db.UserGetFromToken(ctx, token)
	if e != nil {
		return nil, e
	}
	return u, rl.requestLimit(u.Org())
	return u, rl.requestLimit(ctx, u.Org())
}

// Limit spaces created.

func (rl RL) spaceLimit(o org.Org, getter func() (space.Space, error)) (space.Space, error) {
func (rl RL) spaceLimit(ctx context.Context, o org.Org, getter func() (space.Space, error)) (space.Space, error) {
	limit, ok := spaceLimits[o.Tier().Name]
	if !ok {
		// If not in map, unlimited.
		return getter()
	}

	c, err := rl.db.OrgGetSpaceCount(o)
	c, err := rl.db.OrgGetSpaceCount(ctx, o)
	if err != nil {
		return nil, err
	}


@@ 118,15 118,15 @@ func (rl RL) spaceLimit(o org.Org, getter func() (space.Space, error)) (space.Sp
	return getter()
}

func (rl RL) SpaceNew(user user.User, name, desc string) (space.Space, error) {
	return rl.spaceLimit(user.Org(), func() (space.Space, error) {
		return rl.db.SpaceNew(user, name, desc)
func (rl RL) SpaceNew(ctx context.Context, user user.User, name, desc string) (space.Space, error) {
	return rl.spaceLimit(ctx, user.Org(), func() (space.Space, error) {
		return rl.db.SpaceNew(ctx, user, name, desc)
	})
}

func (rl RL) SpaceCopy(user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	return rl.spaceLimit(user.Org(), func() (space.Space, error) {
		return rl.db.SpaceCopy(user, prevS, name, desc)
func (rl RL) SpaceCopy(ctx context.Context, user user.User, prevS space.Space, name, desc string) (space.Space, error) {
	return rl.spaceLimit(ctx, user.Org(), func() (space.Space, error) {
		return rl.db.SpaceCopy(ctx, user, prevS, name, desc)
	})
}



@@ 161,30 161,30 @@ func updateParamHasFile(params []db.ContentTypeUpdateParam) (r bool) {
	return
}

func (rl RL) ContentTypeNew(u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
func (rl RL) ContentTypeNew(ctx context.Context, u user.User, space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error) {
	if space.Org().Tier().Is(tier.Free) && newParamHasFile(params) {
		return nil, fmt.Errorf("can't create content type with field type of file: %w", ErrNoAccess)
	}
	return rl.db.ContentTypeNew(u, space, name, params)
	return rl.db.ContentTypeNew(ctx, u, space, name, params)
}

func (rl RL) ContentTypeUpdate(u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
func (rl RL) ContentTypeUpdate(ctx context.Context, u user.User, space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error) {
	if space.Org().Tier().Is(tier.Free) && (newParamHasFile(newParams) || updateParamHasFile(updateParams)) {
		return nil, fmt.Errorf("can't create content type with field type of file: %w", ErrNoAccess)
	}
	return rl.db.ContentTypeUpdate(u, space, contenttype, name, newParams, updateParams)
	return rl.db.ContentTypeUpdate(ctx, u, space, contenttype, name, newParams, updateParams)
}

// Rate limit users to org.

func (rl RL) InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, error) {
func (rl RL) InviteNew(ctx context.Context, u user.User, o org.Org, r role.Role) (invite.Invite, error) {
	limit, ok := userLimits[o.Tier().Name]
	if !ok {
		// If not in map, unlimited.
		return rl.db.InviteNew(u, o, r)
		return rl.db.InviteNew(ctx, u, o, r)
	}

	c, err := rl.db.OrgGetUserCount(o)
	c, err := rl.db.OrgGetUserCount(ctx, o)
	if err != nil {
		return nil, err
	}


@@ 193,17 193,17 @@ func (rl RL) InviteNew(u user.User, o org.Org, r role.Role) (invite.Invite, erro
		return nil, fmt.Errorf("can't invite new users: %w", ErrHitLimit)
	}

	return rl.db.InviteNew(u, o, r)
	return rl.db.InviteNew(ctx, u, o, r)
}

func (rl RL) InviteAccept(i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
func (rl RL) InviteAccept(ctx context.Context, i invite.Invite, u, p, v string) (user.User, invite.Invite, error) {
	limit, ok := userLimits[i.Org().Tier().Name]
	if !ok {
		// If not in map, unlimited.
		return rl.db.InviteAccept(i, u, p, v)
		return rl.db.InviteAccept(ctx, i, u, p, v)
	}

	c, err := rl.db.OrgGetUserCount(i.Org())
	c, err := rl.db.OrgGetUserCount(ctx, i.Org())
	if err != nil {
		return nil, nil, err
	}


@@ 212,5 212,5 @@ func (rl RL) InviteAccept(i invite.Invite, u, p, v string) (user.User, invite.In
		return nil, nil, fmt.Errorf("cannot join this organization: too many users: %w", ErrHitLimit)
	}

	return rl.db.InviteAccept(i, u, p, v)
	return rl.db.InviteAccept(ctx, i, u, p, v)
}

M internal/s/stripe/stripe.go => internal/s/stripe/stripe.go +14 -13
@@ 1,6 1,7 @@
package stripe

import (
	"context"
	"log"

	"git.sr.ht/~evanj/cms/internal/m/org"


@@ 17,15 18,15 @@ type Stripe struct {
	log                   *log.Logger
	sucesssURL, cancelURL string
	pk, sk                string
	db                    DBer
	db                    dber
}

type DBer interface {
	UserGetFromToken(token string) (user.User, error)
	OrgUpdateTier(u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error
type dber interface {
	UserGetFromToken(ctx context.Context, token string) (user.User, error)
	OrgUpdateTier(ctx context.Context, u user.User, o org.Org, t tier.Tier, paymentCustomerID string) error
}

func New(l *log.Logger, sucesssURL, cancelURL, pk, sk string, db DBer) Stripe {
func New(l *log.Logger, sucesssURL, cancelURL, pk, sk string, db dber) Stripe {
	// Stripe sucks.
	lib.Key = sk
	return Stripe{


@@ 36,7 37,7 @@ func New(l *log.Logger, sucesssURL, cancelURL, pk, sk string, db DBer) Stripe {
	}
}

func (s Stripe) StartCheckout(user user.User, t tier.Tier) (string, string, error) {
func (s Stripe) StartCheckout(ctx context.Context, user user.User, t tier.Tier) (string, string, error) {
	customerParams := &lib.CustomerParams{
		Name:        lib.String(user.Name()),
		Description: lib.String(user.Token()),


@@ 48,7 49,7 @@ func (s Stripe) StartCheckout(user user.User, t tier.Tier) (string, string, erro
	}

	// Make sure we're on free tier.
	if err := s.db.OrgUpdateTier(user, user.Org(), tier.Free, c.ID); err != nil {
	if err := s.db.OrgUpdateTier(ctx, user, user.Org(), tier.Free, c.ID); err != nil {
		return "", "", err
	}



@@ 76,7 77,7 @@ func (s Stripe) StartCheckout(user user.User, t tier.Tier) (string, string, erro
	return sess.ID, s.pk, nil
}

func (s Stripe) CompleteCheckout(sessionID string) error {
func (s Stripe) CompleteCheckout(ctx context.Context, sessionID string) error {
	sess, err := session.Get(sessionID, &lib.CheckoutSessionParams{
		PaymentIntentData: &lib.CheckoutSessionPaymentIntentDataParams{},
	})


@@ 111,15 112,15 @@ func (s Stripe) CompleteCheckout(sessionID string) error {
		return errors.New("subscription could not be found")
	}

	user, err := s.db.UserGetFromToken(c.Description)
	user, err := s.db.UserGetFromToken(ctx, c.Description)
	if err != nil {
		return errors.Wrap(err, "mangled user token")
	}

	return s.db.OrgUpdateTier(user, user.Org(), t, c.ID)
	return s.db.OrgUpdateTier(ctx, user, user.Org(), t, c.ID)
}

func (s Stripe) CancelSubscription(user user.User) error {
func (s Stripe) CancelSubscription(ctx context.Context, user user.User) error {
	if !user.Org().HasPaymentCustomer() {
		return errors.New("organization has no customer")
	}


@@ 133,14 134,14 @@ func (s Stripe) CancelSubscription(user user.User) error {
	}

	currTier := user.Org().Tier()
	if err := s.db.OrgUpdateTier(user, user.Org(), tier.Free, c.ID); err != nil {
	if err := s.db.OrgUpdateTier(ctx, user, user.Org(), tier.Free, c.ID); err != nil {
		return err
	}

	for _, subItem := range c.Subscriptions.Data {
		_, err := sub.Cancel(subItem.ID, nil)
		if err != nil {
			if err := s.db.OrgUpdateTier(user, user.Org(), currTier, c.ID); err != nil {
			if err := s.db.OrgUpdateTier(ctx, user, user.Org(), currTier, c.ID); err != nil {
				// TODO: Better way to report this to ourselves?
				s.log.Println("big error: sub update in DB but not reflected in stripe")
				return errors.Wrap(err, "your subscription has been cancelled within Skipper CMS but not our payment processor: please contact an admin")

M main.go => main.go +6 -3
@@ 2,9 2,11 @@
package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"time"

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/c/content"


@@ 125,12 127,10 @@ func main() {
				"page": doc.New(
					c,
					log.New(w, "[cms:doc] ", 0),
					rbac,
				),
				"stripe": http.StripPrefix("/stripe", stripe.New(
					c,
					log.New(w, "[cms:stripe] ", 0),
					rbac,
					libs,
				)),
				"invite": http.StripPrefix("/invite", invite.New(


@@ 142,7 142,10 @@ func main() {
		}
	)

	if err := db.Setup(); err != nil {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := db.Setup(ctx); err != nil {
		app.log.Fatal(err)
	}
	if err := cacher.Setup(); err != nil {