templates

package
v1.801.307 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package templates mounts /v1/templates — the Hanzo starter-kit gallery, in TWO layers that never mix:

  • the PUBLIC catalog: deployable app/site scaffolds (source of truth: hanzoai/gallery), vendored so the unified `cloud` binary ships it with no external dependency. Reference content — embedded, immutable, and with NO write route, so nothing a customer does can add to it.
  • a customer's OWN templates: rows in {DataDir}/templates.db keyed by the gateway-minted org (principal.Org — never a request field), PRIVATE to that org. Only that org lists, reads, edits, deletes, or forks them.

Two layers rather than one visibility flag is the whole safety argument: a private template cannot surface in the public hanzo.app catalog by CONSTRUCTION — it lives in a different container, reached only by a query that binds org — not by a filter every future reader has to remember. An anonymous GET never touches the store at all.

A slug is single-valued across both layers: publishing over a public slug is 409, so a slug still names exactly one template and no org can shadow the gallery.

ONE template is ONE entry. The shapes it ships in — format, page, theme — are Variants inside that entry, chosen at fork time.

Surface:

GET    /v1/templates          public catalog + (validated caller) that org's own -> {data:[Template]}
GET    /v1/templates/:slug    one template: the caller org's own, else public     -> Template
POST   /v1/templates          publish a template PRIVATE to the caller's org      -> 201 Template
PUT    /v1/templates/:slug    replace the caller org's own template               -> Template
DELETE /v1/templates/:slug    delete the caller org's own template                -> 204

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount registers the templates surface. templates is a "complex" mount now (a package-global `mounted` so Lookup is ONE door for the projects fork flow, and a shutdown that closes the store), so it builds the Service value directly.

func Shutdown

func Shutdown(_ context.Context) error

Shutdown closes the per-org store. Idempotent.

Types

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store holds every org's PRIVATE templates — ONE SQLite file ({DataDir}/templates.db) whose isolation key is the org column, the same discipline clients/projects and clients/marketplace keep.

The PUBLIC catalog is deliberately NOT in this table: it stays the embedded catalog.json, which has no write route at all. So a private row has no path into the public gallery by CONSTRUCTION, not by a filter someone has to remember to write — the two live in different containers, and every read of this one binds org.

The whole Template is one JSON `doc`: it is already the exact shape the API serves, so a row can never drift from it as fields are added, and (org, slug) — the only two things ever queried on — stay real columns.

func (*Store) Close

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, org, slug string) (bool, error)

Delete removes org's own template at slug, reporting whether a row went.

func (*Store) Get

func (s *Store) Get(ctx context.Context, org, slug string) (Template, bool, error)

Get returns org's own template at slug. found=false (nil error) when absent — including when the row belongs to ANOTHER org, because the WHERE binds org.

func (*Store) List

func (s *Store) List(ctx context.Context, org string) ([]Template, error)

List returns org's own templates (tenant-scoped read; there is no all-orgs counterpart, because nothing in this subsystem legitimately wants one).

func (*Store) Put

func (s *Store) Put(ctx context.Context, t Template, create bool, now int64) error

Put writes t for its own org. create=true inserts and reports errConflict if the org already holds that slug; create=false replaces an existing row and reports sql.ErrNoRows if there is none — so "publish" can never silently clobber and "edit" can never silently create.

type Template

type Template struct {
	Slug        string    `json:"slug"`
	Title       string    `json:"title"`
	Category    string    `json:"category"`
	Description string    `json:"description"`
	Framework   string    `json:"framework"`
	Features    []string  `json:"features"`
	UseCase     string    `json:"useCase"`
	Tier        *int      `json:"tier,omitempty"`
	Rating      *float64  `json:"rating,omitempty"`
	Source      string    `json:"source"`
	Preview     string    `json:"preview"`
	Demo        string    `json:"demo,omitempty"`     // live demo (<slug>.hanzo.app), when deployed
	Variants    []Variant `json:"variants,omitempty"` // the shapes this template ships in
	Org         string    `json:"org,omitempty"`      // owner of a PRIVATE template; empty in the public catalog
}

Template is one starter kit as the console gallery browser consumes it. The `Source`/`Preview` URLs point at the live gallery (gallery.hanzo.ai) for a public entry and at whatever the customer supplies for one of their own; `Demo` is the deployed site itself.

Org is the OWNER of a private template, stamped by the SERVER from the row's key. It is empty on every public catalog entry — that emptiness is what the console badges "yours" on, and it is never read from a request body.

func List

func List() ([]Template, error)

List returns the validated PUBLIC starter-kit catalog (the SAME slice the anonymous HTTP GET serves). Read-only reference content; callers must not mutate the returned slice. Private org templates are deliberately NOT here — they are reachable only through Lookup, which takes the org it isolates on.

func Lookup

func Lookup(ctx context.Context, org, slug string) (Template, bool)

Lookup resolves ONE template for a caller org: that org's OWN private template first, then the public catalog. It is the single door other subsystems (the projects fork flow) read templates through, so "which templates may this org use" is answered in exactly one place. org "" (anonymous/unvalidated) resolves against the public catalog only.

func (Template) Variant

func (t Template) Variant(id string) (Variant, bool)

Variant resolves a variant id against the template and is the ONE place the resolution rule lives. The empty id means "no preference" and yields the template's first (default) shape; a template that ships in a single shape answers with itself, so callers never branch on len(Variants).

type Variant

type Variant struct {
	ID        string `json:"id"`                  // selector, unique within the template ("react", "grid-3-fluid")
	Label     string `json:"label"`               // human label for the picker
	Kind      string `json:"kind"`                // the axis it varies: format | page | theme
	Framework string `json:"framework,omitempty"` // only when it differs from the template's
	Source    string `json:"source"`
}

Variant is one SHAPE of a template: the same design in another format (html/react/bootstrap), on another page (folio's about/contact/grid-3), or in another theme. A variant is an option resolved at fork time from what the user asks for — never a catalog row of its own, which is what made one portfolio template read as 26 templates and one dashboard as 2.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL