Documentation
¶
Overview ¶
Package store defines persistence interfaces + an in-memory test implementation for the CMS engine.
Per gocodealone-multisite SPEC.md:
V12: per-tenant data writes ! include tenant_id WHERE clause. V15: CMS-rendered page → tenant_id from session; ⊥ from URL/header. V22: page filtered by (tenant_id, subsite_label).
Production wiring: postgres-backed implementation lives in gocodealone-multisite (per the host's schema in migrations/0001). This package owns the interface and an in-memory store for tests + local dev.
Package store persists CMS records.
Index ¶
- Variables
- type Domain
- type MemoryPageStore
- func (s *MemoryPageStore) Create(_ context.Context, tenantID int64, p *Page) error
- func (s *MemoryPageStore) Delete(_ context.Context, tenantID, id int64) error
- func (s *MemoryPageStore) Get(_ context.Context, tenantID, id int64) (*Page, error)
- func (s *MemoryPageStore) GetByPath(_ context.Context, tenantID int64, subsite, path string) (*Page, error)
- func (s *MemoryPageStore) List(_ context.Context, tenantID int64, subsite string) ([]*Page, error)
- func (s *MemoryPageStore) Update(_ context.Context, tenantID int64, p *Page) error
- type MemoryTenantAdminStore
- func (s *MemoryTenantAdminStore) CreateDomain(_ context.Context, d *Domain) error
- func (s *MemoryTenantAdminStore) CreateTenant(_ context.Context, t *Tenant) error
- func (s *MemoryTenantAdminStore) DeleteDomain(_ context.Context, tenantID, id int64) error
- func (s *MemoryTenantAdminStore) DeleteTenant(_ context.Context, id int64) error
- func (s *MemoryTenantAdminStore) GetTenant(_ context.Context, id int64) (*Tenant, error)
- func (s *MemoryTenantAdminStore) ListDomains(_ context.Context, tenantID int64) ([]*Domain, error)
- func (s *MemoryTenantAdminStore) ListTenants(_ context.Context) ([]*Tenant, error)
- func (s *MemoryTenantAdminStore) UpdateTenant(_ context.Context, t *Tenant) error
- type Page
- type PageStatus
- type PageStore
- type Tenant
- type TenantAdminStore
Constants ¶
This section is empty.
Variables ¶
var ( ErrTenantNotFound = errors.New("store: tenant not found") ErrTenantSlugTaken = errors.New("store: tenant slug taken") ErrDomainNotFound = errors.New("store: domain not found") ErrDomainTaken = errors.New("store: domain host taken") )
Sentinel errors.
var ErrNotFound = errors.New("page not found")
ErrNotFound is returned when a page does not exist OR exists in a different tenant scope. Callers ! NOT distinguish the two cases — leaking tenant existence cross-tenant violates V12/V16.
var ErrPathConflict = errors.New("page path conflict")
ErrPathConflict is returned when a Create or Update would produce a duplicate (tenant_id, subsite, path) tuple.
Functions ¶
This section is empty.
Types ¶
type Domain ¶
type Domain struct {
ID int64
TenantID int64
Host string // exact, lowercase, e.g. "cool-band.com"
SubsiteLabel string // empty = root subsite
Kind string // "vanity" | "preview" | "subdomain"
}
Domain is a vanity / preview / subdomain owned by a tenant.
Per SPEC V22: subsite scope is host-level (set on the Domain), not path-level — `cool-band.com` and `cool-band.com/tour` resolve to the same tenant but different subsites.
type MemoryPageStore ¶
type MemoryPageStore struct {
// contains filtered or unexported fields
}
MemoryPageStore is an in-memory PageStore for tests + local dev. Production uses a postgres-backed implementation that wires the same interface.
func NewMemoryPageStore ¶
func NewMemoryPageStore() *MemoryPageStore
NewMemoryPageStore returns an empty in-memory store.
func (*MemoryPageStore) Create ¶
Create inserts the page. Returns ErrPathConflict if (tenant, subsite, path) is already taken.
func (*MemoryPageStore) Delete ¶
func (s *MemoryPageStore) Delete(_ context.Context, tenantID, id int64) error
Delete removes the page. ErrNotFound on miss / wrong tenant.
func (*MemoryPageStore) Get ¶
Get returns a copy of the page. tenant_id mismatch → ErrNotFound (no leak — V12/V16).
func (*MemoryPageStore) GetByPath ¶
func (s *MemoryPageStore) GetByPath(_ context.Context, tenantID int64, subsite, path string) (*Page, error)
GetByPath looks up by (tenant, subsite, path).
type MemoryTenantAdminStore ¶
type MemoryTenantAdminStore struct {
// contains filtered or unexported fields
}
MemoryTenantAdminStore is the in-memory implementation used for tests and as the default until a persistent store is wired.
func NewMemoryTenantAdminStore ¶
func NewMemoryTenantAdminStore() *MemoryTenantAdminStore
func (*MemoryTenantAdminStore) CreateDomain ¶
func (s *MemoryTenantAdminStore) CreateDomain(_ context.Context, d *Domain) error
func (*MemoryTenantAdminStore) CreateTenant ¶
func (s *MemoryTenantAdminStore) CreateTenant(_ context.Context, t *Tenant) error
func (*MemoryTenantAdminStore) DeleteDomain ¶
func (s *MemoryTenantAdminStore) DeleteDomain(_ context.Context, tenantID, id int64) error
func (*MemoryTenantAdminStore) DeleteTenant ¶
func (s *MemoryTenantAdminStore) DeleteTenant(_ context.Context, id int64) error
func (*MemoryTenantAdminStore) ListDomains ¶
func (*MemoryTenantAdminStore) ListTenants ¶
func (s *MemoryTenantAdminStore) ListTenants(_ context.Context) ([]*Tenant, error)
func (*MemoryTenantAdminStore) UpdateTenant ¶
func (s *MemoryTenantAdminStore) UpdateTenant(_ context.Context, t *Tenant) error
type Page ¶
type Page struct {
ID int64
TenantID int64
Subsite string
Path string
Title string
BodyHTML string
BodyBlocks json.RawMessage
Status PageStatus
Version int
CreatedAt time.Time
UpdatedAt time.Time
}
Page is a CMS-managed dynamic page for a tenant.
Field semantics:
- TenantID: ! non-zero (V12 multi-tenancy guard)
- Subsite: "" → applies to all subsites; "<label>" → subsite-scoped
- Path: URL path (e.g. "/blog/welcome"); ! unique per (tenant, subsite)
- BodyHTML: rendered HTML for serve-time output (Render result)
- BodyBlocks: provider-specific block JSON (source of truth)
- Status: draft | published
- Version: monotonic per-page edit counter
type PageStatus ¶
type PageStatus string
PageStatus is the publish state of a CMS page.
const ( StatusDraft PageStatus = "draft" StatusPublished PageStatus = "published" )
type PageStore ¶
type PageStore interface {
Create(ctx context.Context, tenantID int64, p *Page) error
Get(ctx context.Context, tenantID int64, id int64) (*Page, error)
GetByPath(ctx context.Context, tenantID int64, subsite, path string) (*Page, error)
Update(ctx context.Context, tenantID int64, p *Page) error
Delete(ctx context.Context, tenantID int64, id int64) error
List(ctx context.Context, tenantID int64, subsite string) ([]*Page, error)
}
PageStore persists Page records scoped by tenant.
Every method takes tenantID as the FIRST arg AFTER ctx — making tenant-scoping impossible to forget at the call site (V12 / V15).
type Tenant ¶
type Tenant struct {
ID int64
Slug string
Label string
ThemeID string
CreatedAt time.Time
UpdatedAt time.Time
}
Tenant is a multisite tenant.
type TenantAdminStore ¶
type TenantAdminStore interface {
// CreateTenant assigns an ID + timestamps. Slug must be unique;
// returns ErrTenantSlugTaken otherwise.
CreateTenant(ctx context.Context, t *Tenant) error
GetTenant(ctx context.Context, id int64) (*Tenant, error)
UpdateTenant(ctx context.Context, t *Tenant) error
DeleteTenant(ctx context.Context, id int64) error
ListTenants(ctx context.Context) ([]*Tenant, error)
// CreateDomain attaches a Domain to an existing tenant. Host must be
// globally unique across all tenants; returns ErrDomainTaken otherwise.
CreateDomain(ctx context.Context, d *Domain) error
DeleteDomain(ctx context.Context, tenantID, id int64) error
ListDomains(ctx context.Context, tenantID int64) ([]*Domain, error)
}
TenantAdminStore is the CRUD surface for the multisite admin. It is separate from TenantStore (the resolver's read-only interface) so reads can be optimised independently from writes.
All operations are tenant-scoped via TenantID (no cross-tenant leak).