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
- Variables
- type AppBinding
- type Avatar
- type CardOptions
- type ContentEntities
- type ContentMemberships
- type Count
- type Cover
- type CoverOf
- type CoverResolver
- type Edge
- type EdgeInput
- type Entity
- type EntityCard
- type EntityDetail
- type EntityID
- type EntityInput
- type EntityNames
- type EntityPage
- type EntityUpdate
- type Folders
- type ImportEntity
- type ListOptions
- type LocalScopes
- type MemberOptions
- type MemberPage
- type MemberRef
- type Membership
- type MembershipState
- type Merge
- type MergeInput
- type MergeReport
- type MyEntity
- type MyEntityPage
- type Name
- type NameKind
- type NewEntity
- type Options
- type Order
- type OwnedContent
- type Querier
- type Ref
- type References
- type Rendition
- type Sales
- type Scope
- type Scopes
- type Sort
- type State
- type Store
- func (s *Store) AddEdges(ctx context.Context, edges []Edge) error
- func (s *Store) AddMemberships(ctx context.Context, ms []Membership) error
- func (s *Store) AddMembershipsTx(ctx context.Context, tx pgx.Tx, in []Membership) error
- func (s *Store) AddNames(ctx context.Context, edits []EntityNames) error
- func (s *Store) As(a auth.Authenticator, actor access.Actor) *Store
- func (s *Store) BuildDocuments(ctx context.Context, kind, language string, refs []contentref.ContentRef) ([]search.KeywordDocument, error)
- func (s *Store) Cards(ctx context.Context, ids []EntityID, o CardOptions) ([]EntityCard, error)
- func (s *Store) ContentEditorEntities(ctx context.Context, a auth.Authenticator, actor access.Actor) ([]EntityID, error)
- func (s *Store) CreateEntities(ctx context.Context, inputs []EntityInput) ([]Entity, error)
- func (s *Store) CreateEntitiesTx(ctx context.Context, tx pgx.Tx, in []EntityInput) ([]Entity, error)
- func (s *Store) DeleteEntities(ctx context.Context, ids []EntityID) ([]Entity, error)
- func (s *Store) Detail(ctx context.Context, id EntityID, language string) (EntityDetail, error)
- func (s *Store) DocumentKinds() []string
- func (s *Store) EffectiveEntities(ctx context.Context, q Querier, versionIDs []string) (map[string][]string, error)
- func (s *Store) Entities(ctx context.Context, ids []EntityID) ([]Entity, error)
- func (s *Store) Handler(a auth.Authenticator) (http.Handler, error)
- func (s *Store) ImportDeletes(ctx context.Context, q pgx.Tx, ids []EntityID) error
- func (s *Store) ImportEntities(ctx context.Context, q pgx.Tx, in []ImportEntity, provision bool) error
- func (s *Store) ImportMemberships(ctx context.Context, q pgx.Tx, sets []ContentEntities) error
- func (s *Store) List(ctx context.Context, opts ListOptions) (EntityPage, error)
- func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)
- func (s *Store) LockHierarchyShared(ctx context.Context, q Querier) error
- func (s *Store) LockNames(ctx context.Context, q Querier) error
- func (s *Store) LockReferencesSQLTx(ctx context.Context, tx *sql.Tx, refs []Ref) error
- func (s *Store) LockReferencesTx(ctx context.Context, tx pgx.Tx, refs []Ref) error
- func (s *Store) ManagementDetail(ctx context.Context, id EntityID, language string) (EntityDetail, error)
- func (s *Store) MatchExactNameSQLTx(ctx context.Context, tx *sql.Tx, kind, scopeID, language, name string) ([]string, error)
- func (s *Store) MatchName(ctx context.Context, q Querier, kind, scopeID, language, name string) ([]string, error)
- func (s *Store) Members(ctx context.Context, id EntityID, opts MemberOptions) (MemberPage, error)
- func (s *Store) Memberships(ctx context.Context, tenant, contentKind, entityKind string, ...) (map[string][]EntityID, error)
- func (s *Store) MembershipsOf(ctx context.Context, contentIDs []string) (map[string][]Membership, error)
- func (s *Store) Merge(ctx context.Context, merges []Merge) ([]MergeReport, error)
- func (s *Store) Mine(ctx context.Context, opts ListOptions) (MyEntityPage, error)
- func (s *Store) PrepareStaffWrite(ctx context.Context) (bool, error)
- func (s *Store) PurgeFolders(ctx context.Context) (int, error)
- func (s *Store) Reconcile(ctx context.Context) error
- func (s *Store) RemoveEdges(ctx context.Context, edges []Edge) error
- func (s *Store) RemoveMemberships(ctx context.Context, ms []Membership) error
- func (s *Store) RemoveNames(ctx context.Context, edits []EntityNames) error
- func (s *Store) Reorder(ctx context.Context, orders []Order) error
- func (s *Store) RiverJobs() riverhelpers.Contribution
- func (s *Store) SetContentEntities(ctx context.Context, sets []ContentEntities) error
- func (s *Store) SetContentEntitiesTx(ctx context.Context, tx pgx.Tx, in []ContentEntities) error
- func (s *Store) SetNames(ctx context.Context, edits []EntityNames) error
- func (s *Store) Sync(ctx context.Context, ids []EntityID) error
- func (s *Store) SyncScopes(ctx context.Context, ids []EntityID) error
- func (s *Store) Tenant() string
- func (s *Store) UpdateEntities(ctx context.Context, updates []EntityUpdate) ([]Entity, error)
- type Text
- type Written
Constants ¶
const AvatarPreset = "avatar"
AvatarPreset is the media preset avatars render in (media.AvatarPreset).
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.
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.
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 ¶
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 ¶
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 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 ¶
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 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.
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 References ¶
type References interface {
// 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 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 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 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.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is one tenant's entities.
func (*Store) AddEdges ¶
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 ¶
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 ¶
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 ¶
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 ¶
DeleteEntities marks entities deleted: they keep their memberships and code but are effective nowhere, listed nowhere and lose their documents.
func (*Store) Detail ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
LockNames serializes name resolution in this catalog, so concurrent writers that find no match never mint the same entity twice.
func (*Store) LockReferencesSQLTx ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) UpdateEntities ¶
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.