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
- Variables
- type Avatar
- type CardOptions
- type ContentGroups
- type ContentMemberships
- type Count
- type Cover
- type CoverOf
- type CoverResolver
- type Edge
- type EdgeInput
- type Folders
- type Group
- type GroupCard
- type GroupDetail
- type GroupID
- type GroupInput
- type GroupNames
- type GroupPage
- type GroupUpdate
- type ImportGroup
- type ListOptions
- type MemberOptions
- type MemberPage
- type MemberRef
- type Membership
- type MembershipState
- type Merge
- type MergeInput
- type MergeReport
- type MyGroup
- type MyGroupPage
- type Name
- type NameKind
- type Options
- type Order
- type OwnedContent
- type Querier
- type Ref
- 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) AddNames(ctx context.Context, edits []GroupNames) 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 []GroupID, o CardOptions) ([]GroupCard, error)
- func (s *Store) CreateGroups(ctx context.Context, inputs []GroupInput) ([]Group, error)
- func (s *Store) DeleteGroups(ctx context.Context, ids []GroupID) ([]Group, error)
- func (s *Store) Detail(ctx context.Context, id GroupID, language string) (GroupDetail, error)
- func (s *Store) DocumentKinds() []string
- func (s *Store) EffectiveGroups(ctx context.Context, q Querier, versionIDs []string) (map[string][]string, error)
- func (s *Store) Groups(ctx context.Context, ids []GroupID) ([]Group, error)
- func (s *Store) Handler(a auth.Authenticator) (http.Handler, error)
- func (s *Store) ImportGroups(ctx context.Context, q pgx.Tx, in []ImportGroup, provision bool) error
- func (s *Store) ImportMemberships(ctx context.Context, q pgx.Tx, sets []ContentGroups) error
- func (s *Store) List(ctx context.Context, opts ListOptions) (GroupPage, error)
- func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)
- func (s *Store) Members(ctx context.Context, id GroupID, opts MemberOptions) (MemberPage, error)
- func (s *Store) Memberships(ctx context.Context, tenant, contentKind, groupKind string, ...) (map[string][]GroupID, 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) (MyGroupPage, error)
- func (s *Store) PurgeFolders(ctx context.Context) (int, error)
- func (s *Store) Reconcile(ctx context.Context) error
- func (s *Store) Recount(ctx context.Context) error
- func (s *Store) RefreshWindows(ctx context.Context, lookback time.Duration) 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 []GroupNames) error
- func (s *Store) Reorder(ctx context.Context, orders []Order) error
- func (s *Store) RiverJobs() riverhelpers.Contribution
- func (s *Store) SetContentGroups(ctx context.Context, sets []ContentGroups) error
- func (s *Store) SetNames(ctx context.Context, edits []GroupNames) error
- func (s *Store) Sync(ctx context.Context, ids []GroupID) error
- func (s *Store) SyncScopes(ctx context.Context, ids []GroupID) error
- func (s *Store) Tenant() string
- func (s *Store) UpdateGroups(ctx context.Context, updates []GroupUpdate) ([]Group, 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 group keeps its media folder, so a restore brings it back.
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 ¶
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) 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 ¶
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 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"`
}
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 ¶
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 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.
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 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)
Resync(ctx context.Context, tx pgx.Tx, targets []offers.Target) error
}
Sales answers sellable groups' sales, every group of a page in one call, and the groups a viewer holds the key of, and re-syncs the products of renamed groups 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 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 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.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is one tenant's groups.
func (*Store) AddEdges ¶
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 ¶
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 ¶
Cards returns the active groups among ids as cards, resolved as List resolves them, in no particular order.
func (*Store) CreateGroups ¶
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 ¶
DeleteGroups marks groups deleted: they keep their memberships and code but are effective nowhere, listed nowhere and lose their documents.
func (*Store) Detail ¶
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 ¶
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 ¶
Groups returns the requested groups that exist, in any state, in input order.
func (*Store) Handler ¶
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) ImportGroups ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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) UpdateGroups ¶
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.