Documentation
¶
Index ¶
- Variables
- func ChannelScope(entityID string) string
- func OwnerScope(ownerID string) string
- func RatingSQL(likes, dislikes string) string
- type Limits
- type Media
- type MediaFolders
- type Options
- type Runtime
- func (rt *Runtime) AnonymizeAccount(ctx context.Context, actorID, anonymousID string) error
- func (rt *Runtime) CanUpload(ctx context.Context, actor access.Actor, t media.UploadTarget) (media.UploadGrant, error)
- func (rt *Runtime) CheckModerator(context.Context) error
- func (rt *Runtime) Handler() http.Handler
- func (rt *Runtime) ImportComments(ctx context.Context, tx pgx.Tx, in []content.ImportComment) error
- func (rt *Runtime) ImportDeletes(ctx context.Context, tx pgx.Tx, comments, polls []string) error
- func (rt *Runtime) ImportFavorites(ctx context.Context, tx pgx.Tx, in []content.ImportFavorite) error
- func (rt *Runtime) ImportPolls(ctx context.Context, tx pgx.Tx, in []content.ImportPoll) error
- func (rt *Runtime) ImportReactions(ctx context.Context, tx pgx.Tx, in []content.ImportReaction) error
- func (rt *Runtime) ImportVotes(ctx context.Context, tx pgx.Tx, in []content.ImportVote) error
- func (rt *Runtime) ListHeld(ctx context.Context, kind, cursor string, limit int) (content.HeldPage, error)
- func (rt *Runtime) MediaResolver() resolve.Resolver
- func (rt *Runtime) RaisePreferenceFloor(ctx context.Context, tx pgx.Tx, floor int64) error
- func (rt *Runtime) Ref(contentKind, contentID string) contentref.ContentRef
- func (rt *Runtime) Resolve(ctx context.Context, kind, id string, d content.ReviewDecision) error
- func (rt *Runtime) ResyncPreferences(ctx context.Context, send content.PreferenceSender) (content.PreferenceSyncReport, error)
- func (rt *Runtime) SetAccountDeleted(ctx context.Context, actorID string, deleted bool) error
- func (rt *Runtime) SetAccountHidden(ctx context.Context, actorID string, hidden bool) error
- func (rt *Runtime) SyncPreferences(ctx context.Context, send content.PreferenceSender) (content.PreferenceSyncReport, error)
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCommentsClosed: the target's comments are closed or hidden. -> 403 // comments_closed ErrCommentsClosed = errors.New("content: comments are closed") )
var ( // ErrCommentsLocked: the actor can't open the content the comments are // on, and its kind's locked view hides them. -> 403 comments_locked ErrCommentsLocked = errors.New("content: comments are locked") )
var (
ErrConflict = errors.New("content: import conflict")
)
var ( // ErrForbidden: visible but the actor may not consume it. -> 403 ErrForbidden = errors.New("content: not accessible") )
var (
ErrInvalid = errors.New("content: invalid import")
)
var ( // ErrNotFound: the reference does not exist. -> 404 ErrNotFound = errors.New("content: not found") )
var ( // ErrNotVisible: exists but unpublished or soft-deleted. -> 404 (hidden). ErrNotVisible = errors.New("content: not visible") )
var ErrSubjectErased = fmt.Errorf("content: subject erased")
ErrSubjectErased is returned when an erased account tries to write again.
var ( // ErrTenant: a reference of another tenant reached this runtime. ErrTenant = errors.New("content: reference belongs to another tenant") )
var RateLimitRedisErrors = expvar.NewInt("contentkit_content_ratelimit_redis_errors")
RateLimitRedisErrors counts shared-limit Redis failures, each served by the per-process count; published as expvar "contentkit_content_ratelimit_redis_errors".
var TargetTypes = []content.TargetType{content.TargetContent, content.TargetEntity, content.TargetChannel}
TargetTypes are the route families interactions are served under.
Functions ¶
func ChannelScope ¶
ChannelScope is the retained ban scope of a channel and its original posts. The opaque group: prefix preserves existing restrictions.
func OwnerScope ¶
OwnerScope is the scope of content whose Resolution.Owner is ownerID, managed by that owner.
Types ¶
type Limits ¶
type Limits struct {
Comment []content.Rate // default 5 per 5 minutes and 20 per hour
CommentReaction []content.Rate // default 30 per minute
Reaction []content.Rate // default 30 per minute
Favorite []content.Rate // default 20 per minute
PollVote []content.Rate // default 30 per minute
// Redis (or Garnet) shares the limits across replicas; pass the host's
// client. Without it each process counts alone, so N replicas allow N
// times the limit. Redis errors fall back to the per-process count.
Redis redis.UniversalClient
KeyPrefix string // default "contentkit:content:rl:"; the tenant follows it
// Disabled turns every limit off.
Disabled bool
}
Limits are per-actor interaction limits: an actor is its user id, or its IP when anonymous. Every attempt spends one, so undoing (unfavorite, neutral) costs the same as doing. A nil field takes its default; an action with several rates must pass all of them.
type Media ¶
type Media struct {
Namespace string // media partition; empty retains the content catalog key
Images content.MediaImages // *media.Manifests
Folders MediaFolders // *media.Jobs
// Reader reads items as the media read API does; the engine fills it
// from its Reader.
Reader content.MediaReader
PollKind string // media kind of poll folders; default "poll"
}
Media connects poll images to ContentKit media. The browser uploads an image to a Named upload path of the poll's item (the host's media registry declares it and its public preset), and hands its name ("i-{uuid}") to ContentKit, which stores it. Reads resolve names to the images' current public files, one MediaImages lookup per response; poll deletion removes the item. Runtime.CanUpload authorizes those uploads.
type MediaFolders ¶
type MediaFolders interface {
RetireItemsTx(ctx context.Context, tx pgx.Tx, items ...media.Deletion) error
}
MediaFolders records exact retirement intents in the poll mutation transaction; the existing content-deletion sweep performs storage erasure.
type Options ¶
type Options struct {
// Pool is the host's shared pgx pool; the runtime does not own its lifecycle.
Pool *pgxpool.Pool
// Schema is ContentKit's schema.
Schema string
// Tenant scopes every row, index, cursor and result. Required.
Tenant string
// Auth says who a request is (authkit/auth; AuthKit's ak.Authenticator()).
// The routes ask it at most once per request and build the actor from
// its Verified: anonymous without a credential, 401 for one that fails.
// Staff hold ContentKit's permissions (access.Permissions) in its root
// scope (auth.Root), checked by the Verified's Can: declare them in its
// roles. Required.
Auth auth.Authenticator
// UserInfo is the host's directory (authkit/userinfo; AuthKit's
// ak.UserInfo()): authors' names and usernames, read once per response
// and never copied. Nil shows authors by id.
UserInfo userinfo.Lookup
// Avatars shows authors' avatars (media.Registry.Avatars); nil shows
// none.
Avatars content.Avatars
// Resolver decides interaction targets: content items and their versions
// (the catalog's) and entities. Required.
Resolver resolve.Resolver
// Kinds is the site's content model: interactions target its content
// kinds and entity kinds, and comments carry one of its languages.
Kinds kinds.Config
// PreferenceSyncOverlap bounds how long a preference write may take to
// commit and still be picked up by SyncPreferences.
// Default DefaultPreferenceSyncOverlap.
PreferenceSyncOverlap time.Duration
// Optional ports (nil -> default).
Processor content.ContentProcessor // comments; default: trim plain text
// Moderator screens comment writes, and the catalog's (Screen); nil
// publishes everything.
Moderator content.ContentModerator
// ModeratorTimeout bounds one Screen call (default 5s); a slower
// moderator counts as failed and the write is held.
ModeratorTimeout time.Duration
// ModeratorCooldown is how long writes are held without calling the
// moderator after it failed repeatedly (default 30s); see CheckModerator.
ModeratorCooldown time.Duration
// Media stores poll images in ContentKit media; nil refuses image writes
// with 501 not_configured. See Media.
Media *Media
// ProviderDataEraser is required when moderator ports retain
// external personal data.
// Nil explicitly means stateless ports; never remove it during an outage.
ProviderDataEraser content.ProviderDataEraser
// Limits are the per-actor interaction rate limits (limits.go); the zero
// value takes the defaults, counted per process.
Limits Limits
// Anonymous is what signed-out visitors may do; the zero value lets them
// only read.
Anonymous content.Anonymous
// CommentMaxLength is the longest comment in characters (runes);
// default DefaultCommentMaxLength.
CommentMaxLength int
// DuplicateWindow refuses identical comments within this duration; zero disables it.
DuplicateWindow time.Duration
// NotificationPublisher is AuthKit's scoped account channel. Nil disables
// user notifications; ContentKit never constructs its own inbox.
NotificationPublisher notify.Publisher
// Accounts serves the account lifecycle routes; nil answers them 500.
Accounts content.AccountLifecycle
// EraseAccountReceipts connects the existing account cleanup lifecycle.
// Nil is used by standalone content runtimes without shared account storage.
EraseAccountReceipts func(context.Context, []string) error
// Logger receives the access log: each request at DEBUG, a 500 at ERROR
// with its cause. nil -> slog.Default().
Logger *slog.Logger
}
Options configures a Runtime. Pool, Schema, Tenant, Auth and Resolver are mandatory; the rest fall back to documented defaults.
type Runtime ¶
type Runtime struct {
// contains filtered or unexported fields
}
Runtime is one tenant's embedded content module: shared deps + the module services, exposing one mountable http.Handler.
func New ¶
New constructs a Runtime over a schema the ContentKit baseline was applied to and wires the module services.
func (*Runtime) AnonymizeAccount ¶
AnonymizeAccount permanently removes account attribution without removing comments or contribution rows. Counters are unchanged.
func (*Runtime) CanUpload ¶
func (rt *Runtime) CanUpload(ctx context.Context, actor access.Actor, t media.UploadTarget) (media.UploadGrant, error)
CanUpload is media's UploadAuthorizer for poll items: the actor holds root:polls:write and the poll exists in this tenant. Every other target is refused; other kinds are routed elsewhere.
func (*Runtime) CheckModerator ¶
CheckModerator reports the moderator's state without calling it: an error while repeated failures hold writes for review. Hosts register it as an optional dependency (helpers deps) for statusz and app_dependency_up.
func (*Runtime) Handler ¶
Handler returns the content routes, each behind its tier's gate over Options.Auth: the engine's content route group (/v1). Each request runs in an access.WithMemo context, so a Gate resolver reads billing at most once per request.
func (*Runtime) ImportComments ¶
ImportComments writes comments in tx, inserting new ones and updating stored ones whose import changed (a moderator's decision stays), then recounts their references' comment counts and their threads' replies.
func (*Runtime) ImportDeletes ¶
ImportDeletes deletes comments and polls in tx as their delete routes do: a comment is tombstoned and its reference's and thread's counts follow; a poll tombstone and exact media retirement intent commit together. The existing content-deletion sweep erases files; deleted rows remain for history.
func (*Runtime) ImportFavorites ¶
func (rt *Runtime) ImportFavorites(ctx context.Context, tx pgx.Tx, in []content.ImportFavorite) error
ImportFavorites writes bookmarks in tx: a new one takes a fresh preference revision. Counts follow.
func (*Runtime) ImportPolls ¶
ImportPolls writes polls and their options in tx; options the import no longer lists go, with their votes.
func (*Runtime) ImportReactions ¶
func (rt *Runtime) ImportReactions(ctx context.Context, tx pgx.Tx, in []content.ImportReaction) error
ImportReactions writes reactions in tx: a new value takes a fresh preference revision, so preference sync exports it. Counts follow.
func (*Runtime) ImportVotes ¶
ImportVotes writes votes in tx (one per person, or IP, per poll; the last wins) and recounts their polls' options.
func (*Runtime) ListHeld ¶
func (rt *Runtime) ListHeld(ctx context.Context, kind, cursor string, limit int) (content.HeldPage, error)
ListHeld pages this tenant's held comments oldest first; cursor is the previous page's Next ("" = start). limit is clamped to [1, 100].
func (*Runtime) MediaResolver ¶
MediaResolver is the resolve.Resolver of the poll media kind: route the host registry's Hooks.Resolver to it for Media.PollKind, as CanUpload is routed. A poll's folder shows while the poll exists, edited by root:polls:write holders. Other refs are omitted (denied).
func (*Runtime) RaisePreferenceFloor ¶
RaisePreferenceFloor advances the preference revision sequence so every later revision exceeds floor: after a Postgres restore, past the newest revision a retained signal plane holds. It never moves it back and fails outside the safe range.
func (*Runtime) Ref ¶
func (rt *Runtime) Ref(contentKind, contentID string) contentref.ContentRef
Ref returns a reference to a work of this runtime's tenant.
func (*Runtime) Resolve ¶
Resolve writes a reviewer's final decision on a held item. Approve publishes it (counts follow); reject keeps it author-visible with the reason. A missing, foreign-tenant or not-held item is ErrNotFound.
func (*Runtime) ResyncPreferences ¶
func (rt *Runtime) ResyncPreferences(ctx context.Context, send content.PreferenceSender) (content.PreferenceSyncReport, error)
ResyncPreferences re-sends every exportable row (zeros included): the periodic safety net for sink loss and slower-than-overlap commits. A sender is required.
func (*Runtime) SetAccountDeleted ¶
SetAccountDeleted changes author presentation during the recovery period. It preserves every interaction and never reverses a completed hard deletion.
func (*Runtime) SetAccountHidden ¶
SetAccountHidden changes comment visibility without changing moderation or account deletion state. Repeating the same state leaves counts unchanged.
func (*Runtime) SyncPreferences ¶
func (rt *Runtime) SyncPreferences(ctx context.Context, send content.PreferenceSender) (content.PreferenceSyncReport, error)
SyncPreferences sends every exportable row whose revision is past the watermark, in revision order, then records a checkpoint. No connection is held across send. Revisions are allocated before commit, so each scan restarts from the newest checkpoint taken at least Options.PreferenceSyncOverlap before the latest one: a row is delivered as long as its transaction commits within the overlap of allocating its revision (ResyncPreferences covers the rest). A checkpoint is recorded only after its own scan was sent, so overlapping syncs stay correct and only repeat sends. send is required.