versions

package
v0.98.2 Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Overview

Package versions binds media to content versions. A content kind's media items are its versions, {kind, content, version}; a content has none of its own. This package answers media's hooks from the content model (who uploads, who reads, the facts a version's media yields), publishes versions to viewers (segment items, previews, playback), forks and imports versions' media, copies body images between versions, and serves the reader plan, reader metadata and downloads.

Whether a viewer sees, opens or edits a content or a version is the catalog's decision over the access gate, ContentKit's own resolve.Resolver; this package adds segments' release and the rule's preview to it. The listing projection is refreshed through Facets.

Index

Constants

View Source
const (
	ArchiveOperation = "publishing_operation"
	ArchiveSHA256    = "publishing_sha256"
)

ArchiveOperation and ArchiveSHA256 bind an uploaded ZIP to a publishing operation. The import checks the placed blob's actual SHA-256 before opening it. Ordinary uploads that do not declare these fields retain their behavior.

View Source
const (
	// CodeNoDownload is a version without the file its kind's download serves.
	CodeNoDownload = "no_download"
	// CodeDownloadUnavailable is a download the host's policy refused.
	CodeDownloadUnavailable = "download_unavailable"
)

Download refusals.

View Source
const DefaultDownloadTTL = 5 * time.Minute

DefaultDownloadTTL is how long a file download's URL opens its file: long enough to start the transfer, which then runs to its end.

View Source
const Queue = "contentkit_versions"

Queue is the River queue of versions' media jobs.

Variables

View Source
var (
	ErrArchiveChanged = errors.New("versions: imported archive pages were changed")
	ErrArchiveFailed  = errors.New("versions: archive or a required rendition failed")
)
View Source
var (
	// ErrNotFound is a content or version that does not exist, or that the
	// viewer may not see.
	ErrNotFound = errors.New("versions: not found")
	// ErrNotReady is a version whose media is still processing, being
	// forked or imported, or not yet published.
	ErrNotReady = errors.New("versions: media not ready")
	// ErrNoDownload is a version without the file its kind's download
	// serves: nothing uploaded, or an upload that failed or was dropped.
	ErrNoDownload = errors.New("versions: no download")
	// ErrForbidden is what the viewer sees but may not do.
	ErrForbidden = errors.New("versions: forbidden")
	// ErrInvalid is a malformed request.
	ErrInvalid = errors.New("versions: invalid")
)

Functions

func GroupOwner

func GroupOwner(kind, id string) string

GroupOwner is the quota owner of a group's uploads: "{kind}:{id}".

func ParseGroupOwner

func ParseGroupOwner(owner string) (kind, id string, ok bool)

ParseGroupOwner is GroupOwner's kind and id; ok is false for any other owner.

Types

type Anchor

type Anchor struct {
	ID              string   `json:"id"`
	Page            string   `json:"page"`
	OffsetPercent   float64  `json:"offset_percent"`
	DurationSeconds *float64 `json:"duration_seconds,omitempty"`
}

Anchor is a scroll position within a page of the vertical reader.

type Archive

type Archive struct {
	Name string // the attachment's file name
	// contains filtered or unexported fields
}

Archive is a marked page archive ready to stream.

func (*Archive) WriteTo

func (a *Archive) WriteTo(w io.Writer) (int64, error)

WriteTo streams the archive within its request's context.

type ArchiveReadiness added in v0.93.0

type ArchiveReadiness struct {
	Pages     int
	Completed int
	Ready     bool
	// PublicReady additionally waits for exposure and public cover renditions.
	// A hidden draft prepares the source cover but has no public renditions yet.
	PublicReady bool
}

ArchiveReadiness is the progress of the exact imported archive, not merely any media that happens to occupy its version.

type BackgroundCue

type BackgroundCue struct {
	File       string  `json:"file"`
	FromPage   *string `json:"from_page,omitempty"`
	ToPage     *string `json:"to_page,omitempty"`
	FromAnchor *string `json:"from_anchor,omitempty"`
	ToAnchor   *string `json:"to_anchor,omitempty"`
	Loop       *bool   `json:"loop,omitempty"`
	Layer      *string `json:"layer,omitempty"`
}

BackgroundCue plays a track over a range of pages or anchors.

type BodyCopy added in v0.88.0

type BodyCopy struct {
	Content string   `json:"content"`
	From    string   `json:"from"`
	To      []string `json:"to"`
	Names   []string `json:"names,omitempty"`
}

BodyCopy copies version From's body images into each version of To: those Names, or all when Names is empty. A name a version holds already stays.

func (BodyCopy) Kind added in v0.88.0

func (BodyCopy) Kind() string

type CoverOf

type CoverOf struct {
	Kind      string
	ID        string
	Image     string
	ContentID string
	Version   string
	Language  string
	Preset    string
}

CoverOf names a content's or a group's cover: Kind and ID are its owner; Image an image uploaded to the owner's item ("i-{uuid}"), else ContentID a content whose cover it shows; with neither, a content owner's own. A content's cover is its Version's, else its default edition's in Language (internal/edition). With Preset media.AvatarPreset it names a group's avatar: Image, else the item's first, else the kind's default.

type DownloadDecision added in v0.88.0

type DownloadDecision struct {
	Allowed bool
	// A refusal's stable code of the host's ("release_window") and, when it
	// is a matter of time, when the viewer may download.
	Reason      string
	AvailableAt *time.Time
	// Mark are the pages (from 1) of a page download to mark; files ignore it.
	Mark []int
	// Name is the download's file name without its extension; "" keeps
	// ContentKit's. ContentKit cleans and bounds it.
	Name string
}

DownloadDecision is a policy's ruling.

type DownloadGroup added in v0.90.0

type DownloadGroup struct {
	kinds.GroupRef
	Name       string
	Attributes map[string]any
}

DownloadGroup is a group of a download's version, named in its language, else the site's default, else another, else by its slug, with its attributes.

type DownloadPolicy added in v0.88.0

type DownloadPolicy interface {
	// Decide rules on a download after the access check, before the quota.
	Decide(ctx context.Context, r DownloadRequest) (DownloadDecision, error)
	// Mark returns page's (from 1) marked bytes for actor and their
	// extension (".png"): a page Decide named.
	Mark(ctx context.Context, actor access.Actor, v Target, page int, b []byte) ([]byte, string, error)
}

DownloadPolicy is the host's download policy: whether a viewer may download a version now, and which pages of a page download it marks with its own watermark. ContentKit asks; the host decides.

type DownloadRequest added in v0.88.0

type DownloadRequest struct {
	Actor  access.Actor
	Tiers  []string // the tiers Actor holds
	Editor bool     // Actor edits the version
	Target
	Kind string
	// Language is the version's language; Title is its title, in Language
	// when it has one there (its content's id when it has none).
	Language, Title string
	// Groups are the version's groups, its own and its content's.
	Groups []DownloadGroup
	// Rule is the version's effective access rule.
	Rule access.Rule
	// Released is when the version went live; zero while it is not.
	Released time.Time
	// Pages are a page download's pages; 0 for a file.
	Pages int
}

DownloadRequest is a download Decide rules on.

func (DownloadRequest) In added in v0.90.0

In reports whether the version is in group g.

type Downloads

type Downloads struct {
	Anonymous []Quota     `json:"anonymous,omitempty" yaml:"anonymous"`
	User      []Quota     `json:"user,omitempty" yaml:"user"`
	Tiers     []TierQuota `json:"tiers,omitempty" yaml:"tiers"`
	// TTL is how long a file download's URL opens its file; 0 is DefaultDownloadTTL.
	TTL time.Duration `json:"ttl,omitempty" yaml:"ttl"`
	// Redis shares the counts across replicas; nil keeps them per process.
	Redis redis.UniversalClient `json:"-" yaml:"-"`
}

Downloads are the download quotas: an anonymous viewer's (by address), a signed-in one's, and the first of Tiers a viewer holds, which replaces User. A bucket without quotas is unlimited.

type Facets

type Facets interface {
	RefreshTx(ctx context.Context, tx pgx.Tx, versions []string) error
}

Facets refreshes the listing projection of versions whose derived media facts changed, in the writing transaction.

type FileDownload added in v0.88.0

type FileDownload struct {
	URL       string    `json:"url"`
	Name      string    `json:"name"`
	Type      string    `json:"type"`
	Size      int64     `json:"size,omitempty"`
	ExpiresAt time.Time `json:"expires_at"`
}

FileDownload is a granted download. URL serves it as an attachment named Name until ExpiresAt (a transfer begun runs to its end): the media host's URL of the file (the original, a rendition, a prepared zip), or, for a zip the kind's watermark marks for its viewer, this module's archive route, relative to the download route ("archive?..."). Size is 0 when the zip is marked as it streams.

type ForegroundCue

type ForegroundCue struct {
	File   string  `json:"file"`
	Page   *string `json:"page,omitempty"`
	Anchor *string `json:"anchor,omitempty"`
}

ForegroundCue plays a whole track at a page or anchor.

type Fork

type Fork struct {
	Version  Target
	From     string
	Playback bool
	Segments bool
}

Fork fills Version, a version still without media, from From, a sibling of the same content: its uploads (audio only with Playback; never page archives or previews), copied with their outputs, its reader metadata (audio cues only with Playback) and, with Segments, its segments.

type ForkBody

type ForkBody struct {
	From     string `json:"from"`
	Playback bool   `json:"playback,omitempty"`
	Segments bool   `json:"segments,omitempty"`
}

ForkBody forks a version from a sibling (Fork).

type Media

type Media struct {
	Manifests *media.Manifests
	Uploads   *media.Uploads
	Jobs      *media.Jobs
	Reader    *media.Reader // signs file downloads; nil serves none
}

Media is the media runtime built over Service.Hooks.

type Options

type Options struct {
	Tenant string // the content tables' tenant
	Schema string // ContentKit's schema
	Pool   *pgxpool.Pool
	Kinds  kinds.Config       // the site's content and group kinds
	Auth   auth.Authenticator // the host's: staff and group scope checks
	// AccountExists verifies avatar targets in this tenant's account directory.
	// Scoped engines install it; nil preserves the legacy host contract.
	AccountExists func(context.Context, string) (bool, error)
	// Resolver decides versions ({kind, content, version}) for a viewer:
	// Visible (live, or edited by them), Accessible (opened by them) and
	// Editor.
	Resolver resolve.Resolver
	// Gate answers the tiers a viewer holds for download quotas; optional.
	Gate      *access.Gate
	Facets    Facets
	Others    Others         // nil refuses every other kind
	Policy    DownloadPolicy // the host's download policy; nil allows every download and marks none
	Downloads Downloads
	Logger    *slog.Logger
	Now       func() time.Time
}

Options configure a Service.

type Others

type Others interface {
	resolve.Resolver
	media.UploadAuthorizer
}

Others answer the media kinds of neither contents nor groups: posts' and polls' images.

type Pending

type Pending struct {
	Status string `json:"status"` // "pending"
}

Pending answers a version whose media is still being prepared: retry.

type PlanPage

type PlanPage struct {
	Name  string            `json:"name"`
	W     int               `json:"w"`
	H     int               `json:"h"`
	Files map[string]string `json:"files"`
}

PlanPage is one page of an open segment: its name, reading size and the path of each rendition in the segment's item.

type PlanPlayback

type PlanPlayback struct {
	Item   media.RefBody `json:"item"`
	Tracks []PlanTrack   `json:"tracks"`
}

PlanPlayback is the version's audio item and its tracks.

type PlanPreview

type PlanPreview struct {
	Index int    `json:"index"`
	Name  string `json:"name"`
	URL   string `json:"url"`
	W     int    `json:"w"`
	H     int    `json:"h"`
}

PlanPreview is a public preview of a leading page.

type PlanSegment

type PlanSegment struct {
	Position  *int           `json:"position,omitempty"`
	Title     string         `json:"title,omitempty"`
	Start     int            `json:"start"`
	End       int            `json:"end"`
	Released  bool           `json:"released"`
	ReleaseAt *time.Time     `json:"release_at,omitempty"`
	Locked    bool           `json:"locked"`
	Item      *media.RefBody `json:"item,omitempty"` // read it at GET /media/{kind}/{id}
	Pages     []PlanPage     `json:"pages,omitempty"`
}

PlanSegment is one span of pages: a segment's (Position set) or pages outside every segment.

type PlanTrack

type PlanTrack struct {
	Name     string  `json:"name"`
	Path     string  `json:"path"`
	Duration float64 `json:"duration,omitempty"`
}

type Quota

type Quota struct {
	Limit int           `json:"limit" yaml:"limit"`
	Per   time.Duration `json:"per" yaml:"per"`
}

Quota admits at most Limit downloads in any Per.

type QuotaError

type QuotaError struct {
	Bucket     string
	RetryAfter time.Duration
}

QuotaError is a download refused by its viewer's quota.

func (*QuotaError) Error

func (e *QuotaError) Error() string

type ReaderDefaults

type ReaderDefaults struct {
	AutoAdvanceSeconds float64 `json:"auto_advance_seconds,omitempty"`
}

type ReaderMeta

type ReaderMeta struct {
	SchemaVersion int             `json:"schema_version"` // 1
	Defaults      *ReaderDefaults `json:"defaults,omitempty"`
	Anchors       []Anchor        `json:"anchors,omitempty"`
	Background    []BackgroundCue `json:"background,omitempty"`
	Foreground    []ForegroundCue `json:"foreground,omitempty"`
}

ReaderMeta is a version's reader metadata, kept in its item's manifest: scroll anchors on its pages and audio cues on pages or anchors. Pages and audio files are named by their uploads' {name}.

type ReaderPlan

type ReaderPlan struct {
	Content     string        `json:"content_id"`
	Version     string        `json:"version_id"`
	Publication string        `json:"publication_id"`
	Pages       *int          `json:"pages,omitempty"`
	Segments    []PlanSegment `json:"segments"`
	Previews    []PlanPreview `json:"previews"`
	Playback    *PlanPlayback `json:"playback,omitempty"`
	Reader      *ReaderMeta   `json:"reader,omitempty"`
}

ReaderPlan is how a viewer reads a version: its current publication's segments, each with its release state and, when the viewer opens it, the published item to read through the media read API; the public previews of a locked version; the playback item; and the reader metadata naming what the viewer can read. It signs nothing. A viewer locked out of a version whose kind's locked view hides its length gets neither its page count (absent) nor its locked segments.

type Service

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

Service is media for content versions.

func New

func New(o Options) (*Service, error)

New validates o. Build the media runtime with Hooks, then Bind it.

func (*Service) Archive added in v0.88.0

func (s *Service) Archive(ctx context.Context, t Target, viewer, mark, name, ticket string) (*Archive, error)

Archive opens the marked archive named name a Download granted viewer for t, by its ticket: the pages zipped as they stream, the policy marking those mark names. A ticket that is expired or not for viewer, t, mark and name is ErrNotFound.

func (*Service) ArchiveStatus added in v0.93.0

func (s *Service) ArchiveStatus(ctx context.Context, t Target, operation, digest string) (ArchiveReadiness, error)

ArchiveStatus reads exact archive progress and required publication/cover readiness. It performs no mutation and does not assume that a past completed job means that the current pages still match the submitted archive.

func (*Service) Bind

func (s *Service) Bind(m Media) error

Bind attaches the media runtime built over Hooks.

func (*Service) CopyBodiesTx added in v0.88.0

func (s *Service) CopyBodiesTx(ctx context.Context, tx pgx.Tx, copies []BodyCopy) error

CopyBodiesTx queues copies in tx; each runs as a durable job. Copies of a kind without body images do nothing.

func (*Service) Covers

func (s *Service) Covers(ctx context.Context, covers []CoverOf) ([][]media.PublicRendition, error)

Covers resolves covers to their public renditions, per cover in order, in two queries for the page. A content's cover is the image its version's cover selects, else the version's first cover image, else its kind's default.

func (*Service) DeleteTx

func (s *Service) DeleteTx(ctx context.Context, tx pgx.Tx, targets []Target) error

DeleteTx queues the deletion of targets' media, their publications' included, before their rows go.

func (*Service) Download

func (s *Service) Download(ctx context.Context, actor access.Actor, t Target) (*FileDownload, error)

Download grants t's download to actor, what its kind's download serves (kinds.Kind.Download), as the host's policy allows, and charges it to actor's quota: the version open (or actor edits it) with every segment released. ErrNotFound when the kind serves none, an UnavailableError when the policy refuses, ErrNoDownload when the version has nothing to serve, ErrNotReady while it is placed or produced, a QuotaError past the quota.

func (*Service) ExposeTx

func (s *Service) ExposeTx(ctx context.Context, tx pgx.Tx, targets []Target) error

ExposeTx brings targets' media to their visibility once it changed: published, unpublished, deleted, or due.

func (*Service) ForkTx

func (s *Service) ForkTx(ctx context.Context, tx pgx.Tx, forks []Fork) error

ForkTx queues forks in tx; each runs as a durable job, and its destination takes no other writes until it finishes.

func (*Service) GroupCovers

func (s *Service) GroupCovers() groups.CoverResolver

GroupCovers is Covers for the groups API's cards and pages (groups.Options.Covers).

func (*Service) GroupFolders

func (s *Service) GroupFolders() groups.Folders

GroupFolders hides, shows and deletes groups' media folders for the groups API (groups.Options.Folders).

func (*Service) Handler

func (s *Service) Handler(a auth.Authenticator, log *slog.Logger) (http.Handler, error)

Handler serves the versions module's routes, each behind its tier's gate.

func (*Service) Hooks

func (s *Service) Hooks() media.Hooks

Hooks are media's hooks over the content model; set them as media.Config.Hooks (with the host's PurgePublic) before building the registry.

func (*Service) Ladder added in v0.88.0

func (s *Service) Ladder(kind string) string

Ladder is the folder of the kind's first HLS ladder (a read's hls entry), "" when it streams none or one per upload.

func (*Service) Plan

func (s *Service) Plan(ctx context.Context, actor access.Actor, t Target) (*ReaderPlan, error)

Plan is t's reader plan for actor.

func (*Service) RepublishTx

func (s *Service) RepublishTx(ctx context.Context, tx pgx.Tx, versions []Target) error

RepublishTx queues versions' publication after their segments changed.

func (*Service) RequestFork

func (s *Service) RequestFork(ctx context.Context, actor access.Actor, f Fork) error

RequestFork queues f for an editor of its destination who sees its source.

func (*Service) RiverJobs

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

RiverJobs contributes the versions' workers, queue and recovery pass to the host's River composition, beside media's own.

func (*Service) SetReaderMeta

func (s *Service) SetReaderMeta(ctx context.Context, actor access.Actor, t Target, rm ReaderMeta) error

SetReaderMeta stores v's reader metadata for an editor: every page, anchor and file it names must exist.

func (*Service) WithArchiveReady added in v0.93.0

func (s *Service) WithArchiveReady(ctx context.Context, t Target, operation, digest string, fn func(ArchiveReadiness) error) error

WithArchiveReady serializes validation and fn with the same version source lock as publication builds. It does not hold a database transaction while calling fn. fn must itself compare the catalog revisions before publishing.

type Target

type Target struct {
	Content string `json:"content"`
	Version string `json:"version,omitempty"`
}

Target names a content (Version "") or one of its versions.

type TierQuota

type TierQuota struct {
	Tier   string  `json:"tier" yaml:"tier"`
	Quotas []Quota `json:"quotas" yaml:"quotas"`
}

TierQuota is the bucket of a tier's holders.

type UnavailableError added in v0.88.0

type UnavailableError struct {
	Reason      string
	AvailableAt *time.Time
}

UnavailableError is a download the policy refused.

func (*UnavailableError) Error added in v0.88.0

func (e *UnavailableError) Error() string

Jump to

Keyboard shortcuts

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