groups

package
v0.85.1 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 35 Imported by: 1

Documentation

Overview

Package groups is how content is grouped: tags, artists, series, characters, channels, playlists and featured rails. Group kinds come from kinds.Config.GroupKinds. A group has localized names and aliases, per-language text, declared attributes, a cover, a parent, an optional scope and edges to other groups; content joins it through memberships, at content level or for one version where the kind allows. A version's effective groups (EffectiveGroups) feed content_facets.group_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 group keeps its media folder, so a restore brings it back.

View Source
const ReconcileInterval = time.Minute

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

Variables

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

Sentinel errors; callers match them with errors.Is.

Functions

This section is empty.

Types

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 a user-owned group's square picture: an image uploaded to the group'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 {
	// 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 ContentGroups

type ContentGroups struct {
	// ContentID names the content; routes take it from the path.
	ContentID string `json:"-"`
	// Kinds limits the write to these group kinds; empty means every group
	// kind the content's kind declares.
	Kinds       []string     `json:"kinds,omitempty"`
	Memberships []Membership `json:"memberships"`
}

ContentGroups is one content's memberships in the group kinds Kinds names.

type ContentMemberships

type ContentMemberships struct {
	ContentID   string       `json:"content_id"`
	Memberships []Membership `json:"memberships"`
	Groups      []GroupCard  `json:"groups"`
}

ContentMemberships is a content's memberships with the groups 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 group (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 a group's picture: an image uploaded to the group's media folder ("i-{uuid}") or a member content's cover. Auto covers are the most-favorited live member, chosen by Recount when none is selected.

type CoverOf

type CoverOf struct {
	Kind   string
	ID     GroupID
	Cover  Cover
	Preset string
}

CoverOf is one group's picture to resolve: its cover, or with Preset "avatar" its avatar (Cover.Image the selected one, if any).

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.GroupCovers.

type Edge

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

Edge is a typed relation between two groups 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       GroupID `json:"to"`
}

EdgeInput is an edge from the group a route names.

type Folders

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

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

type Group

type Group struct {
	ID   GroupID `json:"id"`
	Kind string  `json:"kind"`
	Slug string  `json:"slug"`
	// ScopeID is the group the slug is unique within (a character's series).
	ScopeID     GroupID        `json:"scope_id,omitempty"`
	ParentID    GroupID        `json:"parent_id,omitempty"`
	State       State          `json:"state"`
	MergedInto  GroupID        `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.GroupSettingsManage 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"`
}

Group is one group record.

type GroupCard

type GroupCard struct {
	ID           GroupID `json:"id"`
	Kind         string  `json:"kind"`
	Slug         string  `json:"slug"`
	ScopeID      GroupID `json:"scope_id,omitempty"`
	ParentID     GroupID `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 group's avatar.
	Avatar     *Avatar        `json:"avatar,omitempty"`
	Count      int            `json:"count"`
	Attributes map[string]any `json:"attributes"`
	// Access is a sellable group's sale: its key, active offer and the
	// viewer's standing.
	Access    *offers.GroupAccess `json:"access,omitempty"`
	CreatedAt time.Time           `json:"created_at"`
}

GroupCard is a listed group: what a directory page or a chip needs.

type GroupDetail

type GroupDetail struct {
	Group
	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       *GroupCard  `json:"parent,omitempty"`
	Scope        *GroupCard  `json:"scope,omitempty"`
	Children     []GroupCard `json:"children"`
	Edges        []Edge      `json:"edges"`
	Counts       []Count     `json:"counts"`
	// Access is a sellable group's sale: its key, active offer and the
	// viewer's standing.
	Access *offers.GroupAccess `json:"access,omitempty"`
	// Offer is a sellable group's current offer, for its offer editors.
	Offer *offers.Offer `json:"offer,omitempty"`
}

GroupDetail is one group with everything its page shows.

type GroupID

type GroupID = contentref.GroupID

GroupID identifies one group within a tenant: a UUIDv7, kept across import.

type GroupInput

type GroupInput struct {
	// ID is optional; omitted ids are generated.
	ID   GroupID `json:"id,omitempty"`
	Kind string  `json:"kind"`
	Slug string  `json:"slug"`
	// ScopeID is required for a kind scoped by another (a character's series).
	ScopeID    GroupID        `json:"scope_id,omitempty"`
	ParentID   GroupID        `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 group; 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"`
}

GroupInput creates one group.

type GroupNames

type GroupNames struct {
	ID    GroupID `json:"id"`
	Names []Name  `json:"names"`
}

GroupNames is one group's names in a names write.

type GroupPage

type GroupPage struct {
	Groups []GroupCard `json:"groups"`
	// NextCursor pages on; empty on the last page.
	NextCursor string `json:"next_cursor,omitempty"`
}

GroupPage is one page of a listing.

type GroupUpdate

type GroupUpdate struct {
	// ID names the group; routes take it from the path.
	ID      GroupID  `json:"-"`
	Slug    *string  `json:"slug,omitempty"`
	ScopeID *GroupID `json:"scope_id,omitempty"`
	// ParentID "" detaches the group from its parent.
	ParentID *GroupID `json:"parent_id,omitempty"`
	// State restores (active) or deletes a group.
	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 a user-owned group's avatar; 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"`
}

GroupUpdate changes one group's given fields; nil fields are unchanged.

type ImportGroup

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

ImportGroup is a group 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
	// Parent keeps the children of one group (sub-series of a series).
	Parent GroupID
	// Scope keeps the groups scoped by one group (characters of a series).
	Scope GroupID
	Slug  string
	// Letter keeps groups 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 in the first configured language, counts
	// over every language, unprefixed paths.
	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
	// Limit defaults to 50 and is capped at 200.
	Limit int
}

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

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 a group's memberships.

type MemberRef

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

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

type Membership

type Membership struct {
	GroupID     GroupID         `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 a group. Position orders an ordered group (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 GroupID `json:"from"`
	Into GroupID `json:"into"`
}

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

type MergeInput

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

MergeInput names the group a merge folds into.

type MergeReport

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

MergeReport summarizes one merge.

type MyGroup

type MyGroup struct {
	GroupCard
	// Owner: the viewer owns it.
	Owner bool `json:"owner"`
	// Editor: the viewer holds its kind's access.GroupSettingsManage in its
	// AuthKit scope.
	Editor bool `json:"editor"`
	// Writer: the viewer holds its kind's access.GroupContentWrite there.
	Writer bool `json:"writer"`
	// Moderator: the viewer holds its kind's access.GroupCommentsModerate
	// there, or owns it.
	Moderator bool `json:"moderator"`
	// Member: the viewer holds its key.
	Member bool `json:"member"`
}

MyGroup is a group of the viewer's, with how it is theirs.

type MyGroupPage

type MyGroupPage struct {
	Groups     []MyGroup `json:"groups"`
	NextCursor string    `json:"next_cursor,omitempty"`
}

MyGroupPage is one page of the viewer's groups.

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 Options

type Options struct {
	Pool   *pgxpool.Pool
	Schema string
	Tenant string
	// Kinds is the site's content model; its GroupKinds govern every write.
	Kinds kinds.Config
	// ContentChanged runs in the writing transaction whenever the effective
	// groups of contents may have changed (their facets' group_ids are
	// already re-derived), so the engine can rebuild what else depends on
	// them, such as search documents. Optional.
	ContentChanged func(ctx context.Context, q Querier, contentIDs []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
	// Covers fills covers' and avatars' renditions on reads. Optional.
	Covers CoverResolver
	// Sales fills sellable groups' 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 group's media and shows it again on restore,
	// in the writing transaction, and purges a user-owned group's once it
	// stayed deleted for DeletedRetention: media/versions.Service.GroupFolders.
	// Optional.
	Folders Folders
	// DeletedRetention is how long a deleted user-owned group keeps its media
	// for a restore; zero is DefaultDeletedRetention.
	DeletedRetention time.Duration
	// Owned deletes the content a deleted user-owned group owns
	// (owner_group) and restores it with the group: the catalog. Optional.
	Owned OwnedContent
	// Queue is the River queue of group 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 {
	// GroupID names the group; routes take it from the path.
	GroupID GroupID     `json:"-"`
	Members []MemberRef `json:"members"`
}

Order sets the member order of one ordered group.

type OwnedContent

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

OwnedContent follows user-owned groups' deletion and restore with the content they own (owner_group): the catalog. A nil groups is every group.

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   GroupID
}

Ref names one group with its kind.

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 {
	GroupAccess(ctx context.Context, viewer access.Actor, groups []string) ([]offers.GroupAccess, 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 groups' sales, every group of a page in one call, the groups a viewer holds the key of and, for their offer editors, groups' current offers, and re-syncs the products of renamed groups in the writing transaction: offers.Service.

type Scope

type Scope struct {
	ID    string
	Kind  string
	Owner string
}

Scope is one group's AuthKit scope: ContentKit keys it by the group'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 group 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.GroupPermissions user holds there.
	Holding(ctx context.Context, user string, kinds []string) (map[string][]string, error)
}

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

type Sort

type Sort string

Sort orders a group 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 a group. A merged group points at its survivor (MergedInto); deleted groups 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 groups.

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 groups 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 group, content, version and role).

func (*Store) AddNames

func (s *Store) AddNames(ctx context.Context, edits []GroupNames) 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 groups. Its writes: staff holding access.GroupsWrite in a's root scope (and the engine's own client) write anything; anyone else writes only user-owned groups, under the creator rules of CreateGroups and UpdateGroups. 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 group documents: one per active group 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 groups yield no document.

func (*Store) Cards

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

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

func (*Store) CreateGroups

func (s *Store) CreateGroups(ctx context.Context, inputs []GroupInput) ([]Group, error)

CreateGroups creates groups 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 groups the person edits; anything else is ErrForbidden. An auth_scope kind's group gets its AuthKit scope (Scopes) after commit, else from the scope sync job.

func (*Store) DeleteGroups

func (s *Store) DeleteGroups(ctx context.Context, ids []GroupID) ([]Group, error)

DeleteGroups marks groups 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 GroupID, language string) (GroupDetail, error)

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

func (*Store) DocumentKinds

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

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

func (*Store) EffectiveGroups

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

EffectiveGroups returns, per version, the sorted ids of the active groups 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 group maps to an empty list.

func (*Store) Groups

func (s *Store) Groups(ctx context.Context, ids []GroupID) ([]Group, error)

Groups returns the requested groups that exist, in any state, in input order.

func (*Store) Handler

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

Handler serves the groups API (routes): listings and group pages, each with the viewer's standing with sellable groups; creator writes of user-owned groups (As); and staff writes for holders of access.GroupsWrite 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 added in v0.85.0

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

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

func (*Store) ImportGroups

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

ImportGroups writes groups 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 group reaches follow. A merged group is ErrConflict; a taken slug too. An active group 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 []ContentGroups) error

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

func (*Store) List

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

List pages active groups 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 groups of kind in id order.

func (*Store) Members

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

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

func (*Store) Memberships

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

Memberships is popularity.Catalog: per content id, the ids of the active groups 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 group 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 groups re-point, From becomes merged and its code redirects to Into's. Groups of a user-owned kind and groups ever sold are never merged (ErrConflict).

func (*Store) Mine

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

Mine pages the active groups 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) PurgeFolders

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

PurgeFolders deletes the media folders of user-owned groups 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 group what follows from its state does not match yet: those whose job was lost, or whose sync keeps failing. It purges the media of groups deleted past the retention too (PurgeFolders).

func (*Store) Recount

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

Recount rebuilds content_group_counts from content_facets, whose rows are versions: per active group, content kind and language, the distinct live, listed contents it holds or owns. It also re-picks every automatic cover: the most-favorited live, listed member. It is an engine job; counts may lag writes until it runs.

func (*Store) RefreshWindows

func (s *Store) RefreshWindows(ctx context.Context, lookback time.Duration) error

RefreshWindows re-derives the facets of content whose windowed memberships opened or closed within the last lookback. It is an engine job: run it more often than lookback; overlapping runs are harmless.

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 group, content, version and role; unknown ones are ignored.

func (*Store) RemoveNames

func (s *Store) RemoveNames(ctx context.Context, edits []GroupNames) 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 groups: 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 group lacks is ErrNotFound.

func (*Store) RiverJobs

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

RiverJobs contributes the group 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) SetContentGroups

func (s *Store) SetContentGroups(ctx context.Context, sets []ContentGroups) error

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

func (*Store) SetNames

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

SetNames replaces every name and alias of each group.

func (*Store) Sync

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

Sync brings what follows from each group's state in line with it: its AuthKit scope (SyncScopes) and the content a user-owned group 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 []GroupID) error

SyncScopes brings AuthKit in line with each group: an active group of an auth_scope kind without a scope gets one, keyed by its id and owned by its owner; a group no longer active has its scope retired and cleared. It holds the groups' 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) UpdateGroups

func (s *Store) UpdateGroups(ctx context.Context, updates []GroupUpdate) ([]Group, error)

UpdateGroups applies each update in one transaction. A merged group is never updated (ErrConflict). Deleting or restoring a group, 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 group deletes the content it owns, restoring it restores that content and, within the retention, its media (Options.Owned, Options.Folders). Renaming a sellable group renames its offer's product. Written as a person (As), each group is a user-owned one the person owns or holds access.GroupSettingsManage 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 a group'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