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
- Variables
- func GroupOwner(kind, id string) string
- func ParseGroupOwner(owner string) (kind, id string, ok bool)
- type Anchor
- type Archive
- type BackgroundCue
- type BodyCopy
- type CoverOf
- type DownloadDecision
- type DownloadGroup
- type DownloadPolicy
- type DownloadRequest
- type Downloads
- type Facets
- type FileDownload
- type ForegroundCue
- type Fork
- type ForkBody
- type Media
- type Options
- type Others
- type Pending
- type PlanPage
- type PlanPlayback
- type PlanPreview
- type PlanSegment
- type PlanTrack
- type Quota
- type QuotaError
- type ReaderDefaults
- type ReaderMeta
- type ReaderPlan
- type Service
- func (s *Service) Archive(ctx context.Context, t Target, viewer, mark, name, ticket string) (*Archive, error)
- func (s *Service) Bind(m Media) error
- func (s *Service) CopyBodiesTx(ctx context.Context, tx pgx.Tx, copies []BodyCopy) error
- func (s *Service) Covers(ctx context.Context, covers []CoverOf) ([][]media.PublicRendition, error)
- func (s *Service) DeleteTx(ctx context.Context, tx pgx.Tx, targets []Target) error
- func (s *Service) Download(ctx context.Context, actor access.Actor, t Target) (*FileDownload, error)
- func (s *Service) ExposeTx(ctx context.Context, tx pgx.Tx, targets []Target) error
- func (s *Service) ForkTx(ctx context.Context, tx pgx.Tx, forks []Fork) error
- func (s *Service) GroupCovers() groups.CoverResolver
- func (s *Service) GroupFolders() groups.Folders
- func (s *Service) Handler(a auth.Authenticator, log *slog.Logger) (http.Handler, error)
- func (s *Service) Hooks() media.Hooks
- func (s *Service) Ladder(kind string) string
- func (s *Service) Plan(ctx context.Context, actor access.Actor, t Target) (*ReaderPlan, error)
- func (s *Service) RepublishTx(ctx context.Context, tx pgx.Tx, versions []Target) error
- func (s *Service) RequestFork(ctx context.Context, actor access.Actor, f Fork) error
- func (s *Service) RiverJobs() riverhelpers.Contribution
- func (s *Service) SetReaderMeta(ctx context.Context, actor access.Actor, t Target, rm ReaderMeta) error
- type Target
- type TierQuota
- type UnavailableError
Constants ¶
const ( // CodeNoDownload is a version without the file its kind's download serves. CodeNoDownload = "no_download" CodeDownloadUnavailable = "download_unavailable" )
Download refusals.
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.
const Queue = "contentkit_versions"
Queue is the River queue of versions' media jobs.
Variables ¶
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 ¶
GroupOwner is the quota owner of a group's uploads: "{kind}:{id}".
func ParseGroupOwner ¶
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.
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.90.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.
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.90.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
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.90.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.90.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.
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 ¶
Facets refreshes the listing projection of versions whose derived media facts changed, in the writing transaction.
type FileDownload ¶ added in v0.90.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 ¶
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 ¶
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 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 ¶
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 (*Service) Archive ¶ added in v0.90.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) CopyBodiesTx ¶ added in v0.90.0
CopyBodiesTx queues copies in tx; each runs as a durable job. Copies of a kind without body images do nothing.
func (*Service) Covers ¶
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 ¶
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 ¶
ExposeTx brings targets' media to their visibility once it changed: published, unpublished, deleted, or due.
func (*Service) ForkTx ¶
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 ¶
GroupFolders hides, shows and deletes groups' media folders for the groups API (groups.Options.Folders).
func (*Service) 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.90.0
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) RepublishTx ¶
RepublishTx queues versions' publication after their segments changed.
func (*Service) RequestFork ¶
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.
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.90.0
type UnavailableError struct {
}
UnavailableError is a download the policy refused.
func (*UnavailableError) Error ¶ added in v0.90.0
func (e *UnavailableError) Error() string