blog

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AuthorCols = authorColumns{
	ID:        sqlb.Typed[string]("id"),
	OrgID:     sqlb.Typed[string]("org_id"),
	Email:     sqlb.TextColumn[string]("email"),
	Name:      sqlb.TextColumn[string]("name"),
	CreatedAt: sqlb.Typed[time.Time]("created_at"),
	UpdatedAt: sqlb.Typed[time.Time]("updated_at"),
}

AuthorCols are the typed columns of authors. Hidden columns are omitted: a predicate against one should not compile.

View Source
var OrgCols = orgColumns{
	ID:        sqlb.Typed[string]("id"),
	Name:      sqlb.TextColumn[string]("name"),
	Slug:      sqlb.TextColumn[string]("slug"),
	CreatedAt: sqlb.Typed[time.Time]("created_at"),
	UpdatedAt: sqlb.Typed[time.Time]("updated_at"),
}

OrgCols are the typed columns of orgs.

View Source
var PostCols = postColumns{
	ID:          sqlb.Typed[string]("id"),
	OrgID:       sqlb.Typed[string]("org_id"),
	AuthorID:    sqlb.Typed[string]("author_id"),
	Title:       sqlb.TextColumn[string]("title"),
	Body:        sqlb.TextColumn[string]("body"),
	Status:      sqlb.Typed[PostStatus]("status"),
	ViewCount:   sqlb.Typed[int64]("view_count"),
	PublishedAt: sqlb.Typed[time.Time]("published_at"),
	CreatedAt:   sqlb.Typed[time.Time]("created_at"),
	UpdatedAt:   sqlb.Typed[time.Time]("updated_at"),
	DeletedAt:   sqlb.Typed[time.Time]("deleted_at"),
}

PostCols are the typed columns of posts.

Functions

func Register

func Register(api huma.API, db sqlb.Executor) error

Register mounts every exposed resource on api.

The handlers are rest.Resource, instantiated per model. Registration is generic rather than reflective because query hooks are keyed by type: a BeforeQuery hook registered on a model applies to its REST reads too, which is how tenant scoping stops being something each handler must remember.

func RegisterHooks

func RegisterHooks()

Hand-written domain rules, in a file the generator does not touch.

RegisterHooks installs the predicate that makes schema.SoftDelete mean something. The schema adds posts.deleted_at and stops there — nothing in the runtime reads the column — so hiding the deleted rows is a registration, and BeforeQuery is where it goes: one registration constrains every read of Post, including the ones the generated REST handlers issue. That is the argument of ADR-0008, and this is what it looks like applied.

It is an exported function rather than an init so the registration is visible at the call site, and so a test can choose not to make it.

Tenant scoping belongs on the same hook — `q.Where(sqlb.F("org_id").Eq(org))`, reading the org out of ctx. It is left out here because the example has no authentication to read it from, not because it is a separate mechanism.

func RegisterPostSoftDelete

func RegisterPostSoftDelete(api huma.API, db sqlb.Executor)

RegisterPostSoftDelete serves DELETE /posts/{id} as an update to deleted_at.

Post leaves OpDelete out of its Expose, because the generated delete is a real DELETE and this table's deletes are meant to be soft. No hook can bridge that: BeforeDelete receives a *Delete, so it can abort the statement or amend its predicate, but not turn it into an UPDATE. The endpoint is written here instead — which is the same seam post_ext.go uses, for the same reason.

The BeforeQuery registration in hooks.go is the other half. Without it this endpoint would hide nothing, and the row would come straight back on the next list.

Types

type Author

type Author struct {
	ID           string                 `db:"id" json:"id" sqlb:"pk,default,filter,readonly"`
	OrgID        string                 `db:"org_id" json:"org_id" sqlb:"filter,expand"`
	Org          *Org                   `db:"-" json:"org,omitempty" sqlb:"expands=org_id"` // filled in by ?expand=org
	Email        string                 `db:"email" json:"email" sqlb:"filter,search"`
	Name         string                 `db:"name" json:"name" sqlb:"filter,sort,search"`
	PasswordHash string                 `db:"password_hash" json:"-" sqlb:"hidden"`
	CreatedAt    time.Time              `db:"created_at" json:"created_at" sqlb:"default,sort,readonly"`
	UpdatedAt    time.Time              `db:"updated_at" json:"updated_at" sqlb:"default,sort,readonly"`
	Posts        *sqlb.Collection[Post] `db:"-" json:"posts,omitempty" sqlb:"expands=author_id,order=-published_at,limit=2"` // filled in by ?expand=posts
}

Author is a row of authors.

func (Author) TableName

func (Author) TableName() string

TableName is the table Author maps to.

type AuthorCreate

type AuthorCreate struct {
	OrgID string `json:"org_id"`
	Email string `json:"email"`
	Name  string `json:"name"`
}

AuthorCreate is the request body for creating a Author.

Read-only columns are absent: the database or a BeforeCreate hook owns them. A column with a default is optional, so leaving it out means the database supplies the value rather than the zero value overwriting it.

func (AuthorCreate) Row

func (c AuthorCreate) Row() (*Author, error)

Row builds the row to insert. It satisfies rest.CreateBody.

type AuthorPatch

type AuthorPatch struct {
	OrgID *string `json:"org_id,omitempty"`
	Email *string `json:"email,omitempty"`
	Name  *string `json:"name,omitempty"`
	// contains filtered or unexported fields
}

AuthorPatch is the request body for patching a Author.

Every field is a pointer and every field is optional, so a request writes only the columns it names. Immutable columns are absent: they are settable once, at create.

func (AuthorPatch) Changes

func (u AuthorPatch) Changes() (map[string]any, error)

Changes reports the columns the request named. It satisfies rest.UpdateBody.

func (*AuthorPatch) UnmarshalJSON

func (u *AuthorPatch) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the body and remembers which properties were present.

Without this a nil pointer would be ambiguous: `{}` and `{"org_id": null}` decode identically, but the first must change nothing and the second must write NULL.

type AuthorUpdate

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

AuthorUpdate is a typed update statement for authors.

func UpdateAuthor

func UpdateAuthor() *AuthorUpdate

UpdateAuthor starts a typed update.

func (*AuthorUpdate) SetCreatedAt

func (u *AuthorUpdate) SetCreatedAt(v time.Time) *AuthorUpdate

SetCreatedAt sets created_at.

func (*AuthorUpdate) SetEmail

func (u *AuthorUpdate) SetEmail(v string) *AuthorUpdate

SetEmail sets email.

func (*AuthorUpdate) SetName

func (u *AuthorUpdate) SetName(v string) *AuthorUpdate

SetName sets name.

func (*AuthorUpdate) SetOrgID

func (u *AuthorUpdate) SetOrgID(v string) *AuthorUpdate

SetOrgID sets org_id.

func (*AuthorUpdate) SetPasswordHash

func (u *AuthorUpdate) SetPasswordHash(v string) *AuthorUpdate

SetPasswordHash sets password_hash.

func (*AuthorUpdate) SetUpdatedAt

func (u *AuthorUpdate) SetUpdatedAt(v time.Time) *AuthorUpdate

SetUpdatedAt sets updated_at.

func (*AuthorUpdate) Stmt

func (u *AuthorUpdate) Stmt() *sqlb.Update[Author]

Stmt exposes the underlying statement for what the wrapper does not cover, such as Everything, SetExpr, Exec and One.

func (*AuthorUpdate) Where

func (u *AuthorUpdate) Where(preds ...sqlb.Pred) *AuthorUpdate

Where narrows the affected rows.

type Org

type Org struct {
	ID        string                   `db:"id" json:"id" sqlb:"pk,default,filter,readonly"`
	Name      string                   `db:"name" json:"name" sqlb:"filter,sort,search"`
	Slug      string                   `db:"slug" json:"slug" sqlb:"filter"`
	CreatedAt time.Time                `db:"created_at" json:"created_at" sqlb:"default,sort,readonly"`
	UpdatedAt time.Time                `db:"updated_at" json:"updated_at" sqlb:"default,sort,readonly"`
	Authors   *sqlb.Collection[Author] `db:"-" json:"authors,omitempty" sqlb:"expands=org_id,order=name,limit=50"` // filled in by ?expand=authors
}

Org a tenant. Every other table is scoped to one.

func (Org) TableName

func (Org) TableName() string

TableName is the table Org maps to.

type OrgUpdate

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

OrgUpdate is a typed update statement for orgs.

func UpdateOrg

func UpdateOrg() *OrgUpdate

UpdateOrg starts a typed update.

func (*OrgUpdate) SetCreatedAt

func (u *OrgUpdate) SetCreatedAt(v time.Time) *OrgUpdate

SetCreatedAt sets created_at.

func (*OrgUpdate) SetName

func (u *OrgUpdate) SetName(v string) *OrgUpdate

SetName sets name.

func (*OrgUpdate) SetSlug

func (u *OrgUpdate) SetSlug(v string) *OrgUpdate

SetSlug sets slug.

func (*OrgUpdate) SetUpdatedAt

func (u *OrgUpdate) SetUpdatedAt(v time.Time) *OrgUpdate

SetUpdatedAt sets updated_at.

func (*OrgUpdate) Stmt

func (u *OrgUpdate) Stmt() *sqlb.Update[Org]

Stmt exposes the underlying statement for what the wrapper does not cover, such as Everything, SetExpr, Exec and One.

func (*OrgUpdate) Where

func (u *OrgUpdate) Where(preds ...sqlb.Pred) *OrgUpdate

Where narrows the affected rows.

type Post

type Post struct {
	ID          string     `db:"id" json:"id" sqlb:"pk,default,filter,readonly"`
	OrgID       string     `db:"org_id" json:"org_id"`
	AuthorID    string     `db:"author_id" json:"author_id" sqlb:"filter,expand"`
	Author      *Author    `db:"-" json:"author,omitempty" sqlb:"expands=author_id"` // filled in by ?expand=author
	Title       string     `db:"title" json:"title" sqlb:"filter,sort,search"`
	Body        string     `db:"body" json:"body" sqlb:"filter,search"`
	Status      PostStatus `db:"status" json:"status" sqlb:"default,filter,sort"`
	ViewCount   int64      `db:"view_count" json:"view_count" sqlb:"default,filter,sort,readonly"`
	PublishedAt *time.Time `db:"published_at" json:"published_at" sqlb:"filter,sort"`
	CreatedAt   time.Time  `db:"created_at" json:"created_at" sqlb:"default,sort,readonly"`
	UpdatedAt   time.Time  `db:"updated_at" json:"updated_at" sqlb:"default,sort,readonly"`
	DeletedAt   *time.Time `db:"deleted_at" json:"deleted_at" sqlb:"readonly,softdelete"`
}

Post a blog post.

func (Post) TableName

func (Post) TableName() string

TableName is the table Post maps to.

type PostCreate

type PostCreate struct {
	OrgID       string      `json:"org_id"`
	AuthorID    string      `json:"author_id"`
	Title       string      `json:"title"`
	Body        string      `json:"body"`
	Status      *PostStatus `json:"status,omitempty" enum:"draft,review,published"`
	PublishedAt *time.Time  `json:"published_at,omitempty"`
}

PostCreate is the request body for creating a Post.

Read-only columns are absent: the database or a BeforeCreate hook owns them. A column with a default is optional, so leaving it out means the database supplies the value rather than the zero value overwriting it.

func (PostCreate) Row

func (c PostCreate) Row() (*Post, error)

Row builds the row to insert. It satisfies rest.CreateBody.

type PostPatch

type PostPatch struct {
	OrgID       *string     `json:"org_id,omitempty"`
	AuthorID    *string     `json:"author_id,omitempty"`
	Title       *string     `json:"title,omitempty"`
	Body        *string     `json:"body,omitempty"`
	Status      *PostStatus `json:"status,omitempty" enum:"draft,review,published"`
	PublishedAt *time.Time  `json:"published_at,omitempty"`
	// contains filtered or unexported fields
}

PostPatch is the request body for patching a Post.

Every field is a pointer and every field is optional, so a request writes only the columns it names. Immutable columns are absent: they are settable once, at create.

func (PostPatch) Changes

func (u PostPatch) Changes() (map[string]any, error)

Changes reports the columns the request named. It satisfies rest.UpdateBody.

func (*PostPatch) UnmarshalJSON

func (u *PostPatch) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes the body and remembers which properties were present.

Without this a nil pointer would be ambiguous: `{}` and `{"org_id": null}` decode identically, but the first must change nothing and the second must write NULL.

type PostStatus

type PostStatus string

PostStatus is the posts.status column's value set.

const (
	PostStatusDraft     PostStatus = "draft"
	PostStatusReview    PostStatus = "review"
	PostStatusPublished PostStatus = "published"
)

type PostUpdate

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

PostUpdate is a typed update statement for posts.

func UpdatePost

func UpdatePost() *PostUpdate

UpdatePost starts a typed update.

func (*PostUpdate) AddViewCount

func (u *PostUpdate) AddViewCount(n int64) *PostUpdate

AddViewCount increments the counter in the database rather than reading it first, so concurrent increments do not lose updates.

The generator cannot produce this: view_count is ReadOnly in the schema, so there is no SetViewCount, and the read-modify-write it would replace is a correctness decision rather than a mechanical one.

func (*PostUpdate) SetAuthorID

func (u *PostUpdate) SetAuthorID(v string) *PostUpdate

SetAuthorID sets author_id.

func (*PostUpdate) SetBody

func (u *PostUpdate) SetBody(v string) *PostUpdate

SetBody sets body.

func (*PostUpdate) SetCreatedAt

func (u *PostUpdate) SetCreatedAt(v time.Time) *PostUpdate

SetCreatedAt sets created_at.

func (*PostUpdate) SetDeletedAt

func (u *PostUpdate) SetDeletedAt(v *time.Time) *PostUpdate

SetDeletedAt sets deleted_at.

func (*PostUpdate) SetOrgID

func (u *PostUpdate) SetOrgID(v string) *PostUpdate

SetOrgID sets org_id.

func (*PostUpdate) SetPublishedAt

func (u *PostUpdate) SetPublishedAt(v *time.Time) *PostUpdate

SetPublishedAt sets published_at.

func (*PostUpdate) SetStatus

func (u *PostUpdate) SetStatus(v PostStatus) *PostUpdate

SetStatus sets status.

func (*PostUpdate) SetTitle

func (u *PostUpdate) SetTitle(v string) *PostUpdate

SetTitle sets title.

func (*PostUpdate) SetUpdatedAt

func (u *PostUpdate) SetUpdatedAt(v time.Time) *PostUpdate

SetUpdatedAt sets updated_at.

func (*PostUpdate) SetViewCount

func (u *PostUpdate) SetViewCount(v int64) *PostUpdate

SetViewCount sets view_count.

func (*PostUpdate) Stmt

func (u *PostUpdate) Stmt() *sqlb.Update[Post]

Stmt exposes the underlying statement for what the wrapper does not cover, such as Everything, SetExpr, Exec and One.

func (*PostUpdate) Where

func (u *PostUpdate) Where(preds ...sqlb.Pred) *PostUpdate

Where narrows the affected rows.

Directories

Path Synopsis
Package blogschema is the schema definition for the blog example: the single source of truth that an author, or an agent, edits.
Package blogschema is the schema definition for the blog example: the single source of truth that an author, or an agent, edits.

Jump to

Keyboard shortcuts

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