Help us shape the future of Go! Take the 2026 Go Developer Survey

entities

package
v0.117.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package entities is how content is grouped: tags, artists, series, characters, channels, playlists and featured rails. Entity kinds come from kinds.Config.EntityKinds. A entity has localized names and aliases, per-language text, declared attributes, a cover, a parent, an optional scope and edges to other entities; content joins it through memberships, at content level or for one version where the kind allows. A version's effective entities (EffectiveEntities) feed content_facets.entity_ids; counts derive from those facets.

Index

Constants

View Source
const AvatarPreset = "avatar"

AvatarPreset is the media preset avatars render in (media.AvatarPreset).

View Source
const DefaultDeletedRetention = 30 * 24 * time.Hour

DefaultDeletedRetention is how long a deleted user-owned entity keeps its media folder, so a restore brings it back.

View Source
const NameSeparators = `[[:space:]_‐‑‒–—―-]+`

NameSeparators are ignored when names match. Removing all punctuation would conflate distinct names such as C/C++ or AC/DC and ACDC.

View Source
const ReconcileInterval = time.Minute

ReconcileInterval is how often the engine syncs every entity whose scope or owned content does not match its state yet, and purges deleted entities' media past the retention.

Variables

View Source
var (
	ErrInvalid   = errors.New("entities: invalid input")
	ErrNotFound  = errors.New("entities: not found")
	ErrConflict  = errors.New("entities: conflict")
	ErrForbidden = errors.New("entities: forbidden")
	// ErrLimit is a person owning as many entities of a kind as it allows
	// (kinds.EntityKind.MaxPerOwner).
	ErrLimit = errors.New("entities: limit reached")
)

Sentinel errors; callers match them with errors.Is.

Functions

This section is empty.

Types

type AppBinding

type AppBinding interface {
	ContentKitScope() (tenantID, appID string)
}

AppBinding identifies the tenant/app an authorization scope provider serves. A scoped engine requires a matching binding before enabling auth_scope kinds.

type Avatar

type Avatar struct {
	Image string `json:"image,omitempty"`
	// Renditions are its public images, filled on reads (Options.Covers).
	Renditions []Rendition `json:"renditions,omitempty"`
}

Avatar is an entity's square picture (kinds.EntityKind.HasAvatar): an image uploaded to the entity's media folder ("avatar/i-{uuid}"), selected by name. Without a selection a read shows the folder's first avatar, else the kind's default.

type CardOptions

type CardOptions struct {
	// IncludeInternal is a trusted management read; As stores require staff.
	IncludeInternal bool
	// Language resolves names and paths as ListOptions.Language does.
	Language string
	// Covers fills covers' and avatars' renditions in one call; chips leave
	// them out.
	Covers bool
}

CardOptions resolve Cards.

type ContentEntities

type ContentEntities struct {
	// ContentID names the content; routes take it from the path.
	ContentID string `json:"-"`
	// VersionScope limits replacement to one ownership scope. Nil preserves
	// the legacy all-version replacement; empty means shared content links;
	// otherwise it names the one version whose links may change.
	VersionScope *string `json:"-"`
	// Kinds limits the write to these entity kinds; empty means every entity
	// kind the content's kind declares.
	Kinds       []string     `json:"kinds,omitempty"`
	Memberships []Membership `json:"memberships"`
	// Create links these names too, creating entities of creatable kinds that
	// no active entity names yet, in the same transaction.
	Create []NewEntity `json:"create,omitempty"`
}

ContentEntities is one content's memberships in the entity kinds Kinds names.

type ContentMemberships

type ContentMemberships struct {
	ContentID   string       `json:"content_id"`
	Memberships []Membership `json:"memberships"`
	Entities    []EntityCard `json:"entities"`
}

ContentMemberships is a content's memberships with the entities they name.

type Count

type Count struct {
	ContentKind string `json:"content_kind"`
	Language    string `json:"language"`
	Count       int    `json:"count"`
}

Count is the number of distinct live, listed contents of one kind in a entity (its descendants included) or owned by it, serving one language; Language "" counts every language once.

type Cover

type Cover struct {
	Image     string `json:"image,omitempty"`
	ContentID string `json:"content_id,omitempty"`
	Auto      bool   `json:"auto,omitempty"`
	// Renditions are its public images, filled on reads (Options.Covers).
	Renditions []Rendition `json:"renditions,omitempty"`
}

Cover is an entity's picture: an image uploaded to the entity's media folder ("i-{uuid}") or a member content's cover, the cover of the edition a reader's language opens by default. Auto covers are the most-favorited live member, selected at read time when none is explicitly chosen.

type CoverOf

type CoverOf struct {
	Kind     string
	ID       EntityID
	Cover    Cover
	Language string
	Preset   string
}

CoverOf is one entity's picture to resolve: its cover, or with Preset "avatar" its avatar (Cover.Image the selected one, if any). A content's cover is its default edition's in Language.

type CoverResolver

type CoverResolver interface {
	Covers(ctx context.Context, covers []CoverOf) ([][]Rendition, error)
}

CoverResolver turns covers and avatars into their public renditions, per picture in order, one call per page: media/versions.Service.EntityCovers.

type Edge

type Edge struct {
	From     EntityID `json:"from"`
	Relation string   `json:"relation"`
	To       EntityID `json:"to"`
}

Edge is a typed relation between two entities beside parent and scope: "from is <relation> of to", such as a character member_of a second series.

type EdgeInput

type EdgeInput struct {
	Relation string   `json:"relation"`
	To       EntityID `json:"to"`
}

EdgeInput is an edge from the entity a route names.

type Entity

type Entity struct {
	ID   EntityID `json:"id"`
	Kind string   `json:"kind"`
	Slug string   `json:"slug"`
	// Internal marks a classification used by policy rather than public metadata.
	Internal bool `json:"internal"`
	// ScopeID is the entity the slug is unique within (a character's series).
	ScopeID     EntityID       `json:"scope_id,omitempty"`
	ParentID    EntityID       `json:"parent_id,omitempty"`
	State       State          `json:"state"`
	MergedInto  EntityID       `json:"merged_into,omitempty"`
	Attributes  map[string]any `json:"attributes"`
	Cover       *Cover         `json:"cover,omitempty"`
	Avatar      *Avatar        `json:"avatar,omitempty"`
	OwnerUserID string         `json:"owner_user_id,omitempty"`
	// AuthScope is the AuthKit group whose holders of
	// access.EntitySettingsManage edit it; ContentKit provisions it for a
	// kind with auth_scope (Options.Scopes).
	AuthScope string    `json:"auth_scope,omitempty"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	Revision  int64     `json:"revision"`
}

Entity is one entity record.

type EntityCard

type EntityCard struct {
	ID           EntityID `json:"id"`
	Kind         string   `json:"kind"`
	Slug         string   `json:"slug"`
	Internal     bool     `json:"internal"`
	ScopeID      EntityID `json:"scope_id,omitempty"`
	ParentID     EntityID `json:"parent_id,omitempty"`
	Name         string   `json:"name"`
	NameLanguage string   `json:"name_language,omitempty"`
	Code         string   `json:"code,omitempty"`
	Path         string   `json:"path,omitempty"`
	Cover        *Cover   `json:"cover,omitempty"`
	// Avatar is a user-owned entity's avatar.
	Avatar     *Avatar        `json:"avatar,omitempty"`
	Count      int            `json:"count"`
	Attributes map[string]any `json:"attributes"`
	// Access is a sellable entity's sale: its key, active offer and the
	// viewer's standing.
	Access    *offers.EntityAccess `json:"access,omitempty"`
	CreatedAt time.Time            `json:"created_at"`
}

EntityCard is a listed entity: what a directory page or a chip needs.

type EntityDetail

type EntityDetail struct {
	Entity
	Name         string       `json:"name"`
	NameLanguage string       `json:"name_language,omitempty"`
	Code         string       `json:"code,omitempty"`
	Path         string       `json:"path,omitempty"`
	Names        []Name       `json:"names"`
	Text         []Text       `json:"text"`
	Parent       *EntityCard  `json:"parent,omitempty"`
	Scope        *EntityCard  `json:"scope,omitempty"`
	Children     []EntityCard `json:"children"`
	Edges        []Edge       `json:"edges"`
	Counts       []Count      `json:"counts"`
	// Access is a sellable entity's sale: its key, active offer and the
	// viewer's standing.
	Access *offers.EntityAccess `json:"access,omitempty"`
	// Offer is a sellable entity's current offer, for its offer editors.
	Offer *offers.Offer `json:"offer,omitempty"`
}

EntityDetail is one entity with everything its page shows.

type EntityID

type EntityID = contentref.EntityID

EntityID identifies one entity within a tenant: a UUIDv7, kept across import.

type EntityInput

type EntityInput struct {
	// ID is optional; omitted ids are generated.
	ID   EntityID `json:"id,omitempty"`
	Kind string   `json:"kind"`
	Slug string   `json:"slug"`
	// Internal classifications are visible only through management reads.
	Internal bool `json:"internal,omitempty"`
	// ScopeID is required for a kind scoped by another (a character's series).
	ScopeID    EntityID       `json:"scope_id,omitempty"`
	ParentID   EntityID       `json:"parent_id,omitempty"`
	Attributes map[string]any `json:"attributes,omitempty"`
	Cover      *Cover         `json:"cover,omitempty"`
	Avatar     *Avatar        `json:"avatar,omitempty"`
	// OwnerUserID owns a user-owned entity; a person creating one owns it.
	OwnerUserID string `json:"owner_user_id,omitempty"`
	// AuthScope is staff's (an import's); ContentKit provisions it otherwise.
	AuthScope string `json:"auth_scope,omitempty"`
	Names     []Name `json:"names,omitempty"`
	Text      []Text `json:"text,omitempty"`
}

EntityInput creates one entity.

type EntityNames

type EntityNames struct {
	ID    EntityID `json:"id"`
	Names []Name   `json:"names"`
}

EntityNames is one entity's names in a names write.

type EntityPage

type EntityPage struct {
	Entities []EntityCard `json:"entities"`
	// NextCursor pages on; empty on the last page.
	NextCursor string `json:"next_cursor,omitempty"`
	// Total is every matching entity, on numbered pages only.
	Total *int `json:"total,omitempty"`
}

EntityPage is one page of a listing.

type EntityUpdate

type EntityUpdate struct {
	// ID names the entity; routes take it from the path.
	ID       EntityID  `json:"-"`
	Slug     *string   `json:"slug,omitempty"`
	Internal *bool     `json:"internal,omitempty"`
	ScopeID  *EntityID `json:"scope_id,omitempty"`
	// ParentID "" detaches the entity from its parent.
	ParentID *EntityID `json:"parent_id,omitempty"`
	// State restores (active) or deletes an entity.
	State *State `json:"state,omitempty"`
	// Attributes replaces every attribute.
	Attributes map[string]any `json:"attributes,omitempty"`
	// Cover selects a cover; an empty one returns to the automatic cover.
	Cover *Cover `json:"cover,omitempty"`
	// Avatar selects the entity's avatar (kinds with avatars); an empty one returns to
	// the first.
	Avatar      *Avatar `json:"avatar,omitempty"`
	OwnerUserID *string `json:"owner_user_id,omitempty"`
	AuthScope   *string `json:"auth_scope,omitempty"`
	// Names replaces every name and alias.
	Names []Name `json:"names,omitempty"`
	// Text replaces the text of every language.
	Text []Text `json:"text,omitempty"`
}

EntityUpdate changes one entity's given fields; nil fields are unchanged.

type Folders

type Folders interface {
	ExposeTx(ctx context.Context, tx pgx.Tx, entities []Ref) error
	DeleteTx(ctx context.Context, tx pgx.Tx, entities []Ref) error
}

Folders brings entities' media folders in line with their state in a writing transaction: ExposeTx hides a deleted entity's and shows a restored one's, DeleteTx deletes them.

type ImportEntity

type ImportEntity struct {
	ID   EntityID
	Kind string
	Slug string
	// Internal preserves the stored classification when omitted.
	Internal    *bool
	ScopeID     EntityID
	ParentID    EntityID
	State       State
	Attributes  map[string]any
	Cover       *Cover
	Avatar      *Avatar
	OwnerUserID string
	// AuthScope names the entity's AuthKit scope; empty keeps the stored one.
	AuthScope string
	Names     []Name
	Text      []Text
	// Edges are the entity's edges to others (From is the entity).
	Edges     []Edge
	Code      string
	CreatedAt *time.Time
	UpdatedAt *time.Time
}

ImportEntity is an entity as the bulk import writes it: its id kept. Nil Names, Text and Edges keep what is stored; given ones replace it.

type ListOptions

type ListOptions struct {
	Kind string
	// Query matches a name, alias or slug for management selectors.
	Query string
	// IncludeInternal is a trusted management read; As stores require staff.
	IncludeInternal bool
	// Deleted lists deleted entities instead of active ones; with IncludeInternal only.
	Deleted bool
	// Parent keeps the children of one entity (sub-series of a series).
	Parent EntityID
	// Scope keeps the entities scoped by one entity (characters of a series).
	Scope EntityID
	Slug  string
	// Letter keeps entities whose sort name starts with it (a-z), or with
	// anything else ("#").
	Letter string
	// Language resolves names, sort keys and slugs, scopes counts and
	// prefixes paths. Empty: names and paths in the first configured
	// language, counts over every language.
	Language string
	// ContentKind scopes counts to one content kind; empty sums them.
	ContentKind string
	Sort        Sort
	// Cursor is the previous page's NextCursor under the same Sort.
	Cursor string
	// Page numbers pages from 1 instead of Cursor; the page then carries Total.
	Page int
	// Limit defaults to 50 and is capped at 200.
	Limit int
}

ListOptions filters, orders and pages List. Only active entities are listed.

type LocalScopes

type LocalScopes interface {
	HoldingScopes(ctx context.Context, user string, kinds, scopeIDs []string) (map[string][]string, error)
}

LocalScopes optionally narrows authorization lookups to scope IDs verified by this app's store. It lets a shared identity directory resolve grants without enumerating or trusting entities belonging to other apps.

type MemberOptions

type MemberOptions struct {
	// Cursor is the previous page's NextCursor.
	Cursor string
	// Limit defaults to 50 and is capped at 500.
	Limit int
}

MemberOptions pages Members.

type MemberPage

type MemberPage struct {
	Members []Membership `json:"members"`
	// NextCursor pages on; empty on the last page.
	NextCursor string `json:"next_cursor,omitempty"`
}

MemberPage is one page of an entity's memberships.

type MemberRef

type MemberRef struct {
	ContentID string `json:"content_id"`
	VersionID string `json:"version_id,omitempty"`
}

MemberRef names a member of an entity: content, or one version of it.

type Membership

type Membership struct {
	EntityID    EntityID        `json:"group_id,omitempty"`
	ContentID   string          `json:"content_id,omitempty"`
	VersionID   string          `json:"version_id,omitempty"`
	Role        string          `json:"role,omitempty"`
	Position    *int            `json:"position,omitempty"`
	ActiveFrom  *time.Time      `json:"active_from,omitempty"`
	ActiveUntil *time.Time      `json:"active_until,omitempty"`
	State       MembershipState `json:"state,omitempty"`
}

Membership places content, or one version of it, in an entity. Position orders an ordered entity (appended when omitted); the window bounds when a windowed membership (a featured rail) is effective.

type MembershipState

type MembershipState string

MembershipState: a proposed membership (a suggestion awaiting review) is stored but effective nowhere until it is accepted.

const (
	MembershipActive   MembershipState = "active"
	MembershipProposed MembershipState = "proposed"
)

type Merge

type Merge struct {
	From EntityID `json:"from"`
	Into EntityID `json:"into"`
}

Merge folds entity From into entity Into of the same kind.

type MergeInput

type MergeInput struct {
	Into EntityID `json:"into"`
}

MergeInput names the entity a merge folds into.

type MergeReport

type MergeReport struct {
	From             EntityID `json:"from"`
	Into             EntityID `json:"into"`
	MembershipsMoved int64    `json:"memberships_moved"`
	NamesMoved       int64    `json:"names_moved"`
	EdgesMoved       int64    `json:"edges_moved"`
	EntitiesMoved    int64    `json:"groups_moved"`
}

MergeReport summarizes one merge.

type MyEntity

type MyEntity struct {
	EntityCard
	// Owner: the viewer owns it.
	Owner bool `json:"owner"`
	// Editor: the viewer holds its kind's access.EntitySettingsManage in its
	// AuthKit scope.
	Editor bool `json:"editor"`
	// Writer: the viewer holds its kind's access.EntityContentWrite there.
	Writer bool `json:"writer"`
	// Moderator: the viewer holds its kind's access.EntityCommentsModerate
	// there, or owns it.
	Moderator bool `json:"moderator"`
	// Member: the viewer holds its key.
	Member bool `json:"member"`
}

MyEntity is an entity of the viewer's, with how it is theirs.

type MyEntityPage

type MyEntityPage struct {
	Entities   []MyEntity `json:"entities"`
	NextCursor string     `json:"next_cursor,omitempty"`
}

MyEntityPage is one page of the viewer's entities.

type Name

type Name struct {
	Language string   `json:"language"`
	Kind     NameKind `json:"kind"`
	Name     string   `json:"name"`
}

Name is one localized canonical name or alias.

type NameKind

type NameKind string

NameKind distinguishes the one canonical name per language from aliases.

const (
	NameCanonical NameKind = "name"
	NameAlias     NameKind = "alias"
)

type NewEntity

type NewEntity struct {
	Kind string `json:"kind"`
	Name string `json:"name"`
	// Language of a created entity's canonical name; the catalog's first by default.
	Language string `json:"language,omitempty"`
}

NewEntity is a name a content write links, creating the entity when no active entity of its kind carries that name or alias in any language.

type Options

type Options struct {
	Pool   *pgxpool.Pool
	Schema string
	Tenant string
	// Kinds is the site's content model; its EntityKinds govern every write.
	Kinds kinds.Config
	// ContentChanged runs whenever the effective entities of contents may have
	// changed (their facets' entity_ids are already re-derived), so the engine
	// can rebuild what else depends on them, such as search documents: in the
	// writing transaction, or for a large reach in the refresh job's batch
	// transactions. Optional.
	ContentChanged func(ctx context.Context, q Querier, contentIDs []string) error
	// NamesChanged runs in the writing transaction after the names or slugs
	// of entities changed, so the engine can requeue the documents of content
	// that shows them, such as search documents keyed by entity names. Their
	// facets are unchanged; it must not lock content. Optional.
	NamesChanged func(ctx context.Context, q Querier, entityIDs []string) error
	// DocumentsChanged runs in the writing transaction after a write queued
	// search documents, so the engine can sync them at once. Optional.
	DocumentsChanged func(ctx context.Context, q Querier) error
	// References preserves host-owned links when an entity is merged or made
	// unavailable. Nil means no host-owned references are registered.
	References References
	// Covers fills covers' and avatars' renditions on reads. Optional.
	Covers CoverResolver
	// Sales fills sellable entities' offers and the viewer's standing on
	// reads: offers.Service. Optional.
	Sales Sales
	// Scopes provisions and retires the AuthKit scopes of auth_scope kinds,
	// and lists a person's. Optional: without it auth_scope is whatever staff
	// set.
	Scopes Scopes
	// Folders hides a deleted entity's media and shows it again on restore,
	// in the writing transaction, and purges a user-owned entity's once it
	// stayed deleted for DeletedRetention: media/versions.Service.EntityFolders.
	// Optional.
	Folders Folders
	// DeletedRetention is how long a deleted user-owned entity keeps its media
	// for a restore; zero is DefaultDeletedRetention.
	DeletedRetention time.Duration
	// Owned deletes the content a deleted user-owned entity owns
	// (owner_entity) and restores it with the entity: the catalog. Optional.
	Owned OwnedContent
	// Queue is the River queue of entity syncs; contentkit_groups when empty.
	Queue string
	// Logger receives the HTTP API's failures. nil -> slog.Default().
	Logger *slog.Logger
}

Options configures one tenant's store.

type Order

type Order struct {
	// EntityID names the entity; routes take it from the path.
	EntityID EntityID    `json:"-"`
	Members  []MemberRef `json:"members"`
}

Order sets the member order of one ordered entity.

type OwnedContent

type OwnedContent interface {
	// DeleteOwned deletes the live content of the deleted ones among entities,
	// recording each entity as its content's cause; bought content stays
	// readable to its buyers.
	DeleteOwned(ctx context.Context, entities []string) error
	// RestoreOwned restores the content the active ones among entities
	// deleted when they were, unless it was purged since; content deleted
	// for another reason stays deleted.
	RestoreOwned(ctx context.Context, entities []string) error
}

OwnedContent follows user-owned entities' deletion and restore with the content they own (owner_entity): the catalog. A nil entities is every entity.

type Querier

type Querier interface {
	Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
}

Querier is a pgx pool, connection or transaction.

type Ref

type Ref struct {
	Kind string
	ID   EntityID
}

Ref names one entity with its kind.

type References

type References interface {
	// CheckUnavailable rejects deletion or public-to-internal reclassification
	// while host links still require the entities. Return ErrConflict to refuse.
	CheckUnavailable(ctx context.Context, q Querier, tenant string, refs []Ref) error
	// Merge moves From's host links to Into, collapsing duplicate pairs.
	Merge(ctx context.Context, q Querier, tenant string, from, into Ref) error
}

References maintains a host's links to catalog entities. Calls run with the affected entities locked, in the same transaction as the catalog mutation. Implementations must use q, not another connection, for all database work; an error aborts the mutation. Tenant is the store's catalog key. Reference-aware mutations require READ COMMITTED transactions so checks see host links committed while waiting for an entity's lock. Callers of Tx methods must roll back their transaction on error.

type Rendition

type Rendition struct {
	URL string `json:"url"`
	W   int    `json:"w"`
	H   int    `json:"h"`
}

Rendition is one public image of a cover, as media.PublicRendition.

type Sales

type Sales interface {
	EntityAccess(ctx context.Context, viewer access.Actor, entities []string) ([]offers.EntityAccess, error)
	Joined(ctx context.Context, viewer access.Actor) ([]string, error)
	Current(ctx context.Context, a auth.Authenticator, actor access.Actor, targets []offers.Target) ([]*offers.Offer, error)
	Resync(ctx context.Context, tx pgx.Tx, targets []offers.Target) error
}

Sales answers sellable entities' sales, every entity of a page in one call, the entities a viewer holds the key of and, for their offer editors, entities' current offers, and re-syncs the products of renamed entities in the writing transaction: offers.Service.

type Scope

type Scope struct {
	ID    string
	Kind  string
	Owner string
}

Scope is one entity's AuthKit scope: ContentKit keys it by the entity's id.

type Scopes

type Scopes interface {
	// Provision makes each scope a live AuthKit group of its kind's persona,
	// seeded with Owner as its owner when new; a scope retired before is
	// replaced by a new entity under the same id.
	Provision(ctx context.Context, scopes []Scope) error
	// Retire stops each scope resolving and granting.
	Retire(ctx context.Context, ids []string) error
	// Holding answers, per live scope of the kinds' personas user belongs
	// to, the kind's access.EntityPermissions user holds there.
	Holding(ctx context.Context, user string, kinds []string) (map[string][]string, error)
}

Scopes keeps the AuthKit groups (scopes) of auth_scope entity kinds: adapters/authkit over an AuthKit client. Provision and Retire are idempotent.

type Sort

type Sort string

Sort orders an entity listing; every order is made total by id.

const (
	// SortName orders by sort key, else display name. The default.
	SortName Sort = "name"
	// SortCount orders by content count, largest first.
	SortCount Sort = "count"
	// SortNewest orders by creation, newest first.
	SortNewest Sort = "newest"
)

type State

type State string

State of an entity. A merged entity points at its survivor (MergedInto); deleted entities keep their memberships but are effective nowhere.

const (
	StateActive  State = "active"
	StateMerged  State = "merged"
	StateDeleted State = "deleted"
)

type Store

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

Store is one tenant's entities.

func New

func New(opts Options) (*Store, error)

New validates the options and returns the store.

func (*Store) AddEdges

func (s *Store) AddEdges(ctx context.Context, edges []Edge) error

AddEdges adds edges between active entities of the tenant; an endpoint that is missing or not active is ErrNotFound.

func (*Store) AddMemberships

func (s *Store) AddMemberships(ctx context.Context, ms []Membership) error

AddMemberships adds memberships, or updates the position, window and state of existing ones (same entity, content, version and role).

func (*Store) AddMembershipsTx

func (s *Store) AddMembershipsTx(ctx context.Context, tx pgx.Tx, in []Membership) error

AddMembershipsTx adds associations in a caller-owned transaction, preserving every other shared and version-specific association. Authorize the mutation before opening tx; the same hierarchy-lock ordering as SetContentEntitiesTx applies.

func (*Store) AddNames

func (s *Store) AddNames(ctx context.Context, edits []EntityNames) error

AddNames upserts names and aliases; a new canonical name demotes the previous one of its language to an alias.

func (*Store) As

func (s *Store) As(a auth.Authenticator, actor access.Actor) *Store

As returns the store acting as actor, judged by a. Its reads show actor's standing with sellable entities. Its writes: staff holding access.EntitiesWrite in a's root scope (and the engine's own client) write anything; anyone else writes only user-owned entities, under the creator rules of CreateEntities and UpdateEntities. Without As a store reads as an anonymous viewer and writes as the host, unchecked.

func (*Store) BuildDocuments

func (s *Store) BuildDocuments(ctx context.Context, kind, language string, refs []contentref.ContentRef) ([]search.KeywordDocument, error)

BuildDocuments builds entity documents: one per active entity and language, titled by that language's canonical name (falling back to English, then any language), with the language's aliases and every other name as aliases and overflow as keywords. Missing, merged and deleted entities yield no document. Internal classifications are never search documents.

func (*Store) Cards

func (s *Store) Cards(ctx context.Context, ids []EntityID, o CardOptions) ([]EntityCard, error)

Cards returns the active entities among ids as cards, resolved as List resolves them, in no particular order.

func (*Store) ContentEditorEntities

func (s *Store) ContentEditorEntities(ctx context.Context, a auth.Authenticator, actor access.Actor) ([]EntityID, error)

ContentEditorEntities lists only this actor's owned or delegated content-writing entities. The scope directory is narrowed to this app and credential ceilings by heldScopes, as it is for Mine. It never enumerates catalog content.

func (*Store) CreateEntities

func (s *Store) CreateEntities(ctx context.Context, inputs []EntityInput) ([]Entity, error)

CreateEntities creates entities with their names and text in one transaction. A taken id or slug is ErrConflict; a kind, scope or parent the kinds config does not allow is ErrInvalid. Written as a person (As), each is of a user-owned kind and the person's own, within the kind's MaxPerOwner (ErrLimit), linked only to entities the person edits; anything else is ErrForbidden. An auth_scope kind's entity gets its AuthKit scope (Scopes) after commit, else from the scope sync job.

func (*Store) CreateEntitiesTx

func (s *Store) CreateEntitiesTx(ctx context.Context, tx pgx.Tx, in []EntityInput) ([]Entity, error)

CreateEntitiesTx is CreateEntities in a caller-owned transaction. A store bound with As retains that actor and all its ordinary authorization checks. Scope provisioning is left to the existing transactional sync job after commit.

func (*Store) DeleteEntities

func (s *Store) DeleteEntities(ctx context.Context, ids []EntityID) ([]Entity, error)

DeleteEntities marks entities deleted: they keep their memberships and code but are effective nowhere, listed nowhere and lose their documents.

func (*Store) Detail

func (s *Store) Detail(ctx context.Context, id EntityID, language string) (EntityDetail, error)

Detail returns an active entity with its names, text, parent, scope, children, edges, counts and sale, resolved for language as List does. A merged entity answers with its survivor; a deleted one is ErrNotFound.

func (*Store) DocumentKinds

func (s *Store) DocumentKinds() []string

DocumentKinds are the entity kinds: the store is a worker.Source.

func (*Store) EffectiveEntities

func (s *Store) EffectiveEntities(ctx context.Context, q Querier, versionIDs []string) (map[string][]string, error)

EffectiveEntities returns, per version, the sorted ids of the active entities it belongs to through its content's memberships or its own (windows applied), plus each one's active ancestors via parent_id. It runs on q, the caller's transaction, so memberships written in it count. Unknown versions are absent; a version in no entity maps to an empty list.

func (*Store) Entities

func (s *Store) Entities(ctx context.Context, ids []EntityID) ([]Entity, error)

Entities is a trusted raw lookup, including internal classifications and any state, in input order. Reader-facing callers use Cards or Detail instead.

func (*Store) Handler

func (s *Store) Handler(a auth.Authenticator) (http.Handler, error)

Handler serves the entities API (routes): listings and entity pages, each with the viewer's standing with sellable entities; creator writes of user-owned entities (As); and staff writes for holders of access.EntitiesWrite in a's root scope. Errors are JSON {"error", "code"}: 400 for ErrInvalid, 403 for ErrForbidden, 404 for ErrNotFound, 409 for ErrConflict and ErrLimit (limit_reached), 503 when auth or billing is down and a sanitized 500 otherwise; the cause always reaches Options.Logger.

func (*Store) ImportDeletes

func (s *Store) ImportDeletes(ctx context.Context, q pgx.Tx, ids []EntityID) error

ImportDeletes deletes the active entities among ids in q, as DeleteEntities does; deleted and merged ones stay as they are.

func (*Store) ImportEntities

func (s *Store) ImportEntities(ctx context.Context, q pgx.Tx, in []ImportEntity, provision bool) error

ImportEntities writes entities in q: new ones are inserted, stored ones updated where the import changes them. Codes, search documents and the facets of the content a moved or deleted entity reaches follow. A merged entity is ErrConflict; a taken slug too. An active entity of an auth_scope kind keeps the AuthKit scope it names or has; one with neither is provisioned, by the scope sync job, only with provision (else ErrInvalid).

func (*Store) ImportMemberships

func (s *Store) ImportMemberships(ctx context.Context, q pgx.Tx, sets []ContentEntities) error

ImportMemberships replaces, in q, each content's memberships in the entity kinds its set names (SetContentEntities); a set equal to what is stored writes nothing.

func (*Store) List

func (s *Store) List(ctx context.Context, opts ListOptions) (EntityPage, error)

List pages active entities with their display name, code, path, cover, avatar, count and, for a sellable kind, its sale to the store's viewer (As).

func (*Store) ListDocuments

func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)

ListDocuments pages the active entities of kind in id order.

func (*Store) LockHierarchyShared

func (s *Store) LockHierarchyShared(ctx context.Context, q Querier) error

LockHierarchyShared holds off hierarchy changes for the transaction's rest while letting other membership writers through. A membership writer takes it before any content or entity lock, so a hierarchy change either sees its membership or is seen by its facets.

func (*Store) LockNames

func (s *Store) LockNames(ctx context.Context, q Querier) error

LockNames serializes name resolution in this catalog, so concurrent writers that find no match never mint the same entity twice.

func (*Store) LockReferencesSQLTx

func (s *Store) LockReferencesSQLTx(ctx context.Context, tx *sql.Tx, refs []Ref) error

LockReferencesSQLTx is LockReferencesTx for a database/sql transaction using the pgx stdlib driver. Pass the underlying *sql.Tx when using an ORM. It never starts or ends a transaction; the caller must roll back on error.

func (*Store) LockReferencesTx

func (s *Store) LockReferencesTx(ctx context.Context, tx pgx.Tx, refs []Ref) error

LockReferencesTx validates active, non-internal entities of the exact requested kinds and locks them until tx ends. A host must call this before writing its own links, in that same transaction, to serialize with merges and deletion. Pass the complete selection together so locks are acquired in ID order. This is a storage contract, not authorization: callers authorize the host operation before starting tx. It does not follow merged IDs or create entities. tx must use READ COMMITTED isolation; roll it back on error.

func (*Store) ManagementDetail

func (s *Store) ManagementDetail(ctx context.Context, id EntityID, language string) (EntityDetail, error)

ManagementDetail includes internal classifications and requires staff on an As-bound store. Public Detail remains public even for an administrator.

func (*Store) MatchExactNameSQLTx

func (s *Store) MatchExactNameSQLTx(ctx context.Context, tx *sql.Tx, kind, scopeID, language, name string) ([]string, error)

MatchExactNameSQLTx finds up to two distinct active, non-internal entities of kind within scope whose canonical name or alias equals name literally. Case, accents, whitespace and punctuation are significant. Empty language searches all languages. Zero matches means unresolved; two means ambiguous.

This is a lookup, not a reservation of the name or authorization. It neither creates entities nor follows merged IDs. Before writing host links, pass the complete resolved selection to LockReferencesSQLTx in this same READ COMMITTED transaction. Keep the original text for unresolved or ambiguous names.

func (*Store) MatchName

func (s *Store) MatchName(ctx context.Context, q Querier, kind, scopeID, language, name string) ([]string, error)

MatchName returns at most two active entities of kind within scope whose canonical name or alias matches name, ignoring case, accents and word separators; in language only when it is set.

func (*Store) Members

func (s *Store) Members(ctx context.Context, id EntityID, opts MemberOptions) (MemberPage, error)

Members pages an entity's memberships in member order: by position (an ordered entity), then content, version (content first) and role. ErrNotFound for an entity the tenant lacks.

func (*Store) Memberships

func (s *Store) Memberships(ctx context.Context, tenant, contentKind, entityKind string, contentIDs []string) (map[string][]EntityID, error)

Memberships is popularity.Catalog: per content id, the ids of the active entities of groupKind its versions are effective in; contents in none are absent.

func (*Store) MembershipsOf

func (s *Store) MembershipsOf(ctx context.Context, contentIDs []string) (map[string][]Membership, error)

MembershipsOf returns every membership of each content and its versions, by content id, ordered by entity and version (content first).

func (*Store) Merge

func (s *Store) Merge(ctx context.Context, merges []Merge) ([]MergeReport, error)

Merge folds each From into its Into in one transaction: memberships move (duplicates collapse, an accepted membership wins over a proposed one), names become aliases of Into, edges, children and scoped entities re-point, From becomes merged and its code redirects to Into's. Entities of a user-owned kind and entities ever sold are never merged (ErrConflict).

func (*Store) Mine

func (s *Store) Mine(ctx context.Context, opts ListOptions) (MyEntityPage, error)

Mine pages the active entities the store's viewer (As) owns, edits or writes content of through its AuthKit scope (Options.Scopes) or holds the key of (Options.Sales), filtered, ordered and paged as List.

func (*Store) PrepareStaffWrite

func (s *Store) PrepareStaffWrite(ctx context.Context) (bool, error)

PrepareStaffWrite resolves this As-bound store's staff permission before a caller holds a database transaction. The result is cached only on this actor-bound store, so subsequent transactional writes need no pool query. False is not an error: callers that only resolve existing taxonomy need not create any entities.

func (*Store) PurgeFolders

func (s *Store) PurgeFolders(ctx context.Context) (int, error)

PurgeFolders deletes the media folders of user-owned entities deleted for longer than the retention (Options.DeletedRetention), once: a restore after it finds them empty. It returns how many it purged.

func (*Store) Reconcile

func (s *Store) Reconcile(ctx context.Context) error

Reconcile syncs every entity what follows from its state does not match yet: those whose job was lost, or whose sync keeps failing. It purges the media of entities deleted past the retention too (PurgeFolders).

func (*Store) RemoveEdges

func (s *Store) RemoveEdges(ctx context.Context, edges []Edge) error

RemoveEdges deletes edges; unknown edges are ignored.

func (*Store) RemoveMemberships

func (s *Store) RemoveMemberships(ctx context.Context, ms []Membership) error

RemoveMemberships deletes memberships matched by entity, content, version and role; unknown ones are ignored.

func (*Store) RemoveNames

func (s *Store) RemoveNames(ctx context.Context, edits []EntityNames) error

RemoveNames deletes the given names, matched by language and normalized form; unknown names are ignored.

func (*Store) Reorder

func (s *Store) Reorder(ctx context.Context, orders []Order) error

Reorder sets the member order of ordered entities: the listed members take positions 1..n in list order (every role of a member moves together), the others follow in their current order. A listed member the entity lacks is ErrNotFound.

func (*Store) RiverJobs

func (s *Store) RiverJobs() riverhelpers.Contribution

RiverJobs contributes the entity sync worker, its queue and the periodic reconcile to the engine's helpers/river composition. It composes once; until bound, writes leave their syncs to the reconcile.

func (*Store) SetContentEntities

func (s *Store) SetContentEntities(ctx context.Context, sets []ContentEntities) error

SetContentEntities replaces, per content, its content- and version-level memberships in the entity kinds the set names; memberships in other kinds are untouched. Positions omitted for existing memberships are kept.

func (*Store) SetContentEntitiesTx

func (s *Store) SetContentEntitiesTx(ctx context.Context, tx pgx.Tx, in []ContentEntities) error

SetContentEntitiesTx is SetContentEntities in a caller-owned transaction. Like SetContentEntities, its caller must first authorize editing those contents. It takes LockHierarchyShared; a tx that already holds entity or content row locks must take that first, or it can deadlock with a hierarchy change.

func (*Store) SetNames

func (s *Store) SetNames(ctx context.Context, edits []EntityNames) error

SetNames replaces every name and alias of each entity.

func (*Store) Sync

func (s *Store) Sync(ctx context.Context, ids []EntityID) error

Sync brings what follows from each entity's state in line with it: its AuthKit scope (SyncScopes) and the content a user-owned entity owns, deleted with it and restored with it (Options.Owned). Repeating it changes nothing.

func (*Store) SyncScopes

func (s *Store) SyncScopes(ctx context.Context, ids []EntityID) error

SyncScopes brings AuthKit in line with each entity: an active entity of an auth_scope kind without a scope gets one, keyed by its id and owned by its owner; an entity no longer active has its scope retired and cleared. It holds the entities' row locks over the AuthKit calls, so a concurrent delete or restore waits and is synced after; repeating it changes nothing.

func (*Store) Tenant

func (s *Store) Tenant() string

Tenant returns the tenant this store is scoped to.

func (*Store) UpdateEntities

func (s *Store) UpdateEntities(ctx context.Context, updates []EntityUpdate) ([]Entity, error)

UpdateEntities applies each update in one transaction. A merged entity is never updated (ErrConflict). Deleting or restoring an entity, or moving it to another parent, re-derives the facets of the content it reaches, retires or provisions its AuthKit scope and hides or shows its media; deleting a user-owned entity deletes the content it owns, restoring it restores that content and, within the retention, its media (Options.Owned, Options.Folders). Renaming a sellable entity renames its offer's product. Written as a person (As), each entity is a user-owned one the person owns or holds access.EntitySettingsManage in, its slug, links, owner and scope stay, and a restore stays within the kind's MaxPerOwner (ErrLimit); anything else is ErrForbidden.

type Text

type Text struct {
	Language    string `json:"language"`
	Description string `json:"description,omitempty"`
	SortKey     string `json:"sort_key,omitempty"`
}

Text is an entity's description and sort key in one language.

type Written

type Written struct {
	Count int `json:"count"`
}

Written counts the items a write named.

Jump to

Keyboard shortcuts

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