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 Recounts
- 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, later *Recounts) error
- func (rt *Runtime) ImportDeletes(ctx context.Context, tx pgx.Tx, comments, polls []string, later *Recounts) error
- func (rt *Runtime) ImportFavorites(ctx context.Context, tx pgx.Tx, in []content.ImportFavorite, later *Recounts) 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, later *Recounts) error
- func (rt *Runtime) ImportVotes(ctx context.Context, tx pgx.Tx, in []content.ImportVote, later *Recounts) 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) QueueRecounts(ctx context.Context, tx pgx.Tx, r *Recounts) error
- func (rt *Runtime) QueuedRecounts(ctx context.Context) (int, error)
- func (rt *Runtime) RaisePreferenceFloor(ctx context.Context, tx pgx.Tx, floor int64) error
- func (rt *Runtime) Recount(ctx context.Context, tx pgx.Tx, r *Recounts) error
- func (rt *Runtime) RecountQueued(ctx context.Context, limit int) (int, 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) ScreenComments(ctx context.Context, tx pgx.Tx, in []content.ImportComment) ([]error, error)
- func (rt *Runtime) ScreenFavorites(ctx context.Context, tx pgx.Tx, in []content.ImportFavorite) ([]error, error)
- func (rt *Runtime) ScreenReactions(ctx context.Context, tx pgx.Tx, in []content.ImportReaction) ([]error, error)
- func (rt *Runtime) ScreenVotes(ctx context.Context, tx pgx.Tx, in []content.ImportVote) ([]error, 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 Recounts ¶ added in v0.137.8
type Recounts struct {
// contains filtered or unexported fields
}
Recounts are the counts imports leave to recount once, after records are written in parts: a busy reference's recount can take seconds.
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 ¶
func (rt *Runtime) ImportComments(ctx context.Context, tx pgx.Tx, in []content.ImportComment, later *Recounts) error
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, or leaves that to later.
func (*Runtime) ImportDeletes ¶
func (rt *Runtime) ImportDeletes(ctx context.Context, tx pgx.Tx, comments, polls []string, later *Recounts) error
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 (or later recounts them); 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, later *Recounts) error
ImportFavorites writes bookmarks in tx: a new one takes a fresh preference revision. Counts follow, or later recounts them.
func (*Runtime) ImportPolls ¶
ImportPolls replaces choices in tx, retaining votes for kept option IDs. Removed options cascade to their votes; counts settle before commit.
func (*Runtime) ImportReactions ¶
func (rt *Runtime) ImportReactions(ctx context.Context, tx pgx.Tx, in []content.ImportReaction, later *Recounts) error
ImportReactions writes reactions in tx: a new value takes a fresh preference revision, so preference sync exports it. Counts follow, or later recounts them.
func (*Runtime) ImportVotes ¶
func (rt *Runtime) ImportVotes(ctx context.Context, tx pgx.Tx, in []content.ImportVote, later *Recounts) error
ImportVotes writes votes in tx (one per person, or IP, per poll; the last wins) and recounts their polls' options, or leaves that to later.
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) QueueRecounts ¶ added in v0.137.8
QueueRecounts queues r's targets for RecountQueued instead of recounting them, in tx after the writes that left r: queue rows lock last, in key order. Each queuing takes a new mark from a sequence, so a recount that read an older mark leaves the target queued.
func (*Runtime) QueuedRecounts ¶ added in v0.137.8
QueuedRecounts is how many targets wait for RecountQueued.
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) Recount ¶ added in v0.137.8
Recount sets what r names to what its rows hold. Run after the writes that left r, in their tx, it newly locks only rollups and poll options: those writes hold r's comments and threads.
func (*Runtime) RecountQueued ¶ added in v0.137.8
RecountQueued recounts up to limit queued targets, oldest first, a batch per transaction, and returns how many it dequeued. A target an import holds or queues again meanwhile stays queued, and the call moves on.
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) ScreenComments ¶ added in v0.137.8
func (rt *Runtime) ScreenComments(ctx context.Context, tx pgx.Tx, in []content.ImportComment) ([]error, error)
ScreenComments returns, per comment, why ImportComments would refuse it alone or for its erased author, holding the others' erasure fences.
func (*Runtime) ScreenFavorites ¶ added in v0.137.8
func (rt *Runtime) ScreenFavorites(ctx context.Context, tx pgx.Tx, in []content.ImportFavorite) ([]error, error)
ScreenFavorites is ScreenComments for ImportFavorites.
func (*Runtime) ScreenReactions ¶ added in v0.137.8
func (rt *Runtime) ScreenReactions(ctx context.Context, tx pgx.Tx, in []content.ImportReaction) ([]error, error)
ScreenReactions is ScreenComments for ImportReactions.
func (*Runtime) ScreenVotes ¶ added in v0.137.8
func (rt *Runtime) ScreenVotes(ctx context.Context, tx pgx.Tx, in []content.ImportVote) ([]error, error)
ScreenVotes is ScreenComments for ImportVotes, and refuses too each vote for an option its poll lacks.
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.