Documentation
¶
Overview ¶
Package catalog is ContentKit's own content: content items (a gallery, a movie, a creator or blog post), their per-language text, creators, versions with their text and segments, the state machine (draft, published, scheduled, deleted), the listing projection (content_facets), the one visibility rule every read applies, the events outbox and the catalog totals. Kinds are configuration (package kinds); nothing here names a host's nouns.
Index ¶
- Constants
- Variables
- func ImageRef(name string) string
- func RefreshCountsJob() river.JobArgs
- type AuthError
- type Content
- type ContentInput
- type ContentPatch
- type Cover
- type Creator
- type CreatorsInput
- type Decision
- type Event
- type GroupSummary
- type Groups
- type Images
- type ImportContent
- type ImportVersion
- type InlineImage
- type LanguageTotal
- type ManageQuery
- type Media
- type Moderation
- type Offers
- type Options
- type OrderInput
- type Publish
- type PublishInput
- type Querier
- type Reader
- type Sanitizer
- type Segment
- type SegmentsInput
- type Standing
- type Store
- func (s *Store) BindMedia(m Media) error
- func (s *Store) BindModeration(m Moderation)
- func (s *Store) BindStanding(st Standing)
- func (s *Store) BuildDocuments(ctx context.Context, kind, language string, refs []contentref.ContentRef) ([]search.KeywordDocument, error)
- func (s *Store) Close()
- func (s *Store) ContentChanged(ctx context.Context, q groups.Querier, contentIDs []string) error
- func (s *Store) Contents(ctx context.Context, v *Viewer, ids []string) ([]*Content, error)
- func (s *Store) Create(ctx context.Context, actor access.Actor, in []ContentInput) ([]string, error)
- func (s *Store) Delete(ctx context.Context, actor access.Actor, ids []string) error
- func (s *Store) DeleteOwned(ctx context.Context, groups []string) error
- func (s *Store) DeleteVersions(ctx context.Context, actor access.Actor, keys []VersionKey) error
- func (s *Store) DeliverEvents(ctx context.Context) (int, error)
- func (s *Store) DocumentKinds() []string
- func (s *Store) ExpireDrafts(ctx context.Context) (int, error)
- func (s *Store) Handler() http.Handler
- func (s *Store) Held(ctx context.Context, after time.Time, afterID string, limit int) ([]content.HeldItem, error)
- func (s *Store) Import(ctx context.Context, tx pgx.Tx, in []ImportContent) error
- func (s *Store) ImportDeletes(ctx context.Context, tx pgx.Tx, contents, versions []string) error
- func (s *Store) Kinds() kinds.Config
- func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)
- func (s *Store) Manage(ctx context.Context, actor access.Actor, q ManageQuery) ([]string, error)
- func (s *Store) OrderVersions(ctx context.Context, actor access.Actor, contentID string, ids []string) error
- func (s *Store) Publish(ctx context.Context, actor access.Actor, in []Publish) error
- func (s *Store) PublishDue(ctx context.Context) (int, error)
- func (s *Store) PurgeDeleted(ctx context.Context) (int, error)
- func (s *Store) PutText(ctx context.Context, actor access.Actor, in []TextPut) error
- func (s *Store) PutVersions(ctx context.Context, actor access.Actor, in []VersionPut) ([]string, error)
- func (s *Store) Ref(kind, id string) contentref.ContentRef
- func (s *Store) RefreshCounts(ctx context.Context) (int, error)
- func (s *Store) RefreshFacets(ctx context.Context, q Querier, contentIDs []string) error
- func (s *Store) RefreshTx(ctx context.Context, tx pgx.Tx, versionIDs []string) error
- func (s *Store) ReleaseFree(ctx context.Context) (int, error)
- func (s *Store) ResolveHeld(ctx context.Context, id string, d content.ReviewDecision) error
- func (s *Store) Resolver() resolve.Resolver
- func (s *Store) Restore(ctx context.Context, actor access.Actor, ids []string) error
- func (s *Store) RestoreOwned(ctx context.Context, groups []string) error
- func (s *Store) RiverJobs() riverhelpers.Contribution
- func (s *Store) Subscribe(sub Subscriber)
- func (s *Store) Tenant() string
- func (s *Store) Totals(ctx context.Context, q TotalsQuery) (Totals, error)
- func (s *Store) Unpublish(ctx context.Context, actor access.Actor, ids []string) error
- func (s *Store) Update(ctx context.Context, actor access.Actor, in []Update) error
- func (s *Store) Viewer(ctx context.Context, o ViewerOptions) (*Viewer, error)
- func (s *Store) Visibility(r *http.Request, links []contenturl.Link) ([]contenturl.Visibility, error)
- type Subscriber
- type Text
- type TextPut
- type Total
- type Totals
- type TotalsClient
- type TotalsQuery
- type Update
- type Version
- type VersionFork
- type VersionInput
- type VersionKey
- type VersionMedia
- type VersionPut
- type VersionText
- type Viewer
- func (v *Viewer) Actor() access.Actor
- func (v *Viewer) Editors(ctx context.Context, groups []string) map[string]bool
- func (v *Viewer) Indexable(alias string) (string, pgx.NamedArgs)
- func (v *Viewer) Listed(alias string) (string, pgx.NamedArgs)
- func (v *Viewer) Site() kinds.Site
- func (v *Viewer) Staff() bool
- type ViewerOptions
- type ViewerState
Constants ¶
const ( EventContentCreated = "content.created" EventContentUpdated = "content.updated" EventContentScheduled = "content.scheduled" EventContentPublished = "content.published" EventContentUnpublished = "content.unpublished" EventContentDeleted = "content.deleted" EventContentRestored = "content.restored" EventContentPurged = "content.purged" EventVersionCreated = "version.created" EventVersionUpdated = "version.updated" EventVersionPublished = "version.published" EventVersionDeleted = "version.deleted" )
Event types written to the outbox.
const ( StateDraft = "draft" StatePublished = "published" StateDeleted = "deleted" ListingListed = "listed" ListingMembers = "members" ListingUnlisted = "unlisted" )
Content states and listings.
const DefaultDeletedRetention = 30 * 24 * time.Hour
DefaultDeletedRetention is how long deleted, never-sold content keeps its media and can be restored before PurgeDeleted erases it.
const HeldKind = "content"
HeldKind is the review queue's kind for held content (content.Runtime's /moderation/held?kind=content).
const ImageScheme = "contentkit:"
ImageScheme is the URL scheme of an inline image reference in a body.
const Queue = "contentkit_catalog"
Queue is the River queue of the catalog's jobs.
Variables ¶
var ( ErrInvalid = errors.New("catalog: invalid request") ErrNotFound = errors.New("catalog: not found") ErrForbidden = errors.New("catalog: forbidden") ErrConflict = errors.New("catalog: conflict") )
Errors; match with errors.Is.
Functions ¶
func RefreshCountsJob ¶
RefreshCountsJob is RefreshCounts' job, for Queue: an import queues it.
Types ¶
type AuthError ¶
type AuthError struct{ Err error }
AuthError is a permission check the auth could not answer: 401 when the credential ended, else 503. It never grants.
type Content ¶
type Content struct {
ID string `json:"id"`
Kind string `json:"kind"`
// Code and Path are its page: [/{lang}]/{route}/{code}/{slug}.
Code string `json:"code"`
Path string `json:"path"`
// Language is the language of the text below.
Language string `json:"language"`
Title string `json:"title"`
Slug string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
// Body shows each inline image with a current public file as its URL;
// only editors and viewers Decision allows get it. Description is the
// teaser everyone reads.
Body string `json:"body,omitempty"`
// Decision is the viewer's access to the item: allowed when they open any
// of its versions, else its first version's paywall.
Decision Decision `json:"decision"`
// Images maps each resolved image name of Body and the cover to its URL.
Images map[string]string `json:"images,omitempty"`
Cover *Cover `json:"cover,omitempty"`
State string `json:"state"`
Listing string `json:"listing"`
// Comments is open, closed or hidden.
Comments string `json:"comments"`
PublishAt *time.Time `json:"publish_at,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
Live bool `json:"live"`
Access access.Rule `json:"access"`
Restrictions []string `json:"restrictions"`
Attributes map[string]any `json:"attributes"`
OwnerGroupID string `json:"owner_group_id,omitempty"`
// Owner is the owner group's card (a channel's name, path, avatar and
// cover) while it is active.
Owner *groups.GroupCard `json:"owner,omitempty"`
Creators []Creator `json:"creators"`
Groups []GroupSummary `json:"groups"`
Versions []Version `json:"versions"`
// Views counts its distinct viewers of all time plus its imported legacy
// view count, as cards' counts.views do.
Views int64 `json:"views"`
// Viewer is the signed-in reader's standing with the item, to resume
// it; each version carries its own.
Viewer *ViewerState `json:"viewer,omitempty"`
// Editor: the viewer may edit it; the fields below are theirs.
Editor bool `json:"editor"`
Text map[string]Text `json:"text,omitempty"`
Moderation string `json:"moderation,omitempty"`
ModerationReason string `json:"moderation_reason,omitempty"`
EverSold bool `json:"ever_sold,omitempty"`
// Offer is the item's current offer, for its offer editors.
Offer *offers.Offer `json:"offer,omitempty"`
Revision int64 `json:"revision"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
Content is a content item as a viewer sees it: its text in the viewer's language (falling back), its live versions (every version for editors), groups and attributes.
type ContentInput ¶
type ContentInput struct {
Kind string `json:"kind"`
// Text is the text per language.
Text map[string]Text `json:"text,omitempty"`
Listing string `json:"listing,omitempty"` // default listed
// Comments is open (the default), closed (existing ones show, new ones
// are refused) or hidden.
Comments string `json:"comments,omitempty"`
// Access defaults to public.
Access *access.Rule `json:"access,omitempty"`
Restrictions []string `json:"restrictions,omitempty"`
Attributes map[string]any `json:"attributes,omitempty"`
// OwnerGroupID is the home group (a channel); its editors edit the item.
// It never changes.
OwnerGroupID string `json:"owner_group_id,omitempty"`
// Creators nil credits the creating person as its author.
Creators *[]Creator `json:"creators,omitempty"`
// Versions: at least one, except for a kind with hidden versions, which
// gets its one version automatically.
Versions []VersionInput `json:"versions,omitempty"`
}
ContentInput creates a content item, a draft until published.
type ContentPatch ¶
type ContentPatch struct {
Listing *string `json:"listing,omitempty"`
Comments *string `json:"comments,omitempty"`
Access *access.Rule `json:"access,omitempty"`
Restrictions *[]string `json:"restrictions,omitempty"`
Attributes map[string]any `json:"attributes,omitempty"` // replaces all
Creators *[]Creator `json:"creators,omitempty"`
// Cover is an inline image name of the content's folder; "" clears.
Cover *string `json:"cover,omitempty"`
// Revision, when set, refuses the edit (409) unless it is current.
Revision *int64 `json:"revision,omitempty"`
}
ContentPatch edits a content item; nil fields stay.
type Creator ¶
type Creator struct {
UserID string `json:"user_id"`
Role string `json:"role,omitempty"`
Position int `json:"position"`
}
Creator is a person credited on a content item; creators edit it.
type CreatorsInput ¶
type CreatorsInput struct {
Creators []Creator `json:"creators"`
}
CreatorsInput replaces the creators.
type Decision ¶
type Decision struct {
Allowed bool `json:"allowed"`
// Unknown: entitlements could not be read; denied, nothing offered.
Unknown bool `json:"unknown,omitempty"`
// Sell is the keys a paywall offers; Requires the key a buyer holds first.
Sell []string `json:"sell,omitempty"`
Requires string `json:"requires,omitempty"`
// Preview is what a locked viewer still reads; FreeAfter when it opens to all.
Preview *access.Preview `json:"preview,omitempty"`
FreeAfter *time.Time `json:"free_after,omitempty"`
// Offers are the purchasable offers of Sell's keys; tiers are the host's
// to label.
Offers []offers.Sale `json:"offers,omitempty"`
}
Decision is a viewer's access verdict on a version.
type Event ¶
type Event struct {
ID int64 `json:"id"`
Type string `json:"type"`
Kind string `json:"kind"`
ContentID string `json:"content_id"`
VersionID string `json:"version_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
Event is one outbox entry: a change to a content item or one of its versions, delivered at least once in order of ID.
type GroupSummary ¶
type GroupSummary struct {
ID string `json:"id"`
Kind string `json:"kind"`
Slug string `json:"slug"`
Name string `json:"name"`
Code string `json:"code,omitempty"`
Path string `json:"path,omitempty"`
}
GroupSummary names one group a content item belongs to; Code and Path are its page, as content's are, when its kind has one.
type Groups ¶
type Groups interface {
EffectiveGroups(ctx context.Context, q groups.Querier, versionIDs []string) (map[string][]string, error)
Cards(ctx context.Context, ids []groups.GroupID, o groups.CardOptions) ([]groups.GroupCard, error)
}
Groups answers each version's effective group ids (its content's memberships, its own and what they imply), read in q's transaction, and owner groups' cards: *groups.Store.
type Images ¶
type Images interface {
Images(ctx context.Context, qs ...media.ImageQuery) ([][]media.PublicImage, error)
}
Images answers image queries from current publications in one read.
type ImportContent ¶
type ImportContent struct {
ID string
Kind string
State string
PublishAt *time.Time
PublishedAt *time.Time
DeletedAt *time.Time
Listing string
// Comments is open (when empty), closed or hidden.
Comments string
// ViewBaseline is the legacy all-time view count, added to the viewers
// the signal plane counts.
ViewBaseline int64
Access *access.Rule
Restrictions []string
Attributes map[string]any
OwnerGroupID string
Cover string
Creators []Creator
Text map[string]Text
Versions []ImportVersion
Code string
CreatedAt *time.Time
UpdatedAt *time.Time
}
ImportContent is a content item as the bulk import writes it: its id, state and times kept, trusted (not screened). Nil Creators, Text and a version's Text and Segments keep what is stored; given ones replace it.
type ImportVersion ¶
type ImportVersion struct {
ID string
Languages []string
Traits []string
Label string
State string
PublishAt *time.Time
PublishedAt *time.Time
DeletedAt *time.Time
Access *access.Rule
Attributes map[string]any
Ordinal *int
Text map[string]VersionText
Segments []Segment
CreatedAt *time.Time
UpdatedAt *time.Time
}
ImportVersion is one version of an ImportContent, positioned as listed.
type InlineImage ¶
InlineImage is an inline image of a body: Ref goes where its URL would ("contentkit:i-{uuid}"); URL shows it to the editor now (null until it has a file to show).
type LanguageTotal ¶
LanguageTotal sizes one kind in one language, as Total does.
type ManageQuery ¶
type ManageQuery struct {
Kind string
// State is draft, scheduled, published (live) or deleted; "" is every
// state but deleted.
State string
OwnerGroup string
// Text matches title, description or body, ignoring case.
Text string
Limit, Offset int
}
ManageQuery selects content items to manage.
type Media ¶
type Media struct {
Images Images // *media.Manifests
// Reader gives editors an unpublished item's image previews.
Reader Reader
Versions VersionMedia // *versions.Service
}
Media connects content items to ContentKit media, built over the catalog's Resolver and RefreshTx (media/versions): bind it with BindMedia before serving. A content item's media item is (kind, content), holding its cover; its body images are its body item's ({kind}.body, content), shown only to viewers who open it; each version's is (kind, content, version).
type Moderation ¶
type Moderation interface {
Screen(ctx context.Context, in content.ModerationInput) (content.Screening, error)
}
Moderation screens text before it publishes: content.Runtime.
type Offers ¶
type Offers interface {
Resync(ctx context.Context, tx pgx.Tx, targets []offers.Target) error
Sales(ctx context.Context, keys []string) (map[string]offers.Sale, error)
Current(ctx context.Context, a auth.Authenticator, actor access.Actor, targets []offers.Target) ([]*offers.Offer, error)
}
Offers is *offers.Service: Resync re-syncs the targets' products in the writing transaction; Sales and Current fill reads' paywalls and editors' offers.
type Options ¶
type Options struct {
Pool *pgxpool.Pool
Schema string
Tenant string
// Kinds is the site's content model. Required.
Kinds kinds.Config
// Auth gates the routes and answers staff and owner-group permissions.
Auth auth.Authenticator
// Gate answers viewers' entitlements; nil builds one without billing
// (only free rules and claimed tiers open).
Gate *access.Gate
// Offers renames the products of content whose title changes and answers
// paywalls' offers; nil when nothing is sold.
Offers Offers
// Groups answers effective group ids for content_facets; nil: none. Wire
// its Options.ContentChanged to Store.ContentChanged.
Groups Groups
// Moderation screens text of content that is or goes live; nil approves
// unless contentkit.NewRuntime binds its content runtime (BindModeration).
Moderation Moderation
// Body sanitizes text bodies; nil strips tags.
Body Sanitizer
// DeletedRetention: see DefaultDeletedRetention.
DeletedRetention time.Duration
// 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
Now func() time.Time
Logger *slog.Logger
}
Options configures a Store.
type PublishInput ¶
PublishInput publishes now, or at PublishAt when it is in the future.
type Querier ¶
type Querier interface {
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
}
Querier is a pool or a transaction.
type Reader ¶
type Reader interface {
Read(ctx context.Context, ref contentref.ContentRef, actor access.Actor, o media.ReadOptions) (*media.ReadResult, error)
// NamedURLs signs an opened item's named images that have no public files.
NamedURLs(ctx context.Context, ref contentref.ContentRef, actor access.Actor, names []string) (map[string]string, error)
}
Reader reads an item as the media read API does.
type Sanitizer ¶
type Sanitizer = content.ContentProcessor
Sanitizer cleans rich text on write: content.ContentProcessor.
type Segment ¶
type Segment struct {
Position int `json:"position"`
Title string `json:"title,omitempty"`
Start int `json:"start"`
End int `json:"end"`
ReleaseAt *time.Time `json:"release_at,omitempty"`
// Released is computed on reads.
Released bool `json:"released"`
}
Segment is an ordered, individually released range of a version (a chapter): items [Start, End). A segment is released once it and every earlier one are past ReleaseAt.
type SegmentsInput ¶
type SegmentsInput struct {
Segments []Segment `json:"segments"`
}
SegmentsInput replaces a version's segments.
type Standing ¶ added in v0.85.0
type Standing interface {
Standing(ctx context.Context, actor access.Actor, refs []contentref.ContentRef) ([]ViewerState, error)
}
Standing reads a signed-in person's standing with content items and versions, per ref in order: *discovery.Service.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is one tenant's catalog.
func (*Store) BindModeration ¶
func (s *Store) BindModeration(m Moderation)
BindModeration screens through m when Options.Moderation is nil, so one moderator serves comments and catalog text; call it before serving.
func (*Store) BindStanding ¶ added in v0.85.0
BindStanding fills detail reads' viewer state from st; call it once, before serving.
func (*Store) BuildDocuments ¶
func (s *Store) BuildDocuments(ctx context.Context, kind, language string, refs []contentref.ContentRef) ([]search.KeywordDocument, error)
BuildDocuments returns the current document of each published item of refs with text in language; the others get none, which deletes theirs.
func (*Store) ContentChanged ¶
ContentChanged is groups.Options.ContentChanged: after the groups module re-derived the items' group_ids in q's transaction, it queues their search documents (group names are keywords) and records the change.
func (*Store) Contents ¶
Contents reads content items as v sees them (spec §4): an item the viewer may not see, or that does not exist, is nil. Text is in the viewer's language, else the site's first language, else the first it has; an item whose edit awaits review shows its last approved text to everyone but its editors. Editors also get every language's text, every version and the moderation state.
func (*Store) Create ¶
func (s *Store) Create(ctx context.Context, actor access.Actor, in []ContentInput) ([]string, error)
Create creates content items as drafts and returns their ids in order. actor needs root:content:write, or to edit each item's owner group.
func (*Store) Delete ¶
Delete deletes content items. A deleted item stays readable to holders of its own key; one never sold is erased, media included, after the retention (PurgeDeleted), until when Restore brings it back.
func (*Store) DeleteOwned ¶
DeleteOwned deletes the live content of the deleted user-owned groups among groups (every one with nil), recording each group as its content's cause (groups.OwnedContent): holders of a deleted item's key still read it, and one never sold is erased after the retention.
func (*Store) DeleteVersions ¶
DeleteVersions deletes versions; an item keeps at least one.
func (*Store) DeliverEvents ¶
DeliverEvents hands undelivered events to the subscribers in batches, in order, and marks each batch delivered once every subscriber took it; it stops at the first failure, which it returns. Delivered events older than a week are pruned. It returns how many events were delivered.
func (*Store) DocumentKinds ¶
DocumentKinds are the content kinds the catalog documents.
func (*Store) ExpireDrafts ¶
ExpireDrafts erases the drafts of kinds with a DraftTTL that never went live and were created longer than it ago: their media folders and their rows. It returns how many it erased.
func (*Store) Handler ¶
Handler serves the catalog routes behind their gates over Options.Auth, each request in an access.WithMemo context (one billing read). Its paths are under the module's prefix (httpapi.Contents, /v1): mount it with that prefix stripped.
func (*Store) Held ¶
func (s *Store) Held(ctx context.Context, after time.Time, afterID string, limit int) ([]content.HeldItem, error)
Held pages held content items oldest first, for content's review queue (content.HeldSource under HeldKind): the text shown is the site's first language's, else any.
func (*Store) Import ¶
Import writes content items in tx: new ones are inserted, stored ones updated where the import changes them. Then what depends on them follows, as after every write: codes, facets, search documents, events, media exposure and publications, and offers' product names. A version of another content item or a kind change is ErrInvalid.
func (*Store) ImportDeletes ¶ added in v0.85.0
ImportDeletes deletes content items and versions in tx as Delete and DeleteVersions do: holders of a deleted item's key still read it, and one never sold is erased after the retention. Deleted ones stay as they are; a kind's one hidden version goes only with its item.
func (*Store) ListDocuments ¶
func (s *Store) ListDocuments(ctx context.Context, kind, language, cursor string, limit int) ([]contentref.ContentRef, string, bool, error)
ListDocuments pages the published items of kind with text in language, in id order, for backfill.
func (*Store) Manage ¶
Manage lists the ids of items actor manages, newest first: every item for root:content:write holders; else what they created, or with OwnerGroup the group's items when they edit it.
func (*Store) OrderVersions ¶
func (s *Store) OrderVersions(ctx context.Context, actor access.Actor, contentID string, ids []string) error
OrderVersions sets the order of a content item's versions: ids lists every version not deleted, first first; deleted ones follow.
func (*Store) Publish ¶
Publish publishes content items now, or schedules those with a future At. A published item is screened (all its text); a hold leaves it unpublished until a reviewer approves it. Rescheduling a scheduled item moves its time; a live item is left as it is.
func (*Store) PublishDue ¶
PublishDue promotes scheduled content items and versions whose time came: it stamps published_at and fires the side effects of going live (facets, search documents, media exposure, events). Visibility itself never waits for it: a scheduled item is live from its time. It returns how many items and versions it promoted.
func (*Store) PurgeDeleted ¶
PurgeDeleted erases content items deleted longer than the retention that were never sold: their media folders and their rows. Sold ones stay for their holders. It returns how many it erased.
func (*Store) PutText ¶
PutText sets or removes text per language. Text of a published item is screened first: a rejection writes nothing (content.RejectedError), a hold keeps the item's last approved text public until a reviewer decides.
func (*Store) PutVersions ¶
func (s *Store) PutVersions(ctx context.Context, actor access.Actor, in []VersionPut) ([]string, error)
PutVersions creates (VersionID "") or edits versions and returns their ids in order.
func (*Store) Ref ¶
func (s *Store) Ref(kind, id string) contentref.ContentRef
Ref is a content item's reference.
func (*Store) RefreshCounts ¶
RefreshCounts copies each content item's interaction counts into its facets: likes, dislikes, favorites and rating (the Wilson lower bound of likes over likes and dislikes). It returns how many facet rows changed.
func (*Store) RefreshFacets ¶
RefreshFacets rewrites the content_facets rows of every version of the content items, and queues their search documents, in q's transaction. live_from is when a version went (or goes) live, kept through deletion so holders of a deleted item still read what was live; deleted marks either deletion. Every write to a content item, its versions, memberships, access, listing, restrictions, traits or attributes calls it before committing; the groups module calls it after membership changes.
func (*Store) RefreshTx ¶
RefreshTx is versions.Facets: it refreshes the facets of the versions' content items after their media facts changed.
func (*Store) ReleaseFree ¶
ReleaseFree runs what follows a free_after time passing: the content items whose versions turned free since the last run get their facets and documents re-derived and their media, body images included, exposed to everyone, as a write would. It returns how many items it released.
func (*Store) ResolveHeld ¶
ResolveHeld applies a reviewer's decision to a held content item at the revision the queue listed: approval publishes it (or its edit), rejection keeps it from going live (an edit of a live item leaves its last approved text public).
func (*Store) Resolver ¶
Resolver is the resolve.Resolver of content items for media (media/versions) and interactions: a content reference (kind, content) or one of its versions is visible by the rule for direct reads (live, deleted and held by its own keys, or edited by the viewer), accessible when the viewer opens it (a content reference: any of its visible versions), with staff, creators and owner-group editors as Editor and the first creator as Owner. A reference it omits is unseen.
func (*Store) Restore ¶
Restore brings deleted content items back as they were: published (or scheduled) when they had been, else drafts.
func (*Store) RestoreOwned ¶
RestoreOwned restores the content the active groups among groups (every one with nil) deleted when they were deleted (groups.OwnedContent), as Restore does; content deleted for another reason stays deleted, and content purged since is gone.
func (*Store) RiverJobs ¶
func (s *Store) RiverJobs() riverhelpers.Contribution
RiverJobs contributes the catalog's periodic jobs to the engine's River client: scheduled publication and free_after releases (every 30s), event delivery (5s), interaction counts (5m), purging deleted content (1h) and expiring drafts (10m).
func (*Store) Subscribe ¶
func (s *Store) Subscribe(sub Subscriber)
Subscribe registers an in-process subscriber; DeliverEvents calls every subscriber with every batch.
func (*Store) Totals ¶
Totals counts the listed content of each kind by who opens it, cached per site and kind: a count reloads in the background after ten minutes, and the last good one serves while reloads fail, for up to a day.
func (*Store) Viewer ¶
Viewer resolves o once per request: the site, staff rights, entitlements (one billing read, fail-closed when unavailable), the site's required groups and the viewer's content filters (config defaults merged with their own).
func (*Store) Visibility ¶
func (s *Store) Visibility(r *http.Request, links []contenturl.Link) ([]contenturl.Visibility, error)
Visibility is contenturl.RouterOptions.Visibility for content kinds: a page shows while its item is live for an anonymous visitor of the request's site, is Gone once a live item was deleted (or erased), and is Hidden otherwise. Other kinds are Visible.
type Subscriber ¶
Subscriber receives a batch of events in order. An error redelivers the batch later, so a subscriber must be idempotent.
type Text ¶
type Text struct {
Title string `json:"title"`
Slug string `json:"slug,omitempty"`
Description string `json:"description,omitempty"`
Body string `json:"body,omitempty"`
SortKey string `json:"sort_key,omitempty"`
Aliases []string `json:"aliases,omitempty"`
}
Text is a content item's text in one language. Body is rich text whose inline images are "contentkit:i-{uuid}" references (ImageRef).
type Total ¶
type Total struct {
Public int64 `json:"public"`
Tiers map[string]int64 `json:"tiers"`
Languages map[string]LanguageTotal `json:"languages"`
}
Total sizes one kind. Public counts what an anonymous visitor's listing shows and opens; Tiers[t] every eligible item a holder of t opens, members-listed ones included. An item counts once in the kind's total and once per language any of its eligible versions serves, never per version. The site's scope and the default content filters apply.
type TotalsClient ¶
type TotalsClient struct {
// contains filtered or unexported fields
}
TotalsClient reads another deployment's catalog totals (GET /v1/catalog/totals), such as a sibling site's, server to server, cached per kind like Store.Totals.
func NewTotalsClient ¶
func NewTotalsClient(baseURL string, hc *http.Client) (*TotalsClient, error)
NewTotalsClient reads the deployment whose ContentKit is mounted at baseURL, e.g. "https://hentai0.com/api/v1/contentkit". A nil hc has a ten-second timeout.
func (*TotalsClient) Totals ¶
func (c *TotalsClient) Totals(ctx context.Context, q TotalsQuery) (Totals, error)
Totals reads the remote's totals as Store.Totals answers them, in the site the remote derives from its host: q.Kinds is required and q.Site empty.
type TotalsQuery ¶
type TotalsQuery struct {
// Kinds are content kinds; empty means all of them.
Kinds []string
// Languages are the languages Total.Languages reports; empty means every
// language with a counted item. Kind totals ignore it.
Languages []string
// Site is the site whose scope applies (its name); empty is the first
// site. The HTTP route takes it from the request's host.
Site string
}
TotalsQuery selects the kinds and languages to total.
type Version ¶
type Version struct {
ID string `json:"id"`
Position int `json:"position"`
Languages []string `json:"languages"`
Traits []string `json:"traits"`
Label string `json:"label,omitempty"`
// Title and Description are the override in the detail's language.
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
// State is the editors' to see.
State string `json:"state,omitempty"`
PublishAt *time.Time `json:"publish_at,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
// Live: published, past PublishAt, its content live.
Live bool `json:"live"`
// Access is the version's own rule; nil inherits the content's.
Access *access.Rule `json:"access,omitempty"`
Attributes map[string]any `json:"attributes"`
Pages *int `json:"pages,omitempty"`
DurationMS *int64 `json:"duration_ms,omitempty"`
Width *int `json:"width,omitempty"`
Height *int `json:"height,omitempty"`
HasAudio bool `json:"has_audio"`
Ordinal *int `json:"ordinal,omitempty"`
Segments []Segment `json:"segments"`
Decision Decision `json:"decision"`
// Text is every language's override, for editors.
Text map[string]VersionText `json:"text,omitempty"`
// Offer is the version's current offer, for its offer editors.
Offer *offers.Offer `json:"offer,omitempty"`
// Viewer is the signed-in reader's standing with the version.
Viewer *ViewerState `json:"viewer,omitempty"`
}
Version is what a viewer opens.
type VersionFork ¶
type VersionFork struct {
From string `json:"from"`
Playback bool `json:"playback,omitempty"`
Segments bool `json:"segments,omitempty"`
}
VersionFork copies a sibling version's media into a new version: its uploads and reader metadata, with Playback its audio, with Segments its segments.
type VersionInput ¶
type VersionInput struct {
Languages []string `json:"languages,omitempty"`
Traits *[]string `json:"traits,omitempty"`
Label *string `json:"label,omitempty"`
// State is draft or published (the default for a new version): a
// published version is live once its content is and PublishAt passed.
State string `json:"state,omitempty"`
PublishAt *time.Time `json:"publish_at,omitempty"`
// Access overrides the content's rule; ClearAccess inherits it again.
Access *access.Rule `json:"access,omitempty"`
ClearAccess bool `json:"clear_access,omitempty"`
Attributes map[string]any `json:"attributes,omitempty"` // replaces all
Text map[string]VersionText `json:"text,omitempty"` // replaces all
Segments *[]Segment `json:"segments,omitempty"` // replaces all
Ordinal *int `json:"ordinal,omitempty"`
// Fork fills a new version's media from a sibling (creation only).
Fork *VersionFork `json:"fork,omitempty"`
}
VersionInput creates or edits a version; nil fields stay (or default).
type VersionKey ¶
type VersionKey struct{ ContentID, VersionID string }
VersionKey names one version.
type VersionMedia ¶
type VersionMedia interface {
// ExposeTx follows a visibility change.
ExposeTx(ctx context.Context, tx pgx.Tx, targets []versions.Target) error
// RepublishTx follows a change of a version's effective rule or segments.
RepublishTx(ctx context.Context, tx pgx.Tx, versions []versions.Target) error
// DeleteTx deletes media before the rows go.
DeleteTx(ctx context.Context, tx pgx.Tx, targets []versions.Target) error
// ForkTx fills new versions' media from siblings.
ForkTx(ctx context.Context, tx pgx.Tx, forks []versions.Fork) error
}
VersionMedia is the media calls catalog writes make in their transaction: a target is a content item (its item and every version's) or one version.
type VersionPut ¶
type VersionPut struct {
ContentID string
VersionID string
VersionInput
}
VersionPut creates (VersionID "") or edits a version.
type VersionText ¶
type VersionText struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
}
VersionText overrides a version's title and description in one language.
type Viewer ¶
type Viewer struct {
// contains filtered or unexported fields
}
Viewer is the one visibility rule (ContentKit #117 spec §4) for one request: live content and versions, the listing rule, the site scope, the viewer's content filters and entitlements, and the staff, creators and owner-group editors who see everything. Listed renders it as SQL over content_facets for lists and searches; Store.Contents and Resolver decide items with it.
func (*Viewer) Editors ¶
Editors reports, per owner group, whether the viewer edits its content and so opens it: staff edit every group's; anyone else is asked once per group per request, and a failed check reads as not an editor.
func (*Viewer) Indexable ¶ added in v0.85.0
Indexable is a boolean SQL predicate over the content_facets row aliased alias: the versions a site lists to anyone, as its sitemaps name them: live, not deleted, listed and in the viewer's site scope. Access, the language and a reader's content filters do not apply. Args are pgx named args prefixed ci_.
func (*Viewer) Listed ¶
Listed is a boolean SQL predicate over the content_facets row aliased alias: the versions the viewer's listing shows. A list shows one card per content item among its rows. In Hide mode, and for members-listed rows, rows a truncated keyspace may unlock are admitted as candidates: decide them with Contents (or access.Gate.Settle) before showing. Args are pgx named args prefixed cv_ and access_.
type ViewerOptions ¶
type ViewerOptions struct {
Actor access.Actor
// Host is the request's host; it picks the site (kinds.Config.SiteFor).
Host string
// Language, when set, lists only content with a live version serving it.
Language string
// Mode defaults to access.Lock.
Mode access.Mode
// EditorGroups are owner groups (channels) whose editors see their whole
// content in lists; the viewer's editorship of each is checked.
EditorGroups []string
}
ViewerOptions describes one request's reader.
type ViewerState ¶ added in v0.85.0
type ViewerState struct {
Favorited bool `json:"favorited"`
// Seen, Completed, Progress, ProgressMax and Resume (the client's own
// pointer, such as a page) come from the signal plane.
Seen bool `json:"seen"`
Completed bool `json:"completed"`
Progress uint32 `json:"progress"`
ProgressMax uint32 `json:"progress_max"`
Resume string `json:"resume,omitempty"`
LastViewAt *time.Time `json:"last_view_at,omitempty"`
}
ViewerState is a signed-in reader's own standing with a content item or version: their favorite and, with a signal plane, where they are in it.